diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | doc/whatsnew/2.12.rst | 5 | ||||
-rw-r--r-- | pylint/config/option.py | 18 | ||||
-rw-r--r-- | pylint/lint/expand_modules.py | 2 | ||||
-rw-r--r-- | pylint/lint/pylinter.py | 22 | ||||
-rw-r--r-- | pylint/utils/utils.py | 19 | ||||
-rw-r--r-- | tests/lint/unittest_expand_modules.py | 104 | ||||
-rw-r--r-- | tests/unittest_config.py | 34 |
8 files changed, 166 insertions, 43 deletions
@@ -82,6 +82,11 @@ Release date: TBA Closes #3031 +* Normalize the input to the ``ignore-paths`` option to allow both Posix and + Windows paths + + Closes #5194 + * Fix double emitting of ``not-callable`` on inferrable ``properties`` Closes #4426 diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index 2a6c321fe..1126ebd53 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -86,6 +86,11 @@ Other Changes Closes #4426 +* Normalize the input to the ``ignore-paths`` option to allow both Posix and + Windows paths + + Closes #5194 + * ``mising-param-doc`` now correctly parses asterisks for variable length and keyword parameters diff --git a/pylint/config/option.py b/pylint/config/option.py index 0a9a37069..7b3e3b1b1 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -3,7 +3,9 @@ import copy import optparse # pylint: disable=deprecated-module +import pathlib import re +from typing import List, Pattern from pylint import utils @@ -25,6 +27,19 @@ def _regexp_csv_validator(_, name, value): return [_regexp_validator(_, name, val) for val in _csv_validator(_, name, value)] +def _regexp_paths_csv_validator(_, name: str, value: str) -> List[Pattern[str]]: + patterns = [] + for val in _csv_validator(_, name, value): + patterns.append( + re.compile( + str(pathlib.PureWindowsPath(val)).replace("\\", "\\\\") + + "|" + + pathlib.PureWindowsPath(val).as_posix() + ) + ) + return patterns + + def _choice_validator(choices, name, value): if value not in choices: msg = "option %s: invalid value: %r, should be in %s" @@ -80,6 +95,7 @@ VALIDATORS = { "float": float, "regexp": lambda pattern: re.compile(pattern or ""), "regexp_csv": _regexp_csv_validator, + "regexp_paths_csv": _regexp_paths_csv_validator, "csv": _csv_validator, "yn": _yn_validator, "choice": lambda opt, name, value: _choice_validator(opt["choices"], name, value), @@ -122,6 +138,7 @@ class Option(optparse.Option): TYPES = optparse.Option.TYPES + ( "regexp", "regexp_csv", + "regexp_paths_csv", "csv", "yn", "multiple_choice", @@ -132,6 +149,7 @@ class Option(optparse.Option): TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER) TYPE_CHECKER["regexp"] = _regexp_validator TYPE_CHECKER["regexp_csv"] = _regexp_csv_validator + TYPE_CHECKER["regexp_paths_csv"] = _regexp_paths_csv_validator TYPE_CHECKER["csv"] = _csv_validator TYPE_CHECKER["yn"] = _yn_validator TYPE_CHECKER["multiple_choice"] = _multiple_choices_validating_option diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 1d4432322..cea4b5c17 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -43,7 +43,7 @@ def expand_modules( files_or_modules: List[str], ignore_list: List[str], ignore_list_re: List[Pattern], - ignore_list_paths_re: List[Pattern], + ignore_list_paths_re: List[Pattern[str]], ) -> Tuple[List[ModuleDescriptionDict], List[ErrorDescriptionDict]]: """take a list of files/modules/packages and return the list of tuple (file, module name) which have to be actually checked diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 6e6ccd59b..3be78c46c 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -46,7 +46,14 @@ from pylint.typing import ( MessageLocationTuple, ModuleDescriptionDict, ) -from pylint.utils import ASTWalker, FileState, LinterStats, ModuleStats, utils +from pylint.utils import ( + ASTWalker, + FileState, + LinterStats, + ModuleStats, + get_global_option, + utils, +) from pylint.utils.pragma_parser import ( OPTION_PO, InvalidPragmaError, @@ -220,12 +227,12 @@ class PyLinter( ( "ignore-paths", { - "type": "regexp_csv", + "type": "regexp_paths_csv", "metavar": "<pattern>[,<pattern>...]", - "dest": "ignore_list_paths_re", - "default": (), - "help": "Add files or directories matching the regex patterns to the" - " ignore-list. The regex matches against paths.", + "default": [], + "help": "Add files or directories matching the regex patterns to the " + "ignore-list. The regex matches against paths and can be in " + "Posix or Windows format.", }, ), ( @@ -1101,7 +1108,7 @@ class PyLinter( modules, self.config.black_list, self.config.black_list_re, - self.config.ignore_list_paths_re, + self._ignore_paths, ) for error in errors: message = modname = error["mod"] @@ -1259,6 +1266,7 @@ class PyLinter( self.config.extension_pkg_whitelist ) self.stats.reset_message_count() + self._ignore_paths = get_global_option(self, "ignore-paths") def generate_reports(self): """close the whole package /module, it's time to make reports ! diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 96f8a6acc..6c9f11298 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -56,16 +56,24 @@ GLOBAL_OPTION_LIST = Literal["ignored-modules"] GLOBAL_OPTION_PATTERN = Literal[ "no-docstring-rgx", "dummy-variables-rgx", "ignored-argument-names" ] +GLOBAL_OPTION_PATTERN_LIST = Literal["ignore-paths"] GLOBAL_OPTION_TUPLE_INT = Literal["py-version"] GLOBAL_OPTION_NAMES = Union[ GLOBAL_OPTION_BOOL, GLOBAL_OPTION_INT, GLOBAL_OPTION_LIST, GLOBAL_OPTION_PATTERN, + GLOBAL_OPTION_PATTERN_LIST, GLOBAL_OPTION_TUPLE_INT, ] T_GlobalOptionReturnTypes = TypeVar( - "T_GlobalOptionReturnTypes", bool, int, List[str], Pattern[str], Tuple[int, ...] + "T_GlobalOptionReturnTypes", + bool, + int, + List[str], + Pattern[str], + List[Pattern[str]], + Tuple[int, ...], ) @@ -223,6 +231,15 @@ def get_global_option( @overload def get_global_option( checker: "BaseChecker", + option: GLOBAL_OPTION_PATTERN_LIST, + default: Optional[List[Pattern[str]]] = None, +) -> List[Pattern[str]]: + ... + + +@overload +def get_global_option( + checker: "BaseChecker", option: GLOBAL_OPTION_TUPLE_INT, default: Optional[Tuple[int, ...]] = None, ) -> Tuple[int, ...]: diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index ef98cf575..4d679ad64 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -4,10 +4,14 @@ import re from pathlib import Path +from typing import Dict, Tuple, Type import pytest +from pylint.checkers import BaseChecker from pylint.lint.expand_modules import _is_in_ignore_list_re, expand_modules +from pylint.testutils import CheckerTestCase, set_config +from pylint.utils.utils import get_global_option def test__is_in_ignore_list_re_match() -> None: @@ -21,17 +25,6 @@ def test__is_in_ignore_list_re_match() -> None: assert _is_in_ignore_list_re("src/tests/whatever.xml", patterns) -def test__is_in_ignore_list_re_nomatch() -> None: - patterns = [ - re.compile(".*enchilada.*"), - re.compile("unittest_.*"), - re.compile(".*tests/.*"), - ] - assert not _is_in_ignore_list_re("test_utils.py", patterns) - assert not _is_in_ignore_list_re("enchilad.py", patterns) - assert not _is_in_ignore_list_re("src/tests.py", patterns) - - TEST_DIRECTORY = Path(__file__).parent.parent INIT_PATH = str(TEST_DIRECTORY / "lint/__init__.py") EXPAND_MODULES = str(TEST_DIRECTORY / "lint/unittest_expand_modules.py") @@ -84,27 +77,70 @@ init_of_package = { } -@pytest.mark.parametrize( - "files_or_modules,expected", - [ - ([__file__], [this_file]), - ( - [Path(__file__).parent], - [ - init_of_package, - test_pylinter, - test_utils, - this_file_from_init, - unittest_lint, - ], - ), - ], -) -def test_expand_modules(files_or_modules, expected): - ignore_list, ignore_list_re, ignore_list_paths_re = [], [], [] - modules, errors = expand_modules( - files_or_modules, ignore_list, ignore_list_re, ignore_list_paths_re +class TestExpandModules(CheckerTestCase): + """Test the expand_modules function while allowing options to be set""" + + class Checker(BaseChecker): + """This dummy checker is needed to allow options to be set""" + + name = "checker" + msgs: Dict[str, Tuple[str, ...]] = {} + options = (("An option", {"An option": "dict"}),) + + CHECKER_CLASS: Type = Checker + + @pytest.mark.parametrize( + "files_or_modules,expected", + [ + ([__file__], [this_file]), + ( + [str(Path(__file__).parent)], + [ + init_of_package, + test_pylinter, + test_utils, + this_file_from_init, + unittest_lint, + ], + ), + ], + ) + @set_config(ignore_paths="") + def test_expand_modules(self, files_or_modules, expected): + """Test expand_modules with the default value of ignore-paths""" + ignore_list, ignore_list_re = [], [] + modules, errors = expand_modules( + files_or_modules, + ignore_list, + ignore_list_re, + get_global_option(self, "ignore-paths"), + ) + modules.sort(key=lambda d: d["name"]) + assert modules == expected + assert not errors + + @pytest.mark.parametrize( + "files_or_modules,expected", + [ + ([__file__], []), + ( + [str(Path(__file__).parent)], + [ + init_of_package, + ], + ), + ], ) - modules.sort(key=lambda d: d["name"]) - assert modules == expected - assert not errors + @set_config(ignore_paths=".*/lint/.*") + def test_expand_modules_with_ignore(self, files_or_modules, expected): + """Test expand_modules with a non-default value of ignore-paths""" + ignore_list, ignore_list_re = [], [] + modules, errors = expand_modules( + files_or_modules, + ignore_list, + ignore_list_re, + get_global_option(self.checker, "ignore-paths"), + ) + modules.sort(key=lambda d: d["name"]) + assert modules == expected + assert not errors diff --git a/tests/unittest_config.py b/tests/unittest_config.py index 9be02fc4c..9979aa41b 100644 --- a/tests/unittest_config.py +++ b/tests/unittest_config.py @@ -16,10 +16,14 @@ import re import sre_constants +from typing import Dict, Tuple, Type import pytest from pylint import config +from pylint.checkers import BaseChecker +from pylint.testutils import CheckerTestCase, set_config +from pylint.utils.utils import get_global_option RE_PATTERN_TYPE = getattr(re, "Pattern", getattr(re, "_pattern_type", None)) @@ -65,3 +69,33 @@ def test__regexp_csv_validator_invalid() -> None: pattern_strings = ["test_.*", "foo\\.bar", "^baz)$"] with pytest.raises(sre_constants.error): config.option._regexp_csv_validator(None, None, ",".join(pattern_strings)) + + +class TestPyLinterOptionSetters(CheckerTestCase): + """Class to check the set_config decorator and get_global_option util + for options declared in PyLinter.""" + + class Checker(BaseChecker): + name = "checker" + msgs: Dict[str, Tuple[str, ...]] = {} + options = (("An option", {"An option": "dict"}),) + + CHECKER_CLASS: Type = Checker + + @set_config(ignore_paths=".*/tests/.*,.*\\ignore\\.*") + def test_ignore_paths_with_value(self) -> None: + """Test ignore-paths option with value""" + options = get_global_option(self.checker, "ignore-paths") + + assert any(i.match("dir/tests/file.py") for i in options) + assert any(i.match("dir\\tests\\file.py") for i in options) + assert any(i.match("dir/ignore/file.py") for i in options) + assert any(i.match("dir\\ignore\\file.py") for i in options) + + def test_ignore_paths_with_no_value(self) -> None: + """Test ignore-paths option with no value. + Compare against actual list to see if validator works.""" + options = get_global_option(self.checker, "ignore-paths") + + # pylint: disable-next=use-implicit-booleaness-not-comparison + assert options == [] |