diff --git a/Makefile b/Makefile index c052f1cd..49ce6f29 100644 --- a/Makefile +++ b/Makefile @@ -3,12 +3,9 @@ $(shell mkdir -p .cache/make) .PHONY: Makefile -build: .remove-old-cache .cache/make/long-pre-commit .cache/make/long-poetry .cache/make/lint .cache/make/test-latest .cache/make/doc # Build the project faster (only latest Python), for local development (default target if you simply run `make` without targets) +build: .remove-old-cache .cache/make/lint .cache/make/test-latest .cache/make/doc # Simple build: no upgrades (pre-commit/Poetry), test only latest Python. For local development and bug fixes (default target) .PHONY: build -full-build: .remove-old-cache .cache/make/long-pre-commit .cache/make/long-poetry .cache/make/lint .cache/make/test .cache/make/doc # Build the project fully, like in CI -.PHONY: full-build - help: @echo 'Choose one of the following targets:' @cat Makefile | egrep '^[a-z0-9 ./-]*:.*#' | sed -E -e 's/:.+# */@ /g' -e 's/ .+@/@/g' | sort | awk -F@ '{printf " \033[1;34m%-10s\033[0m %s\n", $$1, $$2}' @@ -16,6 +13,9 @@ help: @echo 'Run 'make -B' or 'make --always-make' to force a rebuild of all targets' .PHONY: help +full-build: .remove-old-cache .cache/make/long-pre-commit .cache/make/long-poetry .cache/make/lint .cache/make/test .cache/make/doc # Build the project fully, like in CI +.PHONY: full-build + clean: clean-test # Clean all build output (cache, tox, coverage) rm -rf .cache .mypy_cache docs/_build src/*.egg-info .PHONY: clean @@ -50,6 +50,9 @@ poetry .cache/make/long-poetry: pyproject.toml # Update dependencies touch .cache/make/long-poetry .PHONY: poetry +upgrade: .remove-old-cache .cache/make/long-pre-commit .cache/make/long-poetry # Upgrade pre-commit and Poetry +.PHONY: upgrade + lint .cache/make/lint: .github/*/* .travis/* docs/*.py src/*/* styles/*/* tests/*/* nitpick-style.toml .cache/make/long-poetry # Lint the project (tox running pre-commit, flake8) tox -e lint touch .cache/make/lint diff --git a/poetry.lock b/poetry.lock index 51115213..0b63dace 100644 --- a/poetry.lock +++ b/poetry.lock @@ -66,7 +66,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "babel" -version = "2.8.0" +version = "2.8.1" description = "Internationalization utilities" category = "main" optional = true @@ -580,7 +580,7 @@ python-versions = "*" [[package]] name = "requests" -version = "2.24.0" +version = "2.25.0" description = "Python HTTP for Humans." category = "main" optional = false @@ -590,7 +590,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" certifi = ">=2017.4.17" chardet = ">=3.0.2,<4" idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] @@ -830,7 +830,7 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.25.11" +version = "1.26.1" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -913,8 +913,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] babel = [ - {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, - {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, + {file = "Babel-2.8.1-py2.py3-none-any.whl", hash = "sha256:be432f50d6c38c705ea45a0c05a4bbb22a70742a93888fbae5e7948da1635d23"}, + {file = "Babel-2.8.1.tar.gz", hash = "sha256:820c195271534e8a86f564ba9ef2c207f356cfeb7e94d2bdc6b57897c4233837"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, @@ -1149,8 +1149,8 @@ pytz = [ {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, ] requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, + {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, + {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, ] responses = [ {file = "responses-0.12.0-py2.py3-none-any.whl", hash = "sha256:0de50fbf600adf5ef9f0821b85cc537acca98d66bc7776755924476775c1989c"}, @@ -1268,8 +1268,8 @@ typed-ast = [ {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] urllib3 = [ - {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, - {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, + {file = "urllib3-1.26.1-py2.py3-none-any.whl", hash = "sha256:61ad24434555a42c0439770462df38b47d05d9e8e353d93ec3742900975e3e65"}, + {file = "urllib3-1.26.1.tar.gz", hash = "sha256:097116a6f16f13482d2a2e56792088b9b2920f4eb6b4f84a2c90555fb673db74"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, diff --git a/src/nitpick/fields.py b/src/nitpick/fields.py index 16946998..a7bef37b 100644 --- a/src/nitpick/fields.py +++ b/src/nitpick/fields.py @@ -54,7 +54,7 @@ def string_or_list_field(object_dict, parent_object_dict): # pylint: disable=un def validate_section_dot_field(section_field: str) -> bool: - """Validate if the combinatio section/field has a dot separating them.""" + """Validate if the combination section/field has a dot separating them.""" common = "Use ." if "." not in section_field: raise ValidationError("Dot is missing. {}".format(common)) diff --git a/src/nitpick/plugins/setup_cfg.py b/src/nitpick/plugins/setup_cfg.py index 04a2af7b..2707562a 100644 --- a/src/nitpick/plugins/setup_cfg.py +++ b/src/nitpick/plugins/setup_cfg.py @@ -1,5 +1,6 @@ """Checker for the `setup.cfg ` config file.""" from configparser import ConfigParser +from enum import IntEnum from io import StringIO from typing import Any, Dict, List, Optional, Set, Tuple, Type @@ -10,6 +11,16 @@ from nitpick.typedefs import YieldFlake8Error +class ErrorCodes(IntEnum): + """Setup.cfg error codes.""" + + MissingSections = 1 + MissingValues = 2 + ActualExpected = 3 + MissingKeyValuePairs = 4 + InvalidCommaSeparatedValuesSection = 5 + + class SetupCfgPlugin(NitpickPlugin): """Checker for the `setup.cfg `_ config file. @@ -19,6 +30,7 @@ class SetupCfgPlugin(NitpickPlugin): file_name = "setup.cfg" error_base_number = 320 COMMA_SEPARATED_VALUES = "comma_separated_values" + SECTION_SEPARATOR = "." expected_sections = set() # type: Set[str] missing_sections = set() # type: Set[str] @@ -57,7 +69,17 @@ def check_rules(self) -> YieldFlake8Error: actual_sections = set(setup_cfg.sections()) missing = self.get_missing_output(actual_sections) if missing: - yield self.flake8_error(1, " has some missing sections. Use this:", missing) + yield self.flake8_error(ErrorCodes.MissingSections, " has some missing sections. Use this:", missing) + + csv_sections = {v.split(".")[0] for v in self.comma_separated_values} + missing_csv = csv_sections.difference(actual_sections) + if missing_csv: + yield self.flake8_error( + ErrorCodes.InvalidCommaSeparatedValuesSection, + ": invalid sections on {}:".format(self.COMMA_SEPARATED_VALUES), + ", ".join(sorted(missing_csv)), + ) + return for section in self.expected_sections - self.missing_sections: expected_dict = self.file_dict[section] @@ -79,7 +101,7 @@ def compare_different_keys(self, section, key, raw_actual: Any, raw_expected: An missing = expected_set - actual_set if missing: yield self.flake8_error( - 2, + ErrorCodes.MissingValues, " has missing values in the {!r} key. Include those values:".format(key), "[{}]\n{} = (...),{}".format(section, key, ",".join(sorted(missing))), ) @@ -94,7 +116,7 @@ def compare_different_keys(self, section, key, raw_actual: Any, raw_expected: An expected = raw_expected if actual != expected: yield self.flake8_error( - 3, + ErrorCodes.ActualExpected, ": [{}]{} is {} but it should be like this:".format(section, key, raw_actual), "[{}]\n{} = {}".format(section, key, raw_expected), ) @@ -106,7 +128,11 @@ def show_missing_keys( # pylint: disable=unused-argument missing_cfg = ConfigParser() missing_cfg[section] = dict(values) output = self.get_example_cfg(missing_cfg) - yield self.flake8_error(4, ": section [{}] has some missing key/value pairs. Use this:".format(section), output) + yield self.flake8_error( + ErrorCodes.MissingKeyValuePairs, + ": section [{}] has some missing key/value pairs. Use this:".format(section), + output, + ) @staticmethod def get_example_cfg(config_parser: ConfigParser) -> str: diff --git a/tests/test_setup_cfg.py b/tests/test_setup_cfg.py index 93b3d40c..6b833a91 100644 --- a/tests/test_setup_cfg.py +++ b/tests/test_setup_cfg.py @@ -179,3 +179,27 @@ def test_invalid_section_dot_fields(request): nitpick.files."setup.cfg".comma_separated_values.3: Empty field name. Use .\x1b[0m """ ) + + +def test_invalid_sections_comma_separated_values(request): + """Test invalid sections on comma_separated_values.""" + ProjectMock(request).style( + """ + ["setup.cfg".flake8] + ignore = "W503,E203,FI58,PT003,C408" + exclude = "venv*,**/migrations/" + per-file-ignores = "tests/**.py:FI18,setup.py:FI18" + + [nitpick.files."setup.cfg"] + comma_separated_values = ["flake8.ignore", "flake8.exclude", "falek8.per-file-ignores", "aaa.invalid-section"] + """ + ).setup_cfg( + """ + [flake8] + exclude = venv*,**/migrations/ + ignore = W503,E203,FI12,FI15,FI16,FI17,FI18,FI50,FI51,FI53,FI54,FI55,FI58,PT003,C408 + per-file-ignores = tests/**.py:FI18,setup.py:FI18,tests/**.py:BZ01 + """ + ).flake8().assert_single_error( + "NIP325 File setup.cfg: invalid sections on comma_separated_values:\x1b[32m\naaa, falek8\x1b[0m" + )