Skip to content

Commit

Permalink
Merge branch 'master' into issue-4412-python-360
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Sassoulas authored May 11, 2021
2 parents 75914fb + 9b268ec commit 5fed063
Show file tree
Hide file tree
Showing 30 changed files with 398 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ updates:
labels:
- "dependency"
open-pull-requests-limit: 10
rebase-strategy: "disabled"

- package-ecosystem: "github-actions"
directory: "/"
Expand All @@ -15,3 +16,4 @@ updates:
labels:
- "dependency"
open-pull-requests-limit: 10
rebase-strategy: "disabled"
8 changes: 4 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev]
outputs:
python-key: ${{ steps.generate-python-key.outputs.key }}
steps:
Expand Down Expand Up @@ -189,7 +189,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev]
steps:
- name: Check out code from GitHub
uses: actions/[email protected]
Expand Down Expand Up @@ -321,7 +321,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev]
outputs:
python-key: ${{ steps.generate-python-key.outputs.key }}
steps:
Expand Down Expand Up @@ -363,7 +363,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev]
steps:
- name: Set temp directory
run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repos:
- --remove-duplicate-keys
- --remove-unused-variables
- repo: https://github.com/asottile/pyupgrade
rev: v2.14.0
rev: v2.15.0
hooks:
- id: pyupgrade
args: [--py36-plus]
Expand All @@ -31,7 +31,7 @@ repos:
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 21.4b2
rev: 21.5b1
hooks:
- id: black
args: [--safe, --quiet]
Expand All @@ -41,7 +41,7 @@ repos:
hooks:
- id: black-disable-checker
- repo: https://github.com/PyCQA/flake8
rev: 3.9.1
rev: 3.9.2
hooks:
- id: flake8
exclude: *fixtures
Expand Down Expand Up @@ -80,7 +80,7 @@ repos:
additional_dependencies: []
exclude: tests/functional/|tests/input|tests/extensions/data|tests/regrtest_data/|tests/data/|doc/|bin/
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.2.1
rev: v2.3.0
hooks:
- id: prettier
args: [--prose-wrap=always, --print-width=88]
4 changes: 4 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,7 @@ contributors:
* das-intensity: contributor

* Jiajunsu (victor): contributor

* Andrew Haigh (nelfin): contributor

* Pang Yu Shao (yushao2): contributor
24 changes: 17 additions & 7 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
Pylint's ChangeLog
------------------



What's New in Pylint 2.8.3?
What's New in Pylint 2.9.0?
===========================
Release date: 2021-04-26
Release date: TBA

..
Put new features and bugfixes here and also in 'doc/whatsnew/2.9.rst'

* The warning for ``arguments-differ`` now signals explicitly the difference it detected
by naming the argument or arguments that changed and the type of change that occurred.

* Suppress ``consider-using-with`` inside context managers.

Closes #4430
Expand All @@ -31,6 +32,18 @@ Release date: 2021-04-26

Closes #4412

* Stdlib deprecated modules check is moved to stdlib checker. New deprecated
modules are added.

* Fix raising false-positive ``no-member`` on abstract properties

* New checker ``consider-using-dict-items``. Emitted when iterating over dictionary keys and then
indexing the same dictionary with the key within loop body.

Closes #3389

* Don't emit ``import-error`` if import guarded behind ``if sys.version_info >= (x, x)``


What's New in Pylint 2.8.2?
===========================
Expand Down Expand Up @@ -59,9 +72,6 @@ Release date: 2021-04-25

Closes #4399

* The warning for ``arguments-differ`` now signals explicitly the difference it detected
by naming the argument or arguments that changed and the type of change that occured.


What's New in Pylint 2.8.0?
===========================
Expand Down
4 changes: 2 additions & 2 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Sphinx==3.5.4
python-docs-theme==2020.12
Sphinx==4.0.0
python-docs-theme==2021.5
-e .
3 changes: 3 additions & 0 deletions doc/whatsnew/2.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Summary -- Release highlights
New checkers
============

* ``consider-using-dict-items``: Emitted when iterating over dictionary keys and then
indexing the same dictionary with the key within loop body.

Other Changes
=============

