Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize input of ignore-paths for all path types #5201

Merged
merged 8 commits into from
Oct 24, 2021
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
18 changes: 18 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,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"
Expand Down Expand Up @@ -80,6 +95,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 +138,7 @@ class Option(optparse.Option):
TYPES = optparse.Option.TYPES + (
"regexp",
"regexp_csv",
"regexp_paths_csv",
"csv",
"yn",
"multiple_choice",
Expand All @@ -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
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
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
34 changes: 34 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,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
DanielNoord marked this conversation as resolved.
Show resolved Hide resolved
assert options == []