Skip to content

Commit

Permalink
Normalize input of ignore-paths for all path types
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielNoord committed Oct 21, 2021
1 parent 80205dc commit d52b9c0
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 44 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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

Expand Down
15 changes: 15 additions & 0 deletions pylint/config/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,6 +27,16 @@ 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("\\", "\\\\"))
)
patterns.append(re.compile(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"
Expand Down Expand Up @@ -80,6 +92,7 @@ def _py_version_validator(_, name, value):
"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),
Expand Down Expand Up @@ -122,6 +135,7 @@ class Option(optparse.Option):
TYPES = optparse.Option.TYPES + (
"regexp",
"regexp_csv",
"regexp_paths_csv",
"csv",
"yn",
"multiple_choice",
Expand All @@ -132,6 +146,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
Expand Down
2 changes: 1 addition & 1 deletion pylint/lint/expand_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions pylint/lint/pylinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@
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,
Expand Down Expand Up @@ -220,12 +227,12 @@ def make_options():
(
"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.",
},
),
(
Expand Down Expand Up @@ -1101,7 +1108,7 @@ def _expand_files(self, modules) -> List[ModuleDescriptionDict]:
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"]
Expand Down Expand Up @@ -1259,6 +1266,7 @@ def open(self):
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 !
Expand Down
2 changes: 1 addition & 1 deletion pylint/testutils/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def _forward(self, *args, **test_function_kwargs):
self.checker.set_option(
key.replace("_", "-"),
value,
optdict=dict(PyLinter.make_options()),
optdict=dict(PyLinter.make_options())[key.replace("_", "-")],
)
if isinstance(self, CheckerTestCase):
# reopen checker in case, it may be interested in configuration change
Expand Down
19 changes: 18 additions & 1 deletion pylint/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,24 @@
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, ...],
)


Expand Down Expand Up @@ -220,6 +228,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",
Expand Down
104 changes: 70 additions & 34 deletions tests/lint/unittest_expand_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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")
Expand Down Expand Up @@ -84,27 +77,70 @@ def test__is_in_ignore_list_re_nomatch() -> None:
}


@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
32 changes: 32 additions & 0 deletions tests/unittest_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -65,3 +69,31 @@ 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 TestOptionSetters(CheckerTestCase):
"""Class to check the set_config decorator and get_global_option util
Ideally all configs should be tested here."""

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"""
options = get_global_option(self.checker, "ignore-paths")

assert not options

0 comments on commit d52b9c0

Please sign in to comment.