summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-10-24 12:48:19 +0200
committerGitHub <noreply@github.com>2021-10-24 12:48:19 +0200
commitbfc8d32273102495ee1cfd8daa14d9b6693b11f7 (patch)
tree60785b9a4d5981f06ce2d09a8113803417cab479
parent8eb7ff153e951f69b4d1ff3cbddc367c797c0aa4 (diff)
downloadpylint-git-bfc8d32273102495ee1cfd8daa14d9b6693b11f7.tar.gz
Normalize input of ``ignore-paths`` for all path types (#5201)
Closes #5194 Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.12.rst5
-rw-r--r--pylint/config/option.py18
-rw-r--r--pylint/lint/expand_modules.py2
-rw-r--r--pylint/lint/pylinter.py22
-rw-r--r--pylint/utils/utils.py19
-rw-r--r--tests/lint/unittest_expand_modules.py104
-rw-r--r--tests/unittest_config.py34
8 files changed, 166 insertions, 43 deletions
diff --git a/ChangeLog b/ChangeLog
index 13a2d977f..be49bc113 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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 == []