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

Release branch 2.13.9 #6563

Merged
merged 8 commits into from
May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ contributors:
- Ville Skyttä <[email protected]>
- Matus Valo <[email protected]>
- Pierre-Yves David <[email protected]>
- David Shea <[email protected]>: invalid sequence and slice index
- Mark Byrne <[email protected]>
- David Shea <[email protected]>: invalid sequence and slice index
- Derek Gustafson <[email protected]>
- Cezar Elnazli <[email protected]>: deprecated-method
- Nicolas Chauvat <[email protected]>
Expand Down
26 changes: 25 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,32 @@ Release date: TBA

What's New in Pylint 2.13.9?
============================
Release date: TBA
Release date: 2022-05-13


* Respect ignore configuration options with ``--recursive=y``.

Closes #6471

* Fix false positives for ``no-name-in-module`` and ``import-error`` for ``numpy.distutils`` and ``pydantic``.

Closes #6497

* Fix ``IndexError`` crash in ``uninferable_final_decorators`` method.

Relates to #6531

* Fix a crash in ``unnecessary-dict-index-lookup`` when subscripting an attribute.

Closes #6557

* Fix a crash when accessing ``__code__`` and assigning it to a variable.

Closes #6539

* Fix a false positive for ``undefined-loop-variable`` when using ``enumerate()``.

Closes #6593


What's New in Pylint 2.13.8?
Expand Down
22 changes: 22 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,29 @@ Other Changes

Closes #3979

* Fix a crash when accessing ``__code__`` and assigning it to a variable.

Closes #6539

* Fix a crash when linting a file that passes an integer ``mode=`` to
``open``

Closes #6414


* Fix false positives for ``no-name-in-module`` and ``import-error`` for ``numpy.distutils``
and ``pydantic``.

Closes #6497

* Fix ``IndexError`` crash in ``uninferable_final_decorators`` method.

Relates to #6531

* Fix a crash in ``unnecessary-dict-index-lookup`` when subscripting an attribute.

Closes #6557

* Fix a false positive for ``undefined-loop-variable`` when using ``enumerate()``.

Closes #6593
2 changes: 1 addition & 1 deletion pylint/__pkginfo__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from typing import Tuple

__version__ = "2.13.8"
__version__ = "2.13.9"


