From a9d9dc38bc66525b883a4e34f110dcfb54cecd53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 22:41:00 +0200 Subject: [PATCH 01/17] Fix suggestion for min-max with expressions (#9131) (#9141) Closes #8524 (cherry picked from commit ce8d1a9fcea3ad313843da626e91ea9e3a4667a3) Co-authored-by: theirix --- doc/whatsnew/fragments/8524.bugfix | 3 ++ pylint/checkers/nested_min_max.py | 41 +++++++++++++++++++--- tests/functional/n/nested_min_max.py | 12 +++++++ tests/functional/n/nested_min_max.txt | 4 +++ tests/functional/n/nested_min_max_py39.py | 6 ++++ tests/functional/n/nested_min_max_py39.rc | 2 ++ tests/functional/n/nested_min_max_py39.txt | 1 + 7 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 doc/whatsnew/fragments/8524.bugfix create mode 100644 tests/functional/n/nested_min_max_py39.py create mode 100644 tests/functional/n/nested_min_max_py39.rc create mode 100644 tests/functional/n/nested_min_max_py39.txt diff --git a/doc/whatsnew/fragments/8524.bugfix b/doc/whatsnew/fragments/8524.bugfix new file mode 100644 index 0000000000..76bae6e88c --- /dev/null +++ b/doc/whatsnew/fragments/8524.bugfix @@ -0,0 +1,3 @@ +Fixes suggestion for ``nested-min-max`` for expressions with additive operators, list and dict comprehensions. + +Closes #8524 diff --git a/pylint/checkers/nested_min_max.py b/pylint/checkers/nested_min_max.py index 219382ff52..a935d62f53 100644 --- a/pylint/checkers/nested_min_max.py +++ b/pylint/checkers/nested_min_max.py @@ -14,6 +14,7 @@ from pylint.checkers import BaseChecker from pylint.checkers.utils import only_required_for_messages, safe_infer +from pylint.constants import PY39_PLUS from pylint.interfaces import INFERENCE if TYPE_CHECKING: @@ -93,13 +94,10 @@ def visit_call(self, node: nodes.Call) -> None: for idx, arg in enumerate(fixed_node.args): if not isinstance(arg, nodes.Const): - inferred = safe_infer(arg) - if isinstance( - inferred, (nodes.List, nodes.Tuple, nodes.Set, *DICT_TYPES) - ): + if self._is_splattable_expression(arg): splat_node = nodes.Starred( ctx=Context.Load, - lineno=inferred.lineno, + lineno=arg.lineno, col_offset=0, parent=nodes.NodeNG( lineno=None, @@ -125,6 +123,39 @@ def visit_call(self, node: nodes.Call) -> None: confidence=INFERENCE, ) + def _is_splattable_expression(self, arg: nodes.NodeNG) -> bool: + """Returns true if expression under min/max could be converted to splat + expression. + """ + # Support sequence addition (operator __add__) + if isinstance(arg, nodes.BinOp) and arg.op == "+": + return self._is_splattable_expression( + arg.left + ) and self._is_splattable_expression(arg.right) + # Support dict merge (operator __or__ in Python 3.9) + if isinstance(arg, nodes.BinOp) and arg.op == "|" and PY39_PLUS: + return self._is_splattable_expression( + arg.left + ) and self._is_splattable_expression(arg.right) + + inferred = safe_infer(arg) + if inferred and inferred.pytype() in {"builtins.list", "builtins.tuple"}: + return True + if isinstance( + inferred or arg, + ( + nodes.List, + nodes.Tuple, + nodes.Set, + nodes.ListComp, + nodes.DictComp, + *DICT_TYPES, + ), + ): + return True + + return False + def register(linter: PyLinter) -> None: linter.register_checker(NestedMinMaxChecker(linter)) diff --git a/tests/functional/n/nested_min_max.py b/tests/functional/n/nested_min_max.py index 151e035dd1..7bb11264e9 100644 --- a/tests/functional/n/nested_min_max.py +++ b/tests/functional/n/nested_min_max.py @@ -42,3 +42,15 @@ lst2 = [3, 7, 10] max(3, max(nums), max(lst2)) # [nested-min-max] + +max(3, max([5] + [6, 7])) # [nested-min-max] +max(3, *[5] + [6, 7]) + +max(3, max([5] + [i for i in range(4) if i])) # [nested-min-max] +max(3, *[5] + [i for i in range(4) if i]) + +max(3, max([5] + list(range(4)))) # [nested-min-max] +max(3, *[5] + list(range(4))) + +max(3, max(list(range(4)))) # [nested-min-max] +max(3, *list(range(4))) diff --git a/tests/functional/n/nested_min_max.txt b/tests/functional/n/nested_min_max.txt index c03f1b500c..87b31daf65 100644 --- a/tests/functional/n/nested_min_max.txt +++ b/tests/functional/n/nested_min_max.txt @@ -12,3 +12,7 @@ nested-min-max:33:0:33:17::Do not use nested call of 'max'; it's possible to do nested-min-max:37:0:37:17::Do not use nested call of 'max'; it's possible to do 'max(3, *nums)' instead:INFERENCE nested-min-max:40:0:40:26::Do not use nested call of 'max'; it's possible to do 'max(3, *nums.values())' instead:INFERENCE nested-min-max:44:0:44:28::Do not use nested call of 'max'; it's possible to do 'max(3, *nums, *lst2)' instead:INFERENCE +nested-min-max:46:0:46:25::Do not use nested call of 'max'; it's possible to do 'max(3, *[5] + [6, 7])' instead:INFERENCE +nested-min-max:49:0:49:45::Do not use nested call of 'max'; it's possible to do 'max(3, *[5] + [i for i in range(4) if i])' instead:INFERENCE +nested-min-max:52:0:52:33::Do not use nested call of 'max'; it's possible to do 'max(3, *[5] + list(range(4)))' instead:INFERENCE +nested-min-max:55:0:55:27::Do not use nested call of 'max'; it's possible to do 'max(3, *list(range(4)))' instead:INFERENCE diff --git a/tests/functional/n/nested_min_max_py39.py b/tests/functional/n/nested_min_max_py39.py new file mode 100644 index 0000000000..e60146ca1b --- /dev/null +++ b/tests/functional/n/nested_min_max_py39.py @@ -0,0 +1,6 @@ +"""Test detection of redundant nested calls to min/max functions""" + +# pylint: disable=redefined-builtin,unnecessary-lambda-assignment + +max(3, max({1: 2} | {i: i for i in range(4) if i})) # [nested-min-max] +max(3, *{1: 2} | {i: i for i in range(4) if i}) diff --git a/tests/functional/n/nested_min_max_py39.rc b/tests/functional/n/nested_min_max_py39.rc new file mode 100644 index 0000000000..16b75eea75 --- /dev/null +++ b/tests/functional/n/nested_min_max_py39.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/n/nested_min_max_py39.txt b/tests/functional/n/nested_min_max_py39.txt new file mode 100644 index 0000000000..49541ccc2c --- /dev/null +++ b/tests/functional/n/nested_min_max_py39.txt @@ -0,0 +1 @@ +nested-min-max:5:0:5:51::"Do not use nested call of 'max'; it's possible to do 'max(3, *{1: 2} | {i: i for i in range(4) if i})' instead":INFERENCE From a77f0c14197542dc74ff1f2b521e8508bebd5f88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:29:00 +0000 Subject: [PATCH 02/17] Respect py-version for ``inconsistent-quotes`` inside f-strings (#9152) (#9155) * [inconsistent-quotes] Emit for f-strings for 3.12 only * Add docs for inconsistent-quotes with f-strings Despite ``sys.version`` check, pylint raises the warning with Python 3.11 (cherry picked from commit 1c0bc709488af3ad00068e70f5f6b05c9a3868d7) Co-authored-by: theirix --- doc/whatsnew/fragments/9113.bugfix | 3 +++ pylint/checkers/strings.py | 15 +++++++++++++++ .../i/inconsistent/inconsistent_quotes_fstring.py | 4 ++++ .../i/inconsistent/inconsistent_quotes_fstring.rc | 5 +++++ .../inconsistent_quotes_fstring_py312.py | 5 +++++ .../inconsistent_quotes_fstring_py312.rc | 8 ++++++++ .../inconsistent_quotes_fstring_py312.txt | 1 + .../inconsistent_quotes_fstring_py312_311.py | 5 +++++ .../inconsistent_quotes_fstring_py312_311.rc | 8 ++++++++ 9 files changed, 54 insertions(+) create mode 100644 doc/whatsnew/fragments/9113.bugfix create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring.py create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring.rc create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.rc create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.txt create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py create mode 100644 tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.rc diff --git a/doc/whatsnew/fragments/9113.bugfix b/doc/whatsnew/fragments/9113.bugfix new file mode 100644 index 0000000000..4b4b9b6dc0 --- /dev/null +++ b/doc/whatsnew/fragments/9113.bugfix @@ -0,0 +1,3 @@ +Emit ``inconsistent-quotes`` for f-strings with 3.12 interpreter only if targeting pre-3.12 versions. + +Closes #9113 diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 7002a551dd..3c5bced5f7 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -8,6 +8,7 @@ import collections import re +import sys import tokenize from collections import Counter from collections.abc import Iterable, Sequence @@ -834,8 +835,22 @@ def check_for_consistent_string_delimiters( """ string_delimiters: Counter[str] = collections.Counter() + inside_fstring = False # whether token is inside f-string (since 3.12) + target_py312 = self.linter.config.py_version >= (3, 12) + # First, figure out which quote character predominates in the module for tok_type, token, _, _, _ in tokens: + if sys.version_info[:2] >= (3, 12): + # pylint: disable=no-member,useless-suppression + if tok_type == tokenize.FSTRING_START: + inside_fstring = True + elif tok_type == tokenize.FSTRING_END: + inside_fstring = False + + if inside_fstring and not target_py312: + # skip analysis of f-string contents + continue + if tok_type == tokenize.STRING and _is_quote_delimiter_chosen_freely(token): string_delimiters[_get_quote_delimiter(token)] += 1 diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py b/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py new file mode 100644 index 0000000000..dd6f9fbe12 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-module-docstring + +dictionary = {'0': 0} +f_string = f'{dictionary["0"]}' diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring.rc b/tests/functional/i/inconsistent/inconsistent_quotes_fstring.rc new file mode 100644 index 0000000000..ffd31cd2b1 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring.rc @@ -0,0 +1,5 @@ +[STRING] +check-quote-consistency=yes + +[testoptions] +max_pyver=3.12 diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py new file mode 100644 index 0000000000..5e952af196 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py @@ -0,0 +1,5 @@ +# pylint: disable=missing-module-docstring + +dictionary = {'0': 0} +# quotes are inconsistent when targetting Python 3.12 (use single quotes) +f_string = f'{dictionary["0"]}' # [inconsistent-quotes] diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.rc b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.rc new file mode 100644 index 0000000000..3675741473 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.rc @@ -0,0 +1,8 @@ +[STRING] +check-quote-consistency=yes + +[main] +py-version=3.12 + +[testoptions] +min_pyver=3.12 diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.txt b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.txt new file mode 100644 index 0000000000..bf87ba6cb1 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.txt @@ -0,0 +1 @@ +inconsistent-quotes:5:0:None:None::"Quote delimiter "" is inconsistent with the rest of the file":UNDEFINED diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py new file mode 100644 index 0000000000..5b53a19bb5 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py @@ -0,0 +1,5 @@ +# pylint: disable=missing-module-docstring + +dictionary = {'0': 0} +# quotes are consistent when targetting 3.11 and earlier (cannot use single quotes here) +f_string = f'{dictionary["0"]}' diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.rc b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.rc new file mode 100644 index 0000000000..f60d7a71f7 --- /dev/null +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.rc @@ -0,0 +1,8 @@ +[STRING] +check-quote-consistency=yes + +[main] +py-version=3.11 + +[testoptions] +min_pyver=3.12 From 759e2cc225b322db9f54f17c1943318c115ba552 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:15:06 +0000 Subject: [PATCH 03/17] Fix `used-before-assignment` FP for generic type syntax (Py 3.12) (#9150) (#9154) * Fix `used-before-assignment` FP for generic type syntax (Py 3.12) * Bump astroid to 3.0.1 (cherry picked from commit cb29fbdf4e9afc9e9e8b979f52d2189fa7063fa5) Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/9110.false_positive | 3 +++ pylint/checkers/variables.py | 3 +++ pyproject.toml | 2 +- requirements_test_min.txt | 2 +- tests/functional/u/used/used_before_assignment_py312.py | 6 ++++++ tests/functional/u/used/used_before_assignment_py312.rc | 2 ++ 6 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 doc/whatsnew/fragments/9110.false_positive create mode 100644 tests/functional/u/used/used_before_assignment_py312.py create mode 100644 tests/functional/u/used/used_before_assignment_py312.rc diff --git a/doc/whatsnew/fragments/9110.false_positive b/doc/whatsnew/fragments/9110.false_positive new file mode 100644 index 0000000000..f71deed987 --- /dev/null +++ b/doc/whatsnew/fragments/9110.false_positive @@ -0,0 +1,3 @@ +Fix ``used-before-assignment`` false positive for generic type syntax (PEP 695, Python 3.12). + +Closes #9110 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index b0723cdf1d..6cf3924665 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -120,6 +120,7 @@ nodes.Expr, nodes.Return, nodes.Match, + nodes.TypeAlias, ) @@ -2329,6 +2330,8 @@ def _maybe_used_and_assigned_at_once(defstmt: nodes.Statement) -> bool: return any(case.guard for case in defstmt.cases) if isinstance(defstmt, nodes.IfExp): return True + if isinstance(defstmt, nodes.TypeAlias): + return True if isinstance(defstmt.value, nodes.BaseContainer): return any( VariablesChecker._maybe_used_and_assigned_at_once(elt) diff --git a/pyproject.toml b/pyproject.toml index ac52bd9c23..322e6d1b3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dependencies = [ # Also upgrade requirements_test_min.txt. # Pinned to dev of second minor update to allow editable installs and fix primer issues, # see https://github.com/pylint-dev/astroid/issues/1341 - "astroid>=3.0.0,<=3.1.0-dev0", + "astroid>=3.0.1,<=3.1.0-dev0", "isort>=4.2.5,<6", "mccabe>=0.6,<0.8", "tomli>=1.1.0;python_version<'3.11'", diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 4ebc9c2878..8f7fa3ec89 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ .[testutils,spelling] # astroid dependency is also defined in pyproject.toml -astroid==3.0.0 # Pinned to a specific version for tests +astroid==3.0.1 # Pinned to a specific version for tests typing-extensions~=4.8 py~=1.11.0 pytest~=7.4 diff --git a/tests/functional/u/used/used_before_assignment_py312.py b/tests/functional/u/used/used_before_assignment_py312.py new file mode 100644 index 0000000000..f47e005b85 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_py312.py @@ -0,0 +1,6 @@ +"""used-before-assignment re: python 3.12 generic typing syntax (PEP 695)""" + +from typing import Callable +type Point[T] = tuple[T, ...] +type Alias[*Ts] = tuple[*Ts] +type Alias[**P] = Callable[P] diff --git a/tests/functional/u/used/used_before_assignment_py312.rc b/tests/functional/u/used/used_before_assignment_py312.rc new file mode 100644 index 0000000000..9c966d4bda --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_py312.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.12 From 792da24660fcd050d36287268bfe19afda4736fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:45:57 +0000 Subject: [PATCH 04/17] Escape special symbols and newlines in messages. (#9164) (#9165) (cherry picked from commit df0800ec22c108cb1db244b5c8c4b25db79c58ba) Co-authored-by: theirix --- doc/whatsnew/fragments/7874.bugfix | 3 +++ pylint/extensions/magic_value.py | 4 ++-- .../ext/magic_value_comparison/magic_value_comparison.py | 2 ++ .../ext/magic_value_comparison/magic_value_comparison.txt | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 doc/whatsnew/fragments/7874.bugfix diff --git a/doc/whatsnew/fragments/7874.bugfix b/doc/whatsnew/fragments/7874.bugfix new file mode 100644 index 0000000000..112fbb534c --- /dev/null +++ b/doc/whatsnew/fragments/7874.bugfix @@ -0,0 +1,3 @@ +Escape special symbols and newlines in messages. + +Closes #7874 diff --git a/pylint/extensions/magic_value.py b/pylint/extensions/magic_value.py index c69711f85b..fd18476aea 100644 --- a/pylint/extensions/magic_value.py +++ b/pylint/extensions/magic_value.py @@ -84,9 +84,9 @@ def _check_constants_comparison(self, node: nodes.Compare) -> None: operand_value = None if const_operands[LEFT_OPERAND] and self._is_magic_value(left_operand): - operand_value = left_operand.value + operand_value = left_operand.as_string() elif const_operands[RIGHT_OPERAND] and self._is_magic_value(right_operand): - operand_value = right_operand.value + operand_value = right_operand.as_string() if operand_value is not None: self.add_message( "magic-value-comparison", diff --git a/tests/functional/ext/magic_value_comparison/magic_value_comparison.py b/tests/functional/ext/magic_value_comparison/magic_value_comparison.py index f6fb9369be..b7e7cd38a8 100644 --- a/tests/functional/ext/magic_value_comparison/magic_value_comparison.py +++ b/tests/functional/ext/magic_value_comparison/magic_value_comparison.py @@ -34,3 +34,5 @@ class Christmas(Enum): shouldnt_raise = var == '\n' shouldnt_raise = var == '\\b' + +should_escape = var == "foo\nbar" # [magic-value-comparison] diff --git a/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt b/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt index 63976ff68b..256cd4a0c7 100644 --- a/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt +++ b/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt @@ -5,3 +5,4 @@ comparison-of-constants:24:17:24:22::"Comparison between constants: '5 > 7' has singleton-comparison:29:17:29:28::Comparison 'var == True' should be 'var is True' if checking for the singleton value True, or 'bool(var)' if testing for truthiness:UNDEFINED singleton-comparison:30:17:30:29::Comparison 'var == False' should be 'var is False' if checking for the singleton value False, or 'not var' if testing for falsiness:UNDEFINED singleton-comparison:31:17:31:28::Comparison 'var == None' should be 'var is None':UNDEFINED +magic-value-comparison:38:16:38:33::Consider using a named constant or an enum instead of ''foo\nbar''.:HIGH From 31aca81de57c61d298e7328f5913aba09a1ea70e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Oct 2023 12:45:07 +0000 Subject: [PATCH 05/17] Fixes duplicate-code check with ignore-imports (#9147) (#9176) (cherry picked from commit 67f20bd380a9e788b7f58bb121668e9dae7cdbd1) Co-authored-by: theirix --- doc/whatsnew/fragments/8914.bugfix | 3 +++ pylint/checkers/similar.py | 21 ++++++------------- tests/checkers/unittest_similar.py | 7 ++++++- .../ignore_conditional_imports/__init__.py | 0 .../ignore_conditional_imports/file_one.py | 6 ++++++ .../ignore_conditional_imports/file_two.py | 6 ++++++ tests/test_similar.py | 15 +++++++++++++ 7 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 doc/whatsnew/fragments/8914.bugfix create mode 100644 tests/regrtest_data/duplicate_code/ignore_conditional_imports/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py create mode 100644 tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py diff --git a/doc/whatsnew/fragments/8914.bugfix b/doc/whatsnew/fragments/8914.bugfix new file mode 100644 index 0000000000..90a2be6b47 --- /dev/null +++ b/doc/whatsnew/fragments/8914.bugfix @@ -0,0 +1,3 @@ +Fixes ignoring conditional imports with ``ignore-imports=y``. + +Closes #8914 diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index fc50ed4153..b4091631ad 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -41,7 +41,7 @@ from collections.abc import Callable, Generator, Iterable, Sequence from getopt import getopt from io import BufferedIOBase, BufferedReader, BytesIO -from itertools import chain, groupby +from itertools import chain from typing import ( TYPE_CHECKING, Any, @@ -598,17 +598,10 @@ def stripped_lines( if ignore_imports or ignore_signatures: tree = astroid.parse("".join(lines)) if ignore_imports: - node_is_import_by_lineno = ( - (node.lineno, isinstance(node, (nodes.Import, nodes.ImportFrom))) - for node in tree.body - ) - line_begins_import = { - lineno: all(is_import for _, is_import in node_is_import_group) - for lineno, node_is_import_group in groupby( - node_is_import_by_lineno, key=lambda x: x[0] - ) - } - current_line_is_import = False + import_lines = {} + for node in tree.nodes_of_class((nodes.Import, nodes.ImportFrom)): + for lineno in range(node.lineno, (node.end_lineno or node.lineno) + 1): + import_lines[lineno] = True if ignore_signatures: def _get_functions( @@ -664,9 +657,7 @@ def _get_functions( docstring = None line = "" if ignore_imports: - current_line_is_import = line_begins_import.get( - lineno, current_line_is_import - ) + current_line_is_import = import_lines.get(lineno, False) if current_line_is_import: line = "" if ignore_comments: diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index f5bfc6fb20..8c00faee54 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -9,6 +9,7 @@ import pytest from pylint.checkers import similar +from pylint.constants import IS_PYPY, PY39_PLUS from pylint.lint import PyLinter from pylint.testutils import GenericTestReporter as Reporter @@ -130,6 +131,10 @@ def test_multiline_imports() -> None: ) +@pytest.mark.skipif( + IS_PYPY and not PY39_PLUS, + reason="Requires accurate 'end_lineno' value", +) def test_ignore_multiline_imports() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: @@ -295,7 +300,7 @@ def test_no_hide_code_with_imports() -> None: with redirect_stdout(output), pytest.raises(SystemExit) as ex: similar.Run(["--ignore-imports"] + 2 * [HIDE_CODE_WITH_IMPORTS]) assert ex.value.code == 0 - assert "TOTAL lines=32 duplicates=16 percent=50.00" in output.getvalue() + assert "TOTAL lines=32 duplicates=0 percent=0.00" in output.getvalue() def test_ignore_nothing() -> None: diff --git a/tests/regrtest_data/duplicate_code/ignore_conditional_imports/__init__.py b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py new file mode 100644 index 0000000000..9856cd5c77 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py @@ -0,0 +1,6 @@ +import typing + +x = 1 +if typing.TYPE_CHECKING: + import os + import sys diff --git a/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py new file mode 100644 index 0000000000..9856cd5c77 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py @@ -0,0 +1,6 @@ +import typing + +x = 1 +if typing.TYPE_CHECKING: + import os + import sys diff --git a/tests/test_similar.py b/tests/test_similar.py index ca58342c1d..8a09b45008 100644 --- a/tests/test_similar.py +++ b/tests/test_similar.py @@ -264,3 +264,18 @@ def test_useless_suppression() -> None: exit=False, ) assert not runner.linter.stats.by_msg + + def test_conditional_imports(self) -> None: + """Tests enabling ignore-imports with conditional imports works correctly.""" + path = join(DATA, "ignore_conditional_imports") + expected_output = "==ignore_conditional_imports.file_one:[2:4]" + self._test_output( + [ + path, + "-e=duplicate-code", + "-d=unused-import,C", + "--ignore-imports=y", + "--min-similarity-lines=1", + ], + expected_output=expected_output, + ) From efee9618894795cc8847727108522aa79431ee25 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 22 Oct 2023 08:50:27 -0400 Subject: [PATCH 06/17] Bump pylint to 3.0.2, update changelog --- doc/whatsnew/3/3.0/index.rst | 34 ++++++++++++++++++++++ doc/whatsnew/fragments/7874.bugfix | 3 -- doc/whatsnew/fragments/8524.bugfix | 3 -- doc/whatsnew/fragments/8914.bugfix | 3 -- doc/whatsnew/fragments/9110.false_positive | 3 -- doc/whatsnew/fragments/9113.bugfix | 3 -- pylint/__pkginfo__.py | 2 +- tbump.toml | 2 +- towncrier.toml | 2 +- 9 files changed, 37 insertions(+), 18 deletions(-) delete mode 100644 doc/whatsnew/fragments/7874.bugfix delete mode 100644 doc/whatsnew/fragments/8524.bugfix delete mode 100644 doc/whatsnew/fragments/8914.bugfix delete mode 100644 doc/whatsnew/fragments/9110.false_positive delete mode 100644 doc/whatsnew/fragments/9113.bugfix diff --git a/doc/whatsnew/3/3.0/index.rst b/doc/whatsnew/3/3.0/index.rst index 0690002fc5..3730d4857b 100644 --- a/doc/whatsnew/3/3.0/index.rst +++ b/doc/whatsnew/3/3.0/index.rst @@ -65,6 +65,40 @@ easier to parse and provides more info, here's a sample output. .. towncrier release notes start +What's new in Pylint 3.0.2? +--------------------------- +Release date: 2023-10-22 + + +False Positives Fixed +--------------------- + +- Fix ``used-before-assignment`` false positive for generic type syntax (PEP 695, Python 3.12). + + Closes #9110 (`#9110 `_) + + + +Other Bug Fixes +--------------- + +- Escape special symbols and newlines in messages. + + Closes #7874 (`#7874 `_) + +- Fixes suggestion for ``nested-min-max`` for expressions with additive operators, list and dict comprehensions. + + Closes #8524 (`#8524 `_) + +- Fixes ignoring conditional imports with ``ignore-imports=y``. + + Closes #8914 (`#8914 `_) + +- Emit ``inconsistent-quotes`` for f-strings with 3.12 interpreter only if targeting pre-3.12 versions. + + Closes #9113 (`#9113 `_) + + What's new in Pylint 3.0.1? --------------------------- Release date: 2023-10-05 diff --git a/doc/whatsnew/fragments/7874.bugfix b/doc/whatsnew/fragments/7874.bugfix deleted file mode 100644 index 112fbb534c..0000000000 --- a/doc/whatsnew/fragments/7874.bugfix +++ /dev/null @@ -1,3 +0,0 @@ -Escape special symbols and newlines in messages. - -Closes #7874 diff --git a/doc/whatsnew/fragments/8524.bugfix b/doc/whatsnew/fragments/8524.bugfix deleted file mode 100644 index 76bae6e88c..0000000000 --- a/doc/whatsnew/fragments/8524.bugfix +++ /dev/null @@ -1,3 +0,0 @@ -Fixes suggestion for ``nested-min-max`` for expressions with additive operators, list and dict comprehensions. - -Closes #8524 diff --git a/doc/whatsnew/fragments/8914.bugfix b/doc/whatsnew/fragments/8914.bugfix deleted file mode 100644 index 90a2be6b47..0000000000 --- a/doc/whatsnew/fragments/8914.bugfix +++ /dev/null @@ -1,3 +0,0 @@ -Fixes ignoring conditional imports with ``ignore-imports=y``. - -Closes #8914 diff --git a/doc/whatsnew/fragments/9110.false_positive b/doc/whatsnew/fragments/9110.false_positive deleted file mode 100644 index f71deed987..0000000000 --- a/doc/whatsnew/fragments/9110.false_positive +++ /dev/null @@ -1,3 +0,0 @@ -Fix ``used-before-assignment`` false positive for generic type syntax (PEP 695, Python 3.12). - -Closes #9110 diff --git a/doc/whatsnew/fragments/9113.bugfix b/doc/whatsnew/fragments/9113.bugfix deleted file mode 100644 index 4b4b9b6dc0..0000000000 --- a/doc/whatsnew/fragments/9113.bugfix +++ /dev/null @@ -1,3 +0,0 @@ -Emit ``inconsistent-quotes`` for f-strings with 3.12 interpreter only if targeting pre-3.12 versions. - -Closes #9113 diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 95d97c2da6..24c1c50518 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "3.0.1" +__version__ = "3.0.2" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff --git a/tbump.toml b/tbump.toml index 86a1431932..9f6f8c881d 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/pylint" [version] -current = "3.0.1" +current = "3.0.2" regex = ''' ^(?P0|[1-9]\d*) \. diff --git a/towncrier.toml b/towncrier.toml index d26a9e4b4e..dec869630d 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -1,5 +1,5 @@ [tool.towncrier] -version = "3.0.1" +version = "3.0.2" directory = "doc/whatsnew/fragments" filename = "doc/whatsnew/3/3.0/index.rst" template = "doc/whatsnew/fragments/_template.rst" From 53d4541eb52c3f249df8740e5508171a57be31ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:00:53 +0000 Subject: [PATCH 07/17] Fix `undefined-variable` etc for Python 3.12 generic type syntax (#9195) (#9199) (cherry picked from commit c648f164fbeab378389740469a9782d6d70f7ca1) Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/9193.false_positive | 4 ++++ pylint/checkers/variables.py | 12 +++++++++--- .../u/undefined/undefined_variable_py312.py | 7 +++++++ .../u/undefined/undefined_variable_py312.rc | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 doc/whatsnew/fragments/9193.false_positive create mode 100644 tests/functional/u/undefined/undefined_variable_py312.py create mode 100644 tests/functional/u/undefined/undefined_variable_py312.rc diff --git a/doc/whatsnew/fragments/9193.false_positive b/doc/whatsnew/fragments/9193.false_positive new file mode 100644 index 0000000000..39dc70b81d --- /dev/null +++ b/doc/whatsnew/fragments/9193.false_positive @@ -0,0 +1,4 @@ +Fix false positives for ``undefined-variable`` and ``unused-argument`` for +classes and functions using Python 3.12 generic type syntax. + +Closes #9193 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 6cf3924665..c06efc3d85 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1738,6 +1738,9 @@ def _should_node_be_skipped( elif consumer.scope_type == "function" and self._defined_in_function_definition( node, consumer.node ): + if any(node.name == param.name.name for param in consumer.node.type_params): + return False + # If the name node is used as a function default argument's value or as # a decorator, then start from the parent frame of the function instead # of the function frame - and thus open an inner class scope @@ -2262,10 +2265,13 @@ def _is_variable_violation( isinstance(defframe, nodes.FunctionDef) and frame is defframe and defframe.parent_of(node) - and stmt is not defstmt + and ( + defnode in defframe.type_params + # Single statement function, with the statement on the + # same line as the function definition + or stmt is not defstmt + ) ): - # Single statement function, with the statement on the - # same line as the function definition maybe_before_assign = False elif ( isinstance(defstmt, NODES_WITH_VALUE_ATTR) diff --git a/tests/functional/u/undefined/undefined_variable_py312.py b/tests/functional/u/undefined/undefined_variable_py312.py new file mode 100644 index 0000000000..0ca2475eb9 --- /dev/null +++ b/tests/functional/u/undefined/undefined_variable_py312.py @@ -0,0 +1,7 @@ +# pylint: disable=missing-function-docstring,missing-module-docstring,missing-class-docstring,too-few-public-methods + +def f[T](a: T) -> T: + print(a) + +class ChildClass[T, *Ts, **P]: + ... diff --git a/tests/functional/u/undefined/undefined_variable_py312.rc b/tests/functional/u/undefined/undefined_variable_py312.rc new file mode 100644 index 0000000000..9c966d4bda --- /dev/null +++ b/tests/functional/u/undefined/undefined_variable_py312.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.12 From 0273db7fb4b77c882e40cd766c61a56a0cb2b8e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:45:28 +0000 Subject: [PATCH 08/17] Fix false positive for ``unnecessary-lambda``. (#9149) (#9200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This simplifies the check for the call having kwargs but not the lambda. (cherry picked from commit 2289c7156638dc64c49d149e0bdd23f50fc471a4) Co-authored-by: Clément Schreiner --- doc/whatsnew/fragments/9148.false_positive | 3 +++ pylint/checkers/base/basic_checker.py | 16 +++------------- .../u/unnecessary/unnecessary_lambda.py | 4 ++++ 3 files changed, 10 insertions(+), 13 deletions(-) create mode 100644 doc/whatsnew/fragments/9148.false_positive diff --git a/doc/whatsnew/fragments/9148.false_positive b/doc/whatsnew/fragments/9148.false_positive new file mode 100644 index 0000000000..647deb103e --- /dev/null +++ b/doc/whatsnew/fragments/9148.false_positive @@ -0,0 +1,3 @@ +Fixed false positive for ``unnecessary-lambda`` when the call has keyword arguments but not the lambda. + +Closes #9148 diff --git a/pylint/checkers/base/basic_checker.py b/pylint/checkers/base/basic_checker.py index 3505ee7082..8aaa274d65 100644 --- a/pylint/checkers/base/basic_checker.py +++ b/pylint/checkers/base/basic_checker.py @@ -519,7 +519,6 @@ def _has_variadic_argument( ) @utils.only_required_for_messages("unnecessary-lambda") - # pylint: disable-next=too-many-return-statements def visit_lambda(self, node: nodes.Lambda) -> None: """Check whether the lambda is suspicious.""" # if the body of the lambda is a call expression with the same @@ -544,12 +543,13 @@ def visit_lambda(self, node: nodes.Lambda) -> None: # return something else (but we don't check that, yet). return - call_site = astroid.arguments.CallSite.from_call(call) ordinary_args = list(node.args.args) new_call_args = list(self._filter_vararg(node, call.args)) if node.args.kwarg: - if self._has_variadic_argument(call.kwargs, node.args.kwarg): + if self._has_variadic_argument(call.keywords, node.args.kwarg): return + elif call.keywords: + return if node.args.vararg: if self._has_variadic_argument(call.starargs, node.args.vararg): @@ -557,16 +557,6 @@ def visit_lambda(self, node: nodes.Lambda) -> None: elif call.starargs: return - if call.keywords: - # Look for additional keyword arguments that are not part - # of the lambda's signature - lambda_kwargs = {keyword.name for keyword in node.args.defaults} - if len(lambda_kwargs) != len(call_site.keyword_arguments): - # Different lengths, so probably not identical - return - if set(call_site.keyword_arguments).difference(lambda_kwargs): - return - # The "ordinary" arguments must be in a correspondence such that: # ordinary_args[i].name == call.args[i].name. if len(ordinary_args) != len(new_call_args): diff --git a/tests/functional/u/unnecessary/unnecessary_lambda.py b/tests/functional/u/unnecessary/unnecessary_lambda.py index 82571a4444..85c30fef28 100644 --- a/tests/functional/u/unnecessary/unnecessary_lambda.py +++ b/tests/functional/u/unnecessary/unnecessary_lambda.py @@ -52,6 +52,10 @@ _ = lambda: _ANYARGS(*[3], **{'three': 3}) _ = lambda: _ANYARGS(func=42) +# pylint: disable=missing-function-docstring +def f(d): + print(lambda x: str(x, **d)) + # Don't warn about this. _ = lambda: code().analysis() From dc11223e95783bdbf337baea7c2a141e1897d06c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:54:38 +0000 Subject: [PATCH 09/17] [todos] Fix the todos version and the warnings' text (#9202) (#9204) (cherry picked from commit 5546fe8430cca36e217fe25d770328ddfaf24454) Co-authored-by: Pierre Sassoulas --- pylint/checkers/similar.py | 2 +- pylint/checkers/variables.py | 2 +- pylint/config/callback_actions.py | 2 +- pylint/config/config_initialization.py | 5 +++-- pylint/testutils/functional/test_file.py | 2 +- pylint/utils/utils.py | 4 ++-- tests/test_check_parallel.py | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index b4091631ad..4b413924fe 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -839,7 +839,7 @@ def process_module(self, node: nodes.Module) -> None: stream must implement the readlines method """ if self.linter.current_name is None: - # TODO: 3.0 Fix current_name + # TODO: 4.0 Fix current_name warnings.warn( ( "In pylint 3.0 the current_name attribute of the linter object should be a string. " diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index c06efc3d85..925088f60d 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2571,7 +2571,7 @@ def _loopvar_name(self, node: astroid.Name) -> None: else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue) ): return - # TODO: 3.0: Consider using RefactoringChecker._is_function_def_never_returning + # TODO: 4.0: Consider using RefactoringChecker._is_function_def_never_returning if isinstance(else_stmt, nodes.Expr) and isinstance( else_stmt.value, nodes.Call ): diff --git a/pylint/config/callback_actions.py b/pylint/config/callback_actions.py index 0044e1fcd2..bf2decd3c6 100644 --- a/pylint/config/callback_actions.py +++ b/pylint/config/callback_actions.py @@ -243,7 +243,7 @@ def __call__( values: str | Sequence[Any] | None, option_string: str | None = "--generate-rcfile", ) -> None: - # TODO: 3.x: Deprecate this after the auto-upgrade functionality of + # TODO: 4.x: Deprecate this after the auto-upgrade functionality of # pylint-config is sufficient. self.run.linter._generate_config(skipsections=("Commands",)) sys.exit(0) diff --git a/pylint/config/config_initialization.py b/pylint/config/config_initialization.py index 514bdcd815..6fa7b6b895 100644 --- a/pylint/config/config_initialization.py +++ b/pylint/config/config_initialization.py @@ -110,13 +110,14 @@ def _config_initialization( "unrecognized-option", args=unrecognized_options_message, line=0 ) - # TODO 3.1: Change this to emit unknown-option-value + # TODO: Change this to be checked only when upgrading the configuration for exc_name in linter.config.overgeneral_exceptions: if "." not in exc_name: warnings.warn_explicit( f"'{exc_name}' is not a proper value for the 'overgeneral-exceptions' option. " f"Use fully qualified name (maybe 'builtins.{exc_name}' ?) instead. " - "This will cease to be checked at runtime in 3.1.0.", + "This will cease to be checked at runtime when the configuration " + "upgrader is released.", category=UserWarning, filename="pylint: Command line or configuration file", lineno=1, diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index 16593b5c4f..37ba3a5fc6 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -56,7 +56,7 @@ class FunctionalTestFile: def __init__(self, directory: str, filename: str) -> None: self._directory = directory self.base = filename.replace(".py", "") - # TODO: 3.0: Deprecate FunctionalTestFile.options and related code + # TODO:4.0: Deprecate FunctionalTestFile.options and related code # We should just parse these options like a normal configuration file. self.options: TestFileOptions = { "min_pyver": (2, 5), diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 8f16d2f1f4..91f1cdd8b1 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -315,7 +315,7 @@ def format_section( ) -> None: """Format an option's section using the INI format.""" warnings.warn( - "format_section has been deprecated. It will be removed in pylint 3.0.", + "format_section has been deprecated. It will be removed in pylint 4.0.", DeprecationWarning, stacklevel=2, ) @@ -330,7 +330,7 @@ def format_section( def _ini_format(stream: TextIO, options: list[tuple[str, OptionDict, Any]]) -> None: """Format options using the INI format.""" warnings.warn( - "_ini_format has been deprecated. It will be removed in pylint 3.0.", + "_ini_format has been deprecated. It will be removed in pylint 4.0.", DeprecationWarning, stacklevel=2, ) diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index f212df850b..0ae3b1ae10 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -269,7 +269,7 @@ def test_linter_with_unpickleable_plugins_is_pickleable(self) -> None: linter.load_plugin_modules(["pylint.extensions.overlapping_exceptions"]) try: dill.dumps(linter) - # TODO: 3.0: Fix this test by raising this assertion again + # TODO: 4.0: Fix this test by raising this assertion again # raise AssertionError( # "Plugins loaded were pickle-safe! This test needs altering" # ) From a60ddd1a44dc00721163f8390d034d2e1385791f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:02:19 +0000 Subject: [PATCH 10/17] Fix incorrect suggestion for unnecessary-comprehension (#9172) (#9242) (cherry picked from commit 6f83d5af96400cbe8cc01f3c8f098f81c9d99c70) Co-authored-by: Sayyed Faisal Ali <80758388+C0DE-SLAYER@users.noreply.github.com> --- doc/whatsnew/fragments/9172.false_positive | 12 +++++++++ .../refactoring/refactoring_checker.py | 16 ++++++------ .../unnecessary/unnecessary_comprehension.py | 2 ++ .../unnecessary/unnecessary_comprehension.txt | 26 ++++++++++--------- 4 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 doc/whatsnew/fragments/9172.false_positive diff --git a/doc/whatsnew/fragments/9172.false_positive b/doc/whatsnew/fragments/9172.false_positive new file mode 100644 index 0000000000..43884402cd --- /dev/null +++ b/doc/whatsnew/fragments/9172.false_positive @@ -0,0 +1,12 @@ +Fixed incorrect suggestion for shallow copy in unnecessary-comprehension + +Example of the suggestion: +#pylint: disable=missing-module-docstring +a = [1, 2, 3] +b = [x for x in a] +b[0] = 0 +print(a) # [1, 2, 3] + +After changing b = [x for x in a] to b = a based on the suggestion, the script now prints [0, 2, 3]. The correct suggestion should be use list(a) to preserve the original behavior. + +Closes #9172 diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index d29326693a..0d044c7838 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1779,15 +1779,15 @@ def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None: if isinstance(node.parent, nodes.DictComp) and isinstance( inferred, astroid.objects.DictItems ): - args = (f"{node.iter.func.expr.as_string()}",) - elif ( - isinstance(node.parent, nodes.ListComp) - and isinstance(inferred, nodes.List) - ) or ( - isinstance(node.parent, nodes.SetComp) - and isinstance(inferred, nodes.Set) + args = (f"dict({node.iter.func.expr.as_string()})",) + elif isinstance(node.parent, nodes.ListComp) and isinstance( + inferred, nodes.List ): - args = (f"{node.iter.as_string()}",) + args = (f"list({node.iter.as_string()})",) + elif isinstance(node.parent, nodes.SetComp) and isinstance( + inferred, nodes.Set + ): + args = (f"set({node.iter.as_string()})",) if args: self.add_message( "unnecessary-comprehension", node=node.parent, args=args diff --git a/tests/functional/u/unnecessary/unnecessary_comprehension.py b/tests/functional/u/unnecessary/unnecessary_comprehension.py index 2647898c90..ac26243071 100644 --- a/tests/functional/u/unnecessary/unnecessary_comprehension.py +++ b/tests/functional/u/unnecessary/unnecessary_comprehension.py @@ -4,7 +4,9 @@ # List comprehensions [x for x in iterable] # [unnecessary-comprehension] [y for x in iterable] # expression != target_list +[x for x in iterable] # [unnecessary-comprehension] use list(iterable) [x for x,y,z in iterable] # expression != target_list +[(x, y) for x, y in iterable] # [unnecessary-comprehension] [(x,y,z) for x,y,z in iterable] # [unnecessary-comprehension] [(x,y,z) for (x,y,z) in iterable] # [unnecessary-comprehension] [x for x, *y in iterable] # expression != target_list diff --git a/tests/functional/u/unnecessary/unnecessary_comprehension.txt b/tests/functional/u/unnecessary/unnecessary_comprehension.txt index d316fcdc84..f9b28543de 100644 --- a/tests/functional/u/unnecessary/unnecessary_comprehension.txt +++ b/tests/functional/u/unnecessary/unnecessary_comprehension.txt @@ -1,13 +1,15 @@ unnecessary-comprehension:5:0:5:21::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED -unnecessary-comprehension:8:0:8:31::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED -unnecessary-comprehension:9:0:9:33::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED -unnecessary-comprehension:17:7:17:42::Unnecessary use of a comprehension, use list(a_dict.items()) instead.:UNDEFINED -unnecessary-comprehension:20:0:20:21::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED -unnecessary-comprehension:23:0:23:31::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED -unnecessary-comprehension:24:7:24:42::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED -unnecessary-comprehension:32:0:32:27::Unnecessary use of a comprehension, use dict(iterable) instead.:UNDEFINED -unnecessary-comprehension:34:0:34:29::Unnecessary use of a comprehension, use dict(iterable) instead.:UNDEFINED -unnecessary-comprehension:46:0:46:26::Unnecessary use of a comprehension, use my_list instead.:UNDEFINED -unnecessary-comprehension:47:8:47:42::Unnecessary use of a comprehension, use my_dict instead.:UNDEFINED -consider-using-dict-items:48:0:None:None::Consider iterating with .items():UNDEFINED -unnecessary-comprehension:49:0:49:25::Unnecessary use of a comprehension, use my_set instead.:UNDEFINED +unnecessary-comprehension:7:0:7:21::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED +unnecessary-comprehension:9:0:9:29::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED +unnecessary-comprehension:10:0:10:31::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED +unnecessary-comprehension:11:0:11:33::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED +unnecessary-comprehension:19:7:19:42::Unnecessary use of a comprehension, use list(a_dict.items()) instead.:UNDEFINED +unnecessary-comprehension:22:0:22:21::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED +unnecessary-comprehension:25:0:25:31::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED +unnecessary-comprehension:26:7:26:42::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED +unnecessary-comprehension:34:0:34:27::Unnecessary use of a comprehension, use dict(iterable) instead.:UNDEFINED +unnecessary-comprehension:36:0:36:29::Unnecessary use of a comprehension, use dict(iterable) instead.:UNDEFINED +unnecessary-comprehension:48:0:48:26::Unnecessary use of a comprehension, use list(my_list) instead.:UNDEFINED +unnecessary-comprehension:49:8:49:42::Unnecessary use of a comprehension, use dict(my_dict) instead.:UNDEFINED +consider-using-dict-items:50:0:None:None::Consider iterating with .items():UNDEFINED +unnecessary-comprehension:51:0:51:25::Unnecessary use of a comprehension, use set(my_set) instead.:UNDEFINED From 7f01d837385c4505eda1777f7b9c2693c9b305b1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 26 Nov 2023 11:31:16 +0100 Subject: [PATCH 11/17] Fix doc generation in implicit-str-concat New sphinx now check that a block of code is valid. --- doc/data/messages/i/implicit-str-concat/details.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/data/messages/i/implicit-str-concat/details.rst b/doc/data/messages/i/implicit-str-concat/details.rst index 07b9fc172c..6b3f2c32f5 100644 --- a/doc/data/messages/i/implicit-str-concat/details.rst +++ b/doc/data/messages/i/implicit-str-concat/details.rst @@ -11,7 +11,7 @@ In order to detect this case, you must enable `check-str-concat-over-line-jumps` .. code-block:: toml [STRING_CONSTANT] - check-str-concat-over-line-jumps = yes + check-str-concat-over-line-jumps = true However, the drawback of this setting is that it will trigger false positive for string parameters passed on multiple lines in function calls: From 81f0f2ed02c2e79f6a44c324d47574cf271f9590 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:21:08 +0000 Subject: [PATCH 12/17] [Backport maintenance/3.0.x] [bugfix] Find files with ./ as input with a __init__.py file (#9286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [bugfix] Find files with ./ as input with a __init__.py file (#9211) Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> (cherry picked from commit abdb8742201dc31fbea7e3f632a6d7b10b4067b9) --- doc/whatsnew/fragments/9210.bugfix | 4 ++ pylint/lint/expand_modules.py | 3 +- tests/lint/unittest_expand_modules.py | 70 ++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 doc/whatsnew/fragments/9210.bugfix diff --git a/doc/whatsnew/fragments/9210.bugfix b/doc/whatsnew/fragments/9210.bugfix new file mode 100644 index 0000000000..c1d0835712 --- /dev/null +++ b/doc/whatsnew/fragments/9210.bugfix @@ -0,0 +1,4 @@ +Fix a bug where pylint was unable to walk recursively through a directory if the +directory has an `__init__.py` file. + +Closes #9210 diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 1e8fd032f8..d42c798c9d 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -144,8 +144,9 @@ def expand_modules( ) if has_init or is_namespace or is_directory: for subfilepath in modutils.get_module_files( - os.path.dirname(filepath), ignore_list, list_all=is_namespace + os.path.dirname(filepath) or ".", ignore_list, list_all=is_namespace ): + subfilepath = os.path.normpath(subfilepath) if filepath == subfilepath: continue if _is_in_ignore_list_re( diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 7120a17480..34133d759b 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -4,7 +4,11 @@ from __future__ import annotations +import copy +import os import re +from collections.abc import Iterator +from contextlib import contextmanager from pathlib import Path import pytest @@ -28,7 +32,8 @@ def test__is_in_ignore_list_re_match() -> None: 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_MODULES_BASE = "unittest_expand_modules.py" +EXPAND_MODULES = str(TEST_DIRECTORY / "lint" / EXPAND_MODULES_BASE) this_file = { "basename": "lint.unittest_expand_modules", "basepath": EXPAND_MODULES, @@ -37,6 +42,14 @@ def test__is_in_ignore_list_re_match() -> None: "path": EXPAND_MODULES, } +this_file_relative_to_parent = { + "basename": "lint.unittest_expand_modules", + "basepath": EXPAND_MODULES_BASE, + "isarg": True, + "name": "lint.unittest_expand_modules", + "path": EXPAND_MODULES_BASE, +} + this_file_from_init = { "basename": "lint", "basepath": INIT_PATH, @@ -117,6 +130,27 @@ def _list_expected_package_modules( ) +def _list_expected_package_modules_relative() -> tuple[dict[str, object], ...]: + """Generates reusable list of modules for our package with relative path input.""" + abs_result = copy.deepcopy(_list_expected_package_modules()) + for item in abs_result: + assert isinstance(item["basepath"], str) + assert isinstance(item["path"], str) + item["basepath"] = os.path.relpath(item["basepath"], str(Path(__file__).parent)) + item["path"] = os.path.relpath(item["path"], str(Path(__file__).parent)) + return abs_result + + +@contextmanager +def pushd(path: Path) -> Iterator[None]: + prev = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(prev) + + class TestExpandModules(CheckerTestCase): """Test the expand_modules function while allowing options to be set.""" @@ -159,6 +193,40 @@ def test_expand_modules( assert modules == expected assert not errors + @pytest.mark.parametrize( + "files_or_modules,expected", + [ + ( + [Path(__file__).name], + {this_file_relative_to_parent["path"]: this_file_relative_to_parent}, + ), + ( + ["./"], + { + module["path"]: module # pylint: disable=unsubscriptable-object + for module in _list_expected_package_modules_relative() + }, + ), + ], + ) + @set_config(ignore_paths="") + def test_expand_modules_relative_path( + self, files_or_modules: list[str], expected: dict[str, ModuleDescriptionDict] + ) -> None: + """Test expand_modules with the default value of ignore-paths and relative path as input.""" + ignore_list: list[str] = [] + ignore_list_re: list[re.Pattern[str]] = [] + with pushd(Path(__file__).parent): + modules, errors = expand_modules( + files_or_modules, + [], + ignore_list, + ignore_list_re, + self.linter.config.ignore_paths, + ) + assert modules == expected + assert not errors + @pytest.mark.parametrize( "files_or_modules,expected", [ From d0d5c91e26c80933bfc13b36a0428d3a0c593cc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:22:15 -0500 Subject: [PATCH 13/17] [pointless-string-statement] Ignore docstrings on py3.12 type aliases (#9269) (#9287) (cherry picked from commit 796eae3c46b142d479071fdf39f2e47f627da29e) Co-authored-by: Jacob Walls --- doc/data/messages/p/pointless-string-statement/related.rst | 1 + doc/whatsnew/fragments/9268.false_positive | 4 ++++ pylint/checkers/base/basic_checker.py | 4 +++- tests/functional/s/statement_without_effect_py312.py | 7 +++++++ tests/functional/s/statement_without_effect_py312.rc | 2 ++ 5 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 doc/data/messages/p/pointless-string-statement/related.rst create mode 100644 doc/whatsnew/fragments/9268.false_positive create mode 100644 tests/functional/s/statement_without_effect_py312.py create mode 100644 tests/functional/s/statement_without_effect_py312.rc diff --git a/doc/data/messages/p/pointless-string-statement/related.rst b/doc/data/messages/p/pointless-string-statement/related.rst new file mode 100644 index 0000000000..b03ae2cd9b --- /dev/null +++ b/doc/data/messages/p/pointless-string-statement/related.rst @@ -0,0 +1 @@ +- `Discussion thread re: docstrings on assignments `_ diff --git a/doc/whatsnew/fragments/9268.false_positive b/doc/whatsnew/fragments/9268.false_positive new file mode 100644 index 0000000000..f360ea204e --- /dev/null +++ b/doc/whatsnew/fragments/9268.false_positive @@ -0,0 +1,4 @@ +Fixed ``pointless-string-statement`` false positive for docstrings +on Python 3.12 type aliases. + +Closes #9268 diff --git a/pylint/checkers/base/basic_checker.py b/pylint/checkers/base/basic_checker.py index 8aaa274d65..32e0da016c 100644 --- a/pylint/checkers/base/basic_checker.py +++ b/pylint/checkers/base/basic_checker.py @@ -446,7 +446,9 @@ def visit_expr(self, node: nodes.Expr) -> None: if ( sibling is not None and sibling.scope() is scope - and isinstance(sibling, (nodes.Assign, nodes.AnnAssign)) + and isinstance( + sibling, (nodes.Assign, nodes.AnnAssign, nodes.TypeAlias) + ) ): return self.add_message("pointless-string-statement", node=node) diff --git a/tests/functional/s/statement_without_effect_py312.py b/tests/functional/s/statement_without_effect_py312.py new file mode 100644 index 0000000000..5ea5d17e7b --- /dev/null +++ b/tests/functional/s/statement_without_effect_py312.py @@ -0,0 +1,7 @@ +"""Move this into statement_without_effect.py when python 3.12 is minimum.""" + +type MyTuple = tuple[str, str] +""" +Multiline docstring +for Python3.12 type alias (PEP 695) +""" diff --git a/tests/functional/s/statement_without_effect_py312.rc b/tests/functional/s/statement_without_effect_py312.rc new file mode 100644 index 0000000000..9c966d4bda --- /dev/null +++ b/tests/functional/s/statement_without_effect_py312.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.12 From fea5483f2861df71ff5f60d3f6300c2fc93ef21a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 10 Dec 2023 21:28:27 +0000 Subject: [PATCH 14/17] [wrong-exception-operation] Fix FP for tuple concatenation of exception types (#9289) (#9291) (cherry picked from commit 8d4c6c1f300414abad0d4f5cd81e592e8743b6f0) Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/9288.false_positive | 4 ++++ pylint/checkers/exceptions.py | 13 +++++++++++- .../functional/w/wrong_exception_operation.py | 21 +++++++++++++++++++ .../w/wrong_exception_operation.txt | 1 + 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 doc/whatsnew/fragments/9288.false_positive diff --git a/doc/whatsnew/fragments/9288.false_positive b/doc/whatsnew/fragments/9288.false_positive new file mode 100644 index 0000000000..470c308c69 --- /dev/null +++ b/doc/whatsnew/fragments/9288.false_positive @@ -0,0 +1,4 @@ +Fix false positive for ``invalid-exception-operation`` when concatenating tuples +of exception types. + +Closes #9288 diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index ce3f9367f0..2a193a6ce8 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -533,8 +533,19 @@ def gather_exceptions_from_handler( @utils.only_required_for_messages("wrong-exception-operation") def visit_binop(self, node: nodes.BinOp) -> None: if isinstance(node.parent, nodes.ExceptHandler): + both_sides_tuple_or_uninferable = isinstance( + utils.safe_infer(node.left), (nodes.Tuple, util.UninferableBase) + ) and isinstance( + utils.safe_infer(node.right), (nodes.Tuple, util.UninferableBase) + ) + # Tuple concatenation allowed + if both_sides_tuple_or_uninferable: + if node.op == "+": + return + suggestion = f"Did you mean '({node.left.as_string()} + {node.right.as_string()})' instead?" # except (V | A) - suggestion = f"Did you mean '({node.left.as_string()}, {node.right.as_string()})' instead?" + else: + suggestion = f"Did you mean '({node.left.as_string()}, {node.right.as_string()})' instead?" self.add_message("wrong-exception-operation", node=node, args=(suggestion,)) @utils.only_required_for_messages("wrong-exception-operation") diff --git a/tests/functional/w/wrong_exception_operation.py b/tests/functional/w/wrong_exception_operation.py index 8078573c41..141251fed9 100644 --- a/tests/functional/w/wrong_exception_operation.py +++ b/tests/functional/w/wrong_exception_operation.py @@ -16,3 +16,24 @@ 1/0 except (ValueError < TypeError): # [wrong-exception-operation] pass + + +# Concatenation of exception type tuples +DIVISION_BY_ZERO = (ZeroDivisionError,) +VALUE_ERROR = (ValueError,) +UNINFERABLE = DIVISION_BY_ZERO | VALUE_ERROR + +try: + 1/0 +except (ValueError, ) + DIVISION_BY_ZERO: + pass + +try: + 1/0 +except (ValueError, ) | DIVISION_BY_ZERO: # [wrong-exception-operation] + pass + +try: + 1/0 +except (ValueError, ) + UNINFERABLE: + pass diff --git a/tests/functional/w/wrong_exception_operation.txt b/tests/functional/w/wrong_exception_operation.txt index dc3c213462..d7eae8134f 100644 --- a/tests/functional/w/wrong_exception_operation.txt +++ b/tests/functional/w/wrong_exception_operation.txt @@ -2,3 +2,4 @@ catching-non-exception:6:8:6:30::"Catching an exception which doesn't inherit fr wrong-exception-operation:6:8:6:30::Invalid exception operation. Did you mean '(ValueError, TypeError)' instead?:UNDEFINED wrong-exception-operation:11:8:11:30::Invalid exception operation. Did you mean '(ValueError, TypeError)' instead?:UNDEFINED wrong-exception-operation:17:8:17:30::Invalid exception operation. Did you mean '(ValueError, TypeError)' instead?:UNDEFINED +wrong-exception-operation:33:7:33:40::Invalid exception operation. Did you mean '((ValueError, ) + DIVISION_BY_ZERO)' instead?:UNDEFINED From 54687e75b6fb4147e58f1c3204eea6cd915a472b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 10 Dec 2023 23:22:46 +0100 Subject: [PATCH 15/17] Disallow isort 5.13.0 (#9290) (#9292) Refs #9270 (cherry picked from commit 47410adc1b9ffdddc15726803d63518a12a1e998) Co-authored-by: Jacob Walls --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 322e6d1b3d..121a465559 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ # Pinned to dev of second minor update to allow editable installs and fix primer issues, # see https://github.com/pylint-dev/astroid/issues/1341 "astroid>=3.0.1,<=3.1.0-dev0", - "isort>=4.2.5,<6", + "isort>=4.2.5,<6,!=5.13.0", "mccabe>=0.6,<0.8", "tomli>=1.1.0;python_version<'3.11'", "tomlkit>=0.10.1", From 1a5ffc1f447b77071ffe18a9c6836c09147ee2ed Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 10 Dec 2023 20:44:51 -0500 Subject: [PATCH 16/17] Bump pylint to 3.0.3, update changelog --- doc/whatsnew/3/3.0/index.rst | 52 ++++++++++++++++++++++ doc/whatsnew/fragments/9148.false_positive | 3 -- doc/whatsnew/fragments/9172.false_positive | 12 ----- doc/whatsnew/fragments/9193.false_positive | 4 -- doc/whatsnew/fragments/9210.bugfix | 4 -- doc/whatsnew/fragments/9268.false_positive | 4 -- doc/whatsnew/fragments/9288.false_positive | 4 -- pylint/__pkginfo__.py | 2 +- tbump.toml | 2 +- towncrier.toml | 2 +- 10 files changed, 55 insertions(+), 34 deletions(-) delete mode 100644 doc/whatsnew/fragments/9148.false_positive delete mode 100644 doc/whatsnew/fragments/9172.false_positive delete mode 100644 doc/whatsnew/fragments/9193.false_positive delete mode 100644 doc/whatsnew/fragments/9210.bugfix delete mode 100644 doc/whatsnew/fragments/9268.false_positive delete mode 100644 doc/whatsnew/fragments/9288.false_positive diff --git a/doc/whatsnew/3/3.0/index.rst b/doc/whatsnew/3/3.0/index.rst index 3730d4857b..bb7c091107 100644 --- a/doc/whatsnew/3/3.0/index.rst +++ b/doc/whatsnew/3/3.0/index.rst @@ -65,6 +65,58 @@ easier to parse and provides more info, here's a sample output. .. towncrier release notes start +What's new in Pylint 3.0.3? +--------------------------- +Release date: 2023-12-11 + + +False Positives Fixed +--------------------- + +- Fixed false positive for ``unnecessary-lambda`` when the call has keyword arguments but not the lambda. + + Closes #9148 (`#9148 `_) + +- Fixed incorrect suggestion for shallow copy in unnecessary-comprehension + + Example of the suggestion: + #pylint: disable=missing-module-docstring + a = [1, 2, 3] + b = [x for x in a] + b[0] = 0 + print(a) # [1, 2, 3] + + After changing b = [x for x in a] to b = a based on the suggestion, the script now prints [0, 2, 3]. The correct suggestion should be use list(a) to preserve the original behavior. + + Closes #9172 (`#9172 `_) + +- Fix false positives for ``undefined-variable`` and ``unused-argument`` for + classes and functions using Python 3.12 generic type syntax. + + Closes #9193 (`#9193 `_) + +- Fixed ``pointless-string-statement`` false positive for docstrings + on Python 3.12 type aliases. + + Closes #9268 (`#9268 `_) + +- Fix false positive for ``invalid-exception-operation`` when concatenating tuples + of exception types. + + Closes #9288 (`#9288 `_) + + + +Other Bug Fixes +--------------- + +- Fix a bug where pylint was unable to walk recursively through a directory if the + directory has an `__init__.py` file. + + Closes #9210 (`#9210 `_) + + + What's new in Pylint 3.0.2? --------------------------- Release date: 2023-10-22 diff --git a/doc/whatsnew/fragments/9148.false_positive b/doc/whatsnew/fragments/9148.false_positive deleted file mode 100644 index 647deb103e..0000000000 --- a/doc/whatsnew/fragments/9148.false_positive +++ /dev/null @@ -1,3 +0,0 @@ -Fixed false positive for ``unnecessary-lambda`` when the call has keyword arguments but not the lambda. - -Closes #9148 diff --git a/doc/whatsnew/fragments/9172.false_positive b/doc/whatsnew/fragments/9172.false_positive deleted file mode 100644 index 43884402cd..0000000000 --- a/doc/whatsnew/fragments/9172.false_positive +++ /dev/null @@ -1,12 +0,0 @@ -Fixed incorrect suggestion for shallow copy in unnecessary-comprehension - -Example of the suggestion: -#pylint: disable=missing-module-docstring -a = [1, 2, 3] -b = [x for x in a] -b[0] = 0 -print(a) # [1, 2, 3] - -After changing b = [x for x in a] to b = a based on the suggestion, the script now prints [0, 2, 3]. The correct suggestion should be use list(a) to preserve the original behavior. - -Closes #9172 diff --git a/doc/whatsnew/fragments/9193.false_positive b/doc/whatsnew/fragments/9193.false_positive deleted file mode 100644 index 39dc70b81d..0000000000 --- a/doc/whatsnew/fragments/9193.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fix false positives for ``undefined-variable`` and ``unused-argument`` for -classes and functions using Python 3.12 generic type syntax. - -Closes #9193 diff --git a/doc/whatsnew/fragments/9210.bugfix b/doc/whatsnew/fragments/9210.bugfix deleted file mode 100644 index c1d0835712..0000000000 --- a/doc/whatsnew/fragments/9210.bugfix +++ /dev/null @@ -1,4 +0,0 @@ -Fix a bug where pylint was unable to walk recursively through a directory if the -directory has an `__init__.py` file. - -Closes #9210 diff --git a/doc/whatsnew/fragments/9268.false_positive b/doc/whatsnew/fragments/9268.false_positive deleted file mode 100644 index f360ea204e..0000000000 --- a/doc/whatsnew/fragments/9268.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fixed ``pointless-string-statement`` false positive for docstrings -on Python 3.12 type aliases. - -Closes #9268 diff --git a/doc/whatsnew/fragments/9288.false_positive b/doc/whatsnew/fragments/9288.false_positive deleted file mode 100644 index 470c308c69..0000000000 --- a/doc/whatsnew/fragments/9288.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fix false positive for ``invalid-exception-operation`` when concatenating tuples -of exception types. - -Closes #9288 diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 24c1c50518..521ffd04cd 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "3.0.2" +__version__ = "3.0.3" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff --git a/tbump.toml b/tbump.toml index 9f6f8c881d..b7db1431f4 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/pylint" [version] -current = "3.0.2" +current = "3.0.3" regex = ''' ^(?P0|[1-9]\d*) \. diff --git a/towncrier.toml b/towncrier.toml index dec869630d..46ceaf08c2 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -1,5 +1,5 @@ [tool.towncrier] -version = "3.0.2" +version = "3.0.3" directory = "doc/whatsnew/fragments" filename = "doc/whatsnew/3/3.0/index.rst" template = "doc/whatsnew/fragments/_template.rst" From 4860b8224a2a117029769ca0b1165fc7f291ea15 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 11 Dec 2023 10:03:55 -0500 Subject: [PATCH 17/17] Removed mooted changelog fragments from 3.0.3 release --- doc/whatsnew/fragments/9148.false_positive | 3 --- doc/whatsnew/fragments/9172.false_positive | 12 ------------ doc/whatsnew/fragments/9193.false_positive | 4 ---- doc/whatsnew/fragments/9210.bugfix | 4 ---- doc/whatsnew/fragments/9268.false_positive | 4 ---- doc/whatsnew/fragments/9288.false_positive | 4 ---- 6 files changed, 31 deletions(-) delete mode 100644 doc/whatsnew/fragments/9148.false_positive delete mode 100644 doc/whatsnew/fragments/9172.false_positive delete mode 100644 doc/whatsnew/fragments/9193.false_positive delete mode 100644 doc/whatsnew/fragments/9210.bugfix delete mode 100644 doc/whatsnew/fragments/9268.false_positive delete mode 100644 doc/whatsnew/fragments/9288.false_positive diff --git a/doc/whatsnew/fragments/9148.false_positive b/doc/whatsnew/fragments/9148.false_positive deleted file mode 100644 index 647deb103e..0000000000 --- a/doc/whatsnew/fragments/9148.false_positive +++ /dev/null @@ -1,3 +0,0 @@ -Fixed false positive for ``unnecessary-lambda`` when the call has keyword arguments but not the lambda. - -Closes #9148 diff --git a/doc/whatsnew/fragments/9172.false_positive b/doc/whatsnew/fragments/9172.false_positive deleted file mode 100644 index 43884402cd..0000000000 --- a/doc/whatsnew/fragments/9172.false_positive +++ /dev/null @@ -1,12 +0,0 @@ -Fixed incorrect suggestion for shallow copy in unnecessary-comprehension - -Example of the suggestion: -#pylint: disable=missing-module-docstring -a = [1, 2, 3] -b = [x for x in a] -b[0] = 0 -print(a) # [1, 2, 3] - -After changing b = [x for x in a] to b = a based on the suggestion, the script now prints [0, 2, 3]. The correct suggestion should be use list(a) to preserve the original behavior. - -Closes #9172 diff --git a/doc/whatsnew/fragments/9193.false_positive b/doc/whatsnew/fragments/9193.false_positive deleted file mode 100644 index 39dc70b81d..0000000000 --- a/doc/whatsnew/fragments/9193.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fix false positives for ``undefined-variable`` and ``unused-argument`` for -classes and functions using Python 3.12 generic type syntax. - -Closes #9193 diff --git a/doc/whatsnew/fragments/9210.bugfix b/doc/whatsnew/fragments/9210.bugfix deleted file mode 100644 index c1d0835712..0000000000 --- a/doc/whatsnew/fragments/9210.bugfix +++ /dev/null @@ -1,4 +0,0 @@ -Fix a bug where pylint was unable to walk recursively through a directory if the -directory has an `__init__.py` file. - -Closes #9210 diff --git a/doc/whatsnew/fragments/9268.false_positive b/doc/whatsnew/fragments/9268.false_positive deleted file mode 100644 index f360ea204e..0000000000 --- a/doc/whatsnew/fragments/9268.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fixed ``pointless-string-statement`` false positive for docstrings -on Python 3.12 type aliases. - -Closes #9268 diff --git a/doc/whatsnew/fragments/9288.false_positive b/doc/whatsnew/fragments/9288.false_positive deleted file mode 100644 index 470c308c69..0000000000 --- a/doc/whatsnew/fragments/9288.false_positive +++ /dev/null @@ -1,4 +0,0 @@ -Fix false positive for ``invalid-exception-operation`` when concatenating tuples -of exception types. - -Closes #9288