Expand Down
28 changes: 4 additions & 24 deletions pylint/checkers/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ def _has_different_parameters(
original: List[astroid.AssignName],
overridden: List[astroid.AssignName],
dummy_parameter_regex: Pattern,
counter: int,
) -> List[str]:
result = []
zipped = zip_longest(original, overridden)
Expand All @@ -280,25 +279,14 @@ def _has_different_parameters(
if not all(params):
return ["Number of parameters "]

# check for the arguments' type
original_type = original_param.parent.annotations[counter]
if original_type is not None:
overridden_type = overridden_param.parent.annotations[counter]
if overridden_type is not None:
if original_type.name != overridden_type.name:
result.append(
f"Parameter '{original_param.name}' was of type '{original_type.name}' and is now"
+ f" of type '{overridden_type.name}' in"
)
counter += 1

# check for the arguments' name
names = [param.name for param in params]
if any(dummy_parameter_regex.match(name) for name in names):
continue
if original_param.name != overridden_param.name:
result.append(
f"Parameter '{original_param.name}' has been renamed to '{overridden_param.name}' in"
f"Parameter '{original_param.name}' has been renamed "
f"to '{overridden_param.name}' in"
)

return result
Expand Down Expand Up @@ -343,19 +331,11 @@ def _different_parameters(
v for v in original.args.kwonlyargs if v.name in overidden_names
]

arguments = list(original.args.args)
# variable 'count' helps to check if the type of an argument has changed
# at the _has_different_parameters method
if any(arg.name == "self" for arg in arguments) and len(arguments) > 1:
count = 1
else:
count = 0

different_positional = _has_different_parameters(
original_parameters, overridden_parameters, dummy_parameter_regex, count
original_parameters, overridden_parameters, dummy_parameter_regex
)
different_kwonly = _has_different_parameters(
original_kwonlyargs, overridden.args.kwonlyargs, dummy_parameter_regex, count
original_kwonlyargs, overridden.args.kwonlyargs, dummy_parameter_regex
)
if different_kwonly and different_positional:
if "Number " in different_positional[0] and "Number " in different_kwonly[0]:
Expand Down
15 changes: 14 additions & 1 deletion pylint/checkers/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ def _ignore_import_failure(node, modname, ignored_modules):
if submodule in ignored_modules:
return True

# ignore import failure if guarded by `sys.version_info` test
if isinstance(node.parent, astroid.If) and isinstance(
node.parent.test, astroid.Compare
):
value = node.parent.test.left
if isinstance(value, astroid.Subscript):
value = value.value
if (
isinstance(value, astroid.Attribute)
and value.as_string() == "sys.version_info"
):
return True

return node_ignores_exception(node, ImportError)


Expand Down Expand Up @@ -309,7 +322,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
name = "imports"
msgs = MSGS
priority = -2
default_deprecated_modules = ("optparse", "tkinter.tix")
default_deprecated_modules = ()

options = (
(
Expand Down
95 changes: 93 additions & 2 deletions pylint/checkers/refactoring/recommendation_checker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/master/LICENSE
from typing import cast

import astroid

Expand All @@ -26,6 +27,14 @@ class RecommendationChecker(checkers.BaseChecker):
"method. It is enough to just iterate through the dictionary itself, as "
'in "for key in dictionary".',
),
"C0206": (
"Consider iterating with .items()",
"consider-using-dict-items",
"Emitted when iterating over the keys of a dictionary and accessing the "
"value by index lookup."
"Both the key and value can be accessed by iterating using the .items() "
"method of the dictionary instead.",
),
}

@staticmethod
Expand Down Expand Up @@ -53,8 +62,12 @@ def visit_call(self, node):
if isinstance(node.parent, (astroid.For, astroid.Comprehension)):
self.add_message("consider-iterating-dictionary", node=node)

@utils.check_messages("consider-using-enumerate")
def visit_for(self, node):
@utils.check_messages("consider-using-enumerate", "consider-using-dict-items")
def visit_for(self, node: astroid.For) -> None:
self._check_consider_using_enumerate(node)
self._check_consider_using_dict_items(node)

def _check_consider_using_enumerate(self, node: astroid.For) -> None:
"""Emit a convention whenever range and len are used for indexing."""
# Verify that we have a `range([start], len(...), [stop])` call and
# that the object which is iterated is used as a subscript in the
Expand Down Expand Up @@ -119,3 +132,81 @@ def visit_for(self, node):
continue
self.add_message("consider-using-enumerate", node=node)
return

def _check_consider_using_dict_items(self, node: astroid.For) -> None:
"""Add message when accessing dict values by index lookup."""
# Verify that we have a .keys() call and
# that the object which is iterated is used as a subscript in the
# body of the for.

iterating_object_name = utils.get_iterating_dictionary_name(node)
if iterating_object_name is None:
return

# Verify that the body of the for loop uses a subscript
# with the object that was iterated. This uses some heuristics
# in order to make sure that the same object is used in the
# for body.
for child in node.body:
for subscript in child.nodes_of_class(astroid.Subscript):
subscript = cast(astroid.Subscript, subscript)

if not isinstance(subscript.value, (astroid.Name, astroid.Attribute)):
continue

value = subscript.slice
if isinstance(value, astroid.Index):
value = value.value
if (
not isinstance(value, astroid.Name)
or value.name != node.target.name
or iterating_object_name != subscript.value.as_string()
):
continue
last_definition_lineno = value.lookup(value.name)[1][-1].lineno
if last_definition_lineno > node.lineno:
# Ignore this subscript if it has been redefined after
# the for loop. This checks for the line number using .lookup()
# to get the line number where the iterating object was last
# defined and compare that to the for loop's line number
continue
if (
isinstance(subscript.parent, astroid.Assign)
and subscript in subscript.parent.targets
or isinstance(subscript.parent, astroid.AugAssign)
and subscript == subscript.parent.target
):
# Ignore this subscript if it is the target of an assignment
continue

self.add_message("consider-using-dict-items", node=node)
return

@utils.check_messages("consider-using-dict-items")
def visit_comprehension(self, node: astroid.Comprehension) -> None:
iterating_object_name = utils.get_iterating_dictionary_name(node)
if iterating_object_name is None:
return

children = list(node.parent.get_children())
if node.ifs:
children.extend(node.ifs)
for child in children:
for subscript in child.nodes_of_class(astroid.Subscript):
subscript = cast(astroid.Subscript, subscript)

if not isinstance(subscript.value, (astroid.Name, astroid.Attribute)):
continue

value = subscript.slice
if isinstance(value, astroid.Index):
value = value.value
if (
not isinstance(value, astroid.Name)
or value.name != node.target.name
or iterating_object_name != subscript.value.as_string()
):
continue

self.add_message("consider-using-dict-items", node=node)
return
Loading

0 comments on commit 5fed063

Please sign in to comment.