def get_numversion_from_version(v: str) -> Tuple:
Expand Down
1 change: 1 addition & 0 deletions pylint/checkers/refactoring/refactoring_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1938,6 +1938,7 @@ def _check_unnecessary_dict_index_lookup(
elif isinstance(value, nodes.Subscript):
if (
not isinstance(node.target, nodes.AssignName)
or not isinstance(value.value, nodes.Name)
or node.target.name != value.value.name
or iterating_object_name != subscript.value.as_string()
):
Expand Down
30 changes: 18 additions & 12 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,28 +820,34 @@ def uninferable_final_decorators(
"""
decorators = []
for decorator in getattr(node, "nodes", []):
import_nodes = None

# Get the `Import` node. The decorator is of the form: @module.name
if isinstance(decorator, nodes.Attribute):
try:
import_node = decorator.expr.lookup(decorator.expr.name)[1][0]
except AttributeError:
continue
inferred = safe_infer(decorator.expr)
if isinstance(inferred, nodes.Module) and inferred.qname() == "typing":
_, import_nodes = decorator.expr.lookup(decorator.expr.name)

# Get the `ImportFrom` node. The decorator is of the form: @name
elif isinstance(decorator, nodes.Name):
lookup_values = decorator.lookup(decorator.name)
if lookup_values[1]:
import_node = lookup_values[1][0]
else:
continue # pragma: no cover # Covered on Python < 3.8
else:
_, import_nodes = decorator.lookup(decorator.name)

# The `final` decorator is expected to be found in the
# import_nodes. Continue if we don't find any `Import` or `ImportFrom`
# nodes for this decorator.
if not import_nodes:
continue
import_node = import_nodes[0]

if not isinstance(import_node, (astroid.Import, astroid.ImportFrom)):
continue

import_names = dict(import_node.names)

# from typing import final
# Check if the import is of the form: `from typing import final`
is_from_import = ("final" in import_names) and import_node.modname == "typing"
# import typing

# Check if the import is of the form: `import typing`
is_import = ("typing" in import_names) and getattr(
decorator, "attrname", None
) == "final"
Expand Down
7 changes: 7 additions & 0 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,13 @@ def _loopvar_name(self, node: astroid.Name) -> None:
# For functions we can do more by inferring the length of the itered object
try:
inferred = next(assign.iter.infer())
# Prefer the target of enumerate() rather than the enumerate object itself
if (
isinstance(inferred, astroid.Instance)
and inferred.qname() == "builtins.enumerate"
and assign.iter.args
):
inferred = next(assign.iter.args[0].infer())
except astroid.InferenceError:
self.add_message("undefined-loop-variable", args=node.name, node=node)
else:
Expand Down
20 changes: 16 additions & 4 deletions pylint/lint/expand_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ def _is_in_ignore_list_re(element: str, ignore_list_re: List[Pattern]) -> bool:
return any(file_pattern.match(element) for file_pattern in ignore_list_re)


def _is_ignored_file(
element: str,
ignore_list,
ignore_list_re,
ignore_list_paths_re,
) -> bool:
basename = os.path.basename(element)
return (
basename in ignore_list
or _is_in_ignore_list_re(basename, ignore_list_re)
or _is_in_ignore_list_re(element, ignore_list_paths_re)
)


def expand_modules(
files_or_modules: List[str],
ignore_list: List[str],
Expand All @@ -58,10 +72,8 @@ def expand_modules(

for something in files_or_modules:
basename = os.path.basename(something)
if (
basename in ignore_list
or _is_in_ignore_list_re(os.path.basename(something), ignore_list_re)
or _is_in_ignore_list_re(something, ignore_list_paths_re)
if _is_ignored_file(
something, ignore_list, ignore_list_re, ignore_list_paths_re
):
continue
module_path = get_python_path(something)
Expand Down
20 changes: 15 additions & 5 deletions pylint/lint/pylinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
MSG_TYPES_LONG,
MSG_TYPES_STATUS,
)
from pylint.lint.expand_modules import expand_modules
from pylint.lint.expand_modules import _is_ignored_file, expand_modules
from pylint.lint.parallel import check_parallel
from pylint.lint.report_functions import (
report_messages_by_module_stats,
Expand Down Expand Up @@ -251,6 +251,7 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]:
"type": "regexp_paths_csv",
"metavar": "<pattern>[,<pattern>...]",
"default": [],
"dest": "ignore_paths",
"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 @@ -1013,9 +1014,8 @@ def initialize(self):
if not msg.may_be_emitted():
self._msgs_state[msg.msgid] = False

@staticmethod
def _discover_files(files_or_modules: Sequence[str]) -> Iterator[str]:
"""Discover python modules and packages in subdirectory.
def _discover_files(self, files_or_modules: Sequence[str]) -> Iterator[str]:
"""Discover python modules and packages in sub-directory.

Returns iterator of paths to discovered modules and packages.
"""
Expand All @@ -1028,6 +1028,16 @@ def _discover_files(files_or_modules: Sequence[str]) -> Iterator[str]:
if any(root.startswith(s) for s in skip_subtrees):
# Skip subtree of already discovered package.
continue

if _is_ignored_file(
root,
self.config.black_list,
self.config.black_list_re,
self.config.ignore_paths,
):
skip_subtrees.append(root)
continue

if "__init__.py" in files:
skip_subtrees.append(root)
yield root
Expand Down Expand Up @@ -1184,7 +1194,7 @@ def _expand_files(self, modules) -> List[ModuleDescriptionDict]:
modules,
self.config.black_list,
self.config.black_list_re,
self._ignore_paths,
self.config.ignore_paths,
)
for error in errors:
message = modname = error["mod"]
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_min.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-e .[testutil]
# astroid dependency is also defined in setup.cfg
astroid==2.11.3 # Pinned to a specific version for tests
astroid==2.11.5 # Pinned to a specific version for tests
typing-extensions~=4.1
pytest~=7.0
pytest-benchmark~=3.4
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ install_requires =
# Also upgrade requirements_test_min.txt if you are bumping astroid.
# Pinned to dev of next minor update to allow editable installs,
# see https://github.com/PyCQA/astroid/issues/1341
astroid>=2.11.3,<=2.12.0-dev0
astroid>=2.11.5,<=2.12.0-dev0
isort>=4.2.5,<6
mccabe>=0.6,<0.8
tomli>=1.1.0;python_version<"3.11"
Expand Down
2 changes: 1 addition & 1 deletion tbump.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
github_url = "https://github.com/PyCQA/pylint"

[version]
current = "2.13.8"
current = "2.13.9"
regex = '''
^(?P<major>0|[1-9]\d*)
\.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ def A(): # [invalid-name]
CONSTD = A()

CONST = "12 34 ".rstrip().split()


assignment_that_crashed_pylint = type(float.__new__.__code__)
6 changes: 6 additions & 0 deletions tests/functional/n/no/no_name_in_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,9 @@

# Check ignored-modules setting
from argparse import THIS_does_not_EXIST


# This captures the original failure in https://github.com/PyCQA/pylint/issues/6497
# only if numpy is installed. We are not installing numpy on CI (for now)
from numpy.distutils.misc_util import is_sequence
from pydantic import BaseModel
30 changes: 30 additions & 0 deletions tests/functional/r/regression/regression_6531_crash_index_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Regression test for https://github.com/PyCQA/pylint/issues/6531."""

# pylint: disable=missing-docstring, redefined-outer-name

import pytest


class Wallet:
def __init__(self):
self.balance = 0

def add_cash(self, earned):
self.balance += earned

def spend_cash(self, spent):
self.balance -= spent

@pytest.fixture
def my_wallet():
'''Returns a Wallet instance with a zero balance'''
return Wallet()

@pytest.mark.parametrize("earned,spent,expected", [
(30, 10, 20),
(20, 2, 18),
])
def test_transactions(my_wallet, earned, spent, expected):
my_wallet.add_cash(earned)
my_wallet.spend_cash(spent)
assert my_wallet.balance == expected
7 changes: 7 additions & 0 deletions tests/functional/u/undefined/undefined_loop_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,10 @@ def variable_name_assigned_in_body_of_second_loop():
alias = True
if alias:
print(alias)


def use_enumerate():
"""https://github.com/PyCQA/pylint/issues/6593"""
for i, num in enumerate(range(3)):
pass
print(i, num)
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,10 @@ class Foo:
d = {}
for key, in d.items():
print(d[key])

# Test subscripting an attribute
# https://github.com/PyCQA/pylint/issues/6557
f = Foo()
for input_output in d.items():
f.input_output = input_output # pylint: disable=attribute-defined-outside-init
print(d[f.input_output[0]])
Loading