From e3b5edd37828b4ed6322050e9d562a17b0276102 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 29 Nov 2021 20:22:56 +0100 Subject: [PATCH 001/357] Upgrade the version to 2.13.0-dev0 following 2.12.2 release --- pylint/__pkginfo__.py | 2 +- tbump.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 1e2846ef01..b2546d3a1f 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -2,7 +2,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE from typing import Tuple -__version__ = "2.12.2" +__version__ = "2.13.0-dev0" def get_numversion_from_version(v: str) -> Tuple: diff --git a/tbump.toml b/tbump.toml index d53b722f6a..b7855123d0 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/PyCQA/pylint" [version] -current = "2.12.2" +current = "2.13.0-dev0" regex = ''' ^(?P0|[1-9]\d*) \. From ed55e8f66407f1b84a386446372f12feee3230c7 Mon Sep 17 00:00:00 2001 From: Antonio Quarta Date: Fri, 3 Dec 2021 18:33:04 +0100 Subject: [PATCH 002/357] Add mermaidjs as format output for pyreverse (#5272) add mermaid js printer, fix accepted output format without graphviz Make an adapter for package graph, use class until mermaid don't add a package diagram type. Add mmd and html formats to additional commands --- .pre-commit-config.yaml | 3 +- ChangeLog | 2 + doc/additional_commands/index.rst | 2 +- doc/whatsnew/2.13.rst | 2 + pylint/pyreverse/main.py | 9 +- pylint/pyreverse/mermaidjs_printer.py | 110 +++++++++++++++++++++ pylint/pyreverse/printer_factory.py | 3 + tests/pyreverse/conftest.py | 16 +++ tests/pyreverse/data/classes_No_Name.html | 47 +++++++++ tests/pyreverse/data/classes_No_Name.mmd | 38 +++++++ tests/pyreverse/data/packages_No_Name.html | 19 ++++ tests/pyreverse/data/packages_No_Name.mmd | 10 ++ tests/pyreverse/test_writer.py | 30 ++++++ 13 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 pylint/pyreverse/mermaidjs_printer.py create mode 100644 tests/pyreverse/data/classes_No_Name.html create mode 100644 tests/pyreverse/data/classes_No_Name.mmd create mode 100644 tests/pyreverse/data/packages_No_Name.html create mode 100644 tests/pyreverse/data/packages_No_Name.mmd diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7e692843b9..429c04b33d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: rev: v4.0.1 hooks: - id: trailing-whitespace - exclude: "tests/functional/t/trailing_whitespaces.py" + exclude: "tests/functional/t/trailing_whitespaces.py|tests/pyreverse/data/.*.html" - id: end-of-file-fixer exclude: "tests/functional/m/missing/missing_final_newline.py|tests/functional/t/trailing_newlines.py" - repo: https://github.com/myint/autoflake @@ -94,3 +94,4 @@ repos: hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] + exclude: tests(/.*)*/data diff --git a/ChangeLog b/ChangeLog index 4dd010abbf..c55294e16a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ What's New in Pylint 2.13.0? ============================ Release date: TBA +* Pyreverse - add output in mermaidjs format + .. Put new features here and also in 'doc/whatsnew/2.13.rst' diff --git a/doc/additional_commands/index.rst b/doc/additional_commands/index.rst index e4c92cab30..b8ebb15cb6 100644 --- a/doc/additional_commands/index.rst +++ b/doc/additional_commands/index.rst @@ -9,7 +9,7 @@ Pyreverse --------- ``pyreverse`` analyzes your source code and generates package and class diagrams. -It supports output to ``.dot``/``.gv``, ``.vcg`` and ``.puml``/``.plantuml`` (PlantUML) file formats. +It supports output to ``.dot``/``.gv``, ``.vcg``, ``.puml``/``.plantuml`` (PlantUML) and ``.mmd``/``.html`` (MermaidJS) file formats. If Graphviz (or the ``dot`` command) is installed, all `output formats supported by Graphviz `_ can be used as well. In this case, ``pyreverse`` first generates a temporary ``.gv`` file, which is then fed to Graphviz to generate the final image. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 378b4103bf..60099b67ee 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -17,6 +17,8 @@ Removed checkers Extensions ========== +* Pyreverse - add output in mermaid-js format and html which is an mermaid js diagram with html boilerplate + Other Changes ============= diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index fc302149a2..c48b9f3c30 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -206,7 +206,14 @@ def __init__(self, args: Iterable[str]): super().__init__(usage=__doc__) insert_default_options() args = self.load_command_line_configuration(args) - if self.config.output_format not in ("dot", "vcg", "puml", "plantuml"): + if self.config.output_format not in ( + "dot", + "vcg", + "puml", + "plantuml", + "mmd", + "html", + ): check_graphviz_availability() sys.exit(self.run(args)) diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py new file mode 100644 index 0000000000..c8214ab8e0 --- /dev/null +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -0,0 +1,110 @@ +# Copyright (c) 2021 Antonio Quarta + +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +""" +Class to generate files in mermaidjs format +""" +from typing import Dict, Optional + +from pylint.pyreverse.printer import EdgeType, NodeProperties, NodeType, Printer +from pylint.pyreverse.utils import get_annotation_label + + +class MermaidJSPrinter(Printer): + """Printer for MermaidJS diagrams""" + + DEFAULT_COLOR = "black" + + NODES: Dict[NodeType, str] = { + NodeType.CLASS: "class", + NodeType.INTERFACE: "class", + NodeType.PACKAGE: "class", + } + ARROWS: Dict[EdgeType, str] = { + EdgeType.INHERITS: "--|>", + EdgeType.IMPLEMENTS: "..|>", + EdgeType.ASSOCIATION: "--*", + EdgeType.USES: "-->", + } + + def _open_graph(self) -> None: + """Emit the header lines""" + self.emit("classDiagram") + self._inc_indent() + + def emit_node( + self, + name: str, + type_: NodeType, + properties: Optional[NodeProperties] = None, + ) -> None: + """Create a new node. Nodes can be classes, packages, participants etc.""" + if properties is None: + properties = NodeProperties(label=name) + stereotype = "~~Interface~~" if type_ is NodeType.INTERFACE else "" + nodetype = self.NODES[type_] + body = [] + if properties.attrs: + body.extend(properties.attrs) + if properties.methods: + for func in properties.methods: + args = self._get_method_arguments(func) + line = f"{func.name}({', '.join(args)})" + if func.returns: + line += " -> " + get_annotation_label(func.returns) + body.append(line) + name = name.split(".")[-1] + self.emit(f"{nodetype} {name}{stereotype} {{") + self._inc_indent() + for line in body: + self.emit(line) + self._dec_indent() + self.emit("}") + + def emit_edge( + self, + from_node: str, + to_node: str, + type_: EdgeType, + label: Optional[str] = None, + ) -> None: + """Create an edge from one node to another to display relationships.""" + from_node = from_node.split(".")[-1] + to_node = to_node.split(".")[-1] + edge = f"{from_node} {self.ARROWS[type_]} {to_node}" + if label: + edge += f" : {label}" + self.emit(edge) + + def _close_graph(self) -> None: + """Emit the lines needed to properly close the graph.""" + self._dec_indent() + + +class HTMLMermaidJSPrinter(MermaidJSPrinter): + """Printer for MermaidJS diagrams wrapped in an html boilerplate""" + + HTML_OPEN_BOILERPLATE = """ + + +
+ """ + HTML_CLOSE_BOILERPLATE = """ +
+ + +""" + GRAPH_INDENT_LEVEL = 4 + + def _open_graph(self) -> None: + self.emit(self.HTML_OPEN_BOILERPLATE) + for _ in range(self.GRAPH_INDENT_LEVEL): + self._inc_indent() + super()._open_graph() + + def _close_graph(self) -> None: + for _ in range(self.GRAPH_INDENT_LEVEL): + self._dec_indent() + self.emit(self.HTML_CLOSE_BOILERPLATE) diff --git a/pylint/pyreverse/printer_factory.py b/pylint/pyreverse/printer_factory.py index 73200c35d5..bdcaf0869d 100644 --- a/pylint/pyreverse/printer_factory.py +++ b/pylint/pyreverse/printer_factory.py @@ -8,6 +8,7 @@ from typing import Dict, Type from pylint.pyreverse.dot_printer import DotPrinter +from pylint.pyreverse.mermaidjs_printer import HTMLMermaidJSPrinter, MermaidJSPrinter from pylint.pyreverse.plantuml_printer import PlantUmlPrinter from pylint.pyreverse.printer import Printer from pylint.pyreverse.vcg_printer import VCGPrinter @@ -16,6 +17,8 @@ "vcg": VCGPrinter, "plantuml": PlantUmlPrinter, "puml": PlantUmlPrinter, + "mmd": MermaidJSPrinter, + "html": HTMLMermaidJSPrinter, "dot": DotPrinter, } diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py index 9536fbcb0a..c83e74cbd4 100644 --- a/tests/pyreverse/conftest.py +++ b/tests/pyreverse/conftest.py @@ -43,6 +43,22 @@ def colorized_puml_config() -> PyreverseConfig: ) +@pytest.fixture() +def mmd_config() -> PyreverseConfig: + return PyreverseConfig( + output_format="mmd", + colorized=False, + ) + + +@pytest.fixture() +def html_config() -> PyreverseConfig: + return PyreverseConfig( + output_format="html", + colorized=False, + ) + + @pytest.fixture(scope="session") def get_project() -> Callable: def _get_project(module: str, name: Optional[str] = "No Name") -> Project: diff --git a/tests/pyreverse/data/classes_No_Name.html b/tests/pyreverse/data/classes_No_Name.html new file mode 100644 index 0000000000..3f81c340e5 --- /dev/null +++ b/tests/pyreverse/data/classes_No_Name.html @@ -0,0 +1,47 @@ + + + +
+ + classDiagram + class Ancestor { + attr : str + cls_member + get_value() + set_value(value) + } + class CustomException { + } + class DoNothing { + } + class DoNothing2 { + } + class DoSomething { + my_int : Optional[int] + my_int_2 : Optional[int] + my_string : str + do_it(new_int: int) -> int + } + class Interface { + get_value() + set_value(value) + } + class PropertyPatterns { + prop1 + prop2 + } + class Specialization { + TYPE : str + relation + relation2 + top : str + } + Specialization --|> Ancestor + Ancestor ..|> Interface + DoNothing --* Ancestor : cls_member + DoNothing --* Specialization : relation + DoNothing2 --* Specialization : relation2 + +
+ + diff --git a/tests/pyreverse/data/classes_No_Name.mmd b/tests/pyreverse/data/classes_No_Name.mmd new file mode 100644 index 0000000000..d2ac9839db --- /dev/null +++ b/tests/pyreverse/data/classes_No_Name.mmd @@ -0,0 +1,38 @@ +classDiagram + class Ancestor { + attr : str + cls_member + get_value() + set_value(value) + } + class CustomException { + } + class DoNothing { + } + class DoNothing2 { + } + class DoSomething { + my_int : Optional[int] + my_int_2 : Optional[int] + my_string : str + do_it(new_int: int) -> int + } + class Interface { + get_value() + set_value(value) + } + class PropertyPatterns { + prop1 + prop2 + } + class Specialization { + TYPE : str + relation + relation2 + top : str + } + Specialization --|> Ancestor + Ancestor ..|> Interface + DoNothing --* Ancestor : cls_member + DoNothing --* Specialization : relation + DoNothing2 --* Specialization : relation2 diff --git a/tests/pyreverse/data/packages_No_Name.html b/tests/pyreverse/data/packages_No_Name.html new file mode 100644 index 0000000000..128f8d1a43 --- /dev/null +++ b/tests/pyreverse/data/packages_No_Name.html @@ -0,0 +1,19 @@ + + + +
+ + classDiagram + class data { + } + class clientmodule_test { + } + class property_pattern { + } + class suppliermodule_test { + } + clientmodule_test --> suppliermodule_test + +
+ + diff --git a/tests/pyreverse/data/packages_No_Name.mmd b/tests/pyreverse/data/packages_No_Name.mmd new file mode 100644 index 0000000000..e8b02d0704 --- /dev/null +++ b/tests/pyreverse/data/packages_No_Name.mmd @@ -0,0 +1,10 @@ +classDiagram + class data { + } + class clientmodule_test { + } + class property_pattern { + } + class suppliermodule_test { + } + clientmodule_test --> suppliermodule_test diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 142ba04a00..bbfd364111 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -58,6 +58,8 @@ VCG_FILES = ["packages_No_Name.vcg", "classes_No_Name.vcg"] PUML_FILES = ["packages_No_Name.puml", "classes_No_Name.puml"] COLORIZED_PUML_FILES = ["packages_colorized.puml", "classes_colorized.puml"] +MMD_FILES = ["packages_No_Name.mmd", "classes_No_Name.mmd"] +HTML_FILES = ["packages_No_Name.html", "classes_No_Name.html"] class Config: @@ -121,6 +123,22 @@ def setup_colorized_puml( yield from _setup(project, colorized_puml_config, writer) +@pytest.fixture() +def setup_mmd(mmd_config: PyreverseConfig, get_project: Callable) -> Iterator: + writer = DiagramWriter(mmd_config) + + project = get_project(TEST_DATA_DIR) + yield from _setup(project, mmd_config, writer) + + +@pytest.fixture() +def setup_html(html_config: PyreverseConfig, get_project: Callable) -> Iterator: + writer = DiagramWriter(html_config) + + project = get_project(TEST_DATA_DIR) + yield from _setup(project, html_config, writer) + + def _setup( project: Project, config: PyreverseConfig, writer: DiagramWriter ) -> Iterator: @@ -164,6 +182,18 @@ def test_puml_files(generated_file: str) -> None: _assert_files_are_equal(generated_file) +@pytest.mark.usefixtures("setup_mmd") +@pytest.mark.parametrize("generated_file", MMD_FILES) +def test_mmd_files(generated_file: str) -> None: + _assert_files_are_equal(generated_file) + + +@pytest.mark.usefixtures("setup_html") +@pytest.mark.parametrize("generated_file", HTML_FILES) +def test_html_files(generated_file: str) -> None: + _assert_files_are_equal(generated_file) + + @pytest.mark.usefixtures("setup_colorized_puml") @pytest.mark.parametrize("generated_file", COLORIZED_PUML_FILES) def test_colorized_puml_files(generated_file: str) -> None: From c209dc0d31ca9e244eda1e7b0f29be6663a9983a Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 13:03:13 +0100 Subject: [PATCH 003/357] Add a path argument to get_tests for functional tests --- tests/test_functional.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_functional.py b/tests/test_functional.py index 6f0eceb23f..e1de539149 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -25,7 +25,8 @@ import csv import os import sys -from typing import Union +from pathlib import Path +from typing import List, Union import pytest from _pytest.config import Config @@ -64,8 +65,7 @@ def _check_output_text(self, _, expected_output, actual_output): writer.writerow(line.to_csv()) -def get_tests(): - input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "functional") +def get_tests(input_dir: Union[Path, str]) -> List[FunctionalTestFile]: suite = [] for dirpath, _, filenames in os.walk(input_dir): if dirpath.endswith("__pycache__"): @@ -85,7 +85,9 @@ def get_tests(): return suite -TESTS = get_tests() +TESTS = get_tests( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "functional") +) TESTS_NAMES = [t.base for t in TESTS] TEST_WITH_EXPECTED_DEPRECATION = [ "future_unicode_literals", From e38dfbbf597a25d9f5f96e5c9030cf43ef58257e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 13:06:29 +0100 Subject: [PATCH 004/357] Rename get_tests to get_functional_test_files_from_directory And use Path instead of os --- tests/test_functional.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_functional.py b/tests/test_functional.py index e1de539149..c0badfbe37 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -43,6 +43,7 @@ # 'Wet finger' number of files that are reasonable to display by an IDE # 'Wet finger' as in 'in my settings there are precisely this many'. REASONABLY_DISPLAYABLE_VERTICALLY = 48 +FUNCTIONAL_DIR = Path(__file__).parent.resolve() / "functional" class LintModuleOutputUpdate(testutils.LintModuleTest): @@ -65,7 +66,9 @@ def _check_output_text(self, _, expected_output, actual_output): writer.writerow(line.to_csv()) -def get_tests(input_dir: Union[Path, str]) -> List[FunctionalTestFile]: +def get_functional_test_files_from_directory( + input_dir: Union[Path, str] +) -> List[FunctionalTestFile]: suite = [] for dirpath, _, filenames in os.walk(input_dir): if dirpath.endswith("__pycache__"): @@ -85,9 +88,7 @@ def get_tests(input_dir: Union[Path, str]) -> List[FunctionalTestFile]: return suite -TESTS = get_tests( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "functional") -) +TESTS = get_functional_test_files_from_directory(FUNCTIONAL_DIR) TESTS_NAMES = [t.base for t in TESTS] TEST_WITH_EXPECTED_DEPRECATION = [ "future_unicode_literals", From e88c145acc473e895763d0ff6e2d51a1a5034e95 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 13:12:10 +0100 Subject: [PATCH 005/357] Move the filter for isort 5 out of the generic function --- tests/test_functional.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_functional.py b/tests/test_functional.py index c0badfbe37..d1d8d6d11d 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -80,15 +80,16 @@ def get_functional_test_files_from_directory( for filename in filenames: if filename != "__init__.py" and filename.endswith(".py"): - # isort 5 has slightly different rules as isort 4. Testing - # both would be hard: test with isort 5 only. - if filename == "wrong_import_order.py" and not HAS_ISORT_5: - continue suite.append(testutils.FunctionalTestFile(dirpath, filename)) return suite -TESTS = get_functional_test_files_from_directory(FUNCTIONAL_DIR) +# isort 5 has slightly different rules as isort 4. Testing both would be hard: test with isort 5 only. +TESTS = [ + t + for t in get_functional_test_files_from_directory(FUNCTIONAL_DIR) + if not (t.base == "wrong_import_order" and not HAS_ISORT_5) +] TESTS_NAMES = [t.base for t in TESTS] TEST_WITH_EXPECTED_DEPRECATION = [ "future_unicode_literals", From 3f1588c3b02a54800ee3a5b071a8d19d381b8649 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 14:39:21 +0100 Subject: [PATCH 006/357] Remove a constant that was never used anywhere --- pylint/testutils/constants.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pylint/testutils/constants.py b/pylint/testutils/constants.py index fc04d927d7..c46da393ce 100644 --- a/pylint/testutils/constants.py +++ b/pylint/testutils/constants.py @@ -4,14 +4,12 @@ import operator import re import sys -from os.path import abspath, dirname from pathlib import Path SYS_VERS_STR = ( "%d%d%d" % sys.version_info[:3] # pylint: disable=consider-using-f-string ) TITLE_UNDERLINES = ["", "=", "-", "."] -PREFIX = abspath(dirname(__file__)) UPDATE_OPTION = "--update-functional-output" UPDATE_FILE = Path("pylint-functional-test-update") # Common sub-expressions. From 833ed555101abc1e85386dabc378586700ac8a96 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 14:56:13 +0100 Subject: [PATCH 007/357] Deprecate some file in pylint/testutils and update changelog --- ChangeLog | 2 + pylint/testutils/__init__.py | 2 +- pylint/testutils/functional/__init__.py | 14 ++++ pylint/testutils/functional/test_file.py | 75 +++++++++++++++++++ pylint/testutils/functional_test_file.py | 91 +++++------------------- pylint/testutils/lint_module_test.py | 2 +- tests/test_functional.py | 2 +- 7 files changed, 113 insertions(+), 75 deletions(-) create mode 100644 pylint/testutils/functional/__init__.py create mode 100644 pylint/testutils/functional/test_file.py diff --git a/ChangeLog b/ChangeLog index c55294e16a..83b0b15868 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,8 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Some file in ``pylint.testutils`` were deprecated, imports should be done from the + ``pylint.testutils`` API directly .. Insert your changelog randomly, it will reduce merge conflicts diff --git a/pylint/testutils/__init__.py b/pylint/testutils/__init__.py index 737940c8fc..803a1ae160 100644 --- a/pylint/testutils/__init__.py +++ b/pylint/testutils/__init__.py @@ -49,7 +49,7 @@ from pylint.testutils.checker_test_case import CheckerTestCase from pylint.testutils.constants import UPDATE_FILE, UPDATE_OPTION from pylint.testutils.decorator import set_config -from pylint.testutils.functional_test_file import FunctionalTestFile +from pylint.testutils.functional.test_file import FunctionalTestFile from pylint.testutils.get_test_info import _get_tests_info from pylint.testutils.global_test_linter import linter from pylint.testutils.lint_module_test import LintModuleTest diff --git a/pylint/testutils/functional/__init__.py b/pylint/testutils/functional/__init__.py new file mode 100644 index 0000000000..64c150d7e2 --- /dev/null +++ b/pylint/testutils/functional/__init__.py @@ -0,0 +1,14 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +__all__ = [ + "FunctionalTestFile", + "NoFileError", + "parse_python_version", +] + +from pylint.testutils.functional.test_file import ( + FunctionalTestFile, + NoFileError, + parse_python_version, +) diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py new file mode 100644 index 0000000000..4bf0777f80 --- /dev/null +++ b/pylint/testutils/functional/test_file.py @@ -0,0 +1,75 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +import configparser +from os.path import basename, exists, join + + +def parse_python_version(ver_str): + return tuple(int(digit) for digit in ver_str.split(".")) + + +class NoFileError(Exception): + pass + + +class FunctionalTestFile: + """A single functional test case file with options.""" + + _CONVERTERS = { + "min_pyver": parse_python_version, + "max_pyver": parse_python_version, + "min_pyver_end_position": parse_python_version, + "requires": lambda s: s.split(","), + } + + def __init__(self, directory, filename): + self._directory = directory + self.base = filename.replace(".py", "") + self.options = { + "min_pyver": (2, 5), + "max_pyver": (4, 0), + "min_pyver_end_position": (3, 8), + "requires": [], + "except_implementations": [], + "exclude_platforms": [], + } + self._parse_options() + + def __repr__(self): + return f"FunctionalTest:{self.base}" + + def _parse_options(self): + cp = configparser.ConfigParser() + cp.add_section("testoptions") + try: + cp.read(self.option_file) + except NoFileError: + pass + + for name, value in cp.items("testoptions"): + conv = self._CONVERTERS.get(name, lambda v: v) + self.options[name] = conv(value) + + @property + def option_file(self): + return self._file_type(".rc") + + @property + def module(self): + package = basename(self._directory) + return ".".join([package, self.base]) + + @property + def expected_output(self): + return self._file_type(".txt", check_exists=False) + + @property + def source(self): + return self._file_type(".py") + + def _file_type(self, ext, check_exists=True): + name = join(self._directory, self.base + ext) + if not check_exists or exists(name): + return name + raise NoFileError(f"Cannot find '{name}'.") diff --git a/pylint/testutils/functional_test_file.py b/pylint/testutils/functional_test_file.py index 4bf0777f80..5a6cad2df9 100644 --- a/pylint/testutils/functional_test_file.py +++ b/pylint/testutils/functional_test_file.py @@ -1,75 +1,22 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -import configparser -from os.path import basename, exists, join - - -def parse_python_version(ver_str): - return tuple(int(digit) for digit in ver_str.split(".")) - - -class NoFileError(Exception): - pass - - -class FunctionalTestFile: - """A single functional test case file with options.""" - - _CONVERTERS = { - "min_pyver": parse_python_version, - "max_pyver": parse_python_version, - "min_pyver_end_position": parse_python_version, - "requires": lambda s: s.split(","), - } - - def __init__(self, directory, filename): - self._directory = directory - self.base = filename.replace(".py", "") - self.options = { - "min_pyver": (2, 5), - "max_pyver": (4, 0), - "min_pyver_end_position": (3, 8), - "requires": [], - "except_implementations": [], - "exclude_platforms": [], - } - self._parse_options() - - def __repr__(self): - return f"FunctionalTest:{self.base}" - - def _parse_options(self): - cp = configparser.ConfigParser() - cp.add_section("testoptions") - try: - cp.read(self.option_file) - except NoFileError: - pass - - for name, value in cp.items("testoptions"): - conv = self._CONVERTERS.get(name, lambda v: v) - self.options[name] = conv(value) - - @property - def option_file(self): - return self._file_type(".rc") - - @property - def module(self): - package = basename(self._directory) - return ".".join([package, self.base]) - - @property - def expected_output(self): - return self._file_type(".txt", check_exists=False) - - @property - def source(self): - return self._file_type(".py") - - def _file_type(self, ext, check_exists=True): - name = join(self._directory, self.base + ext) - if not check_exists or exists(name): - return name - raise NoFileError(f"Cannot find '{name}'.") +__all__ = [ + "FunctionalTestFile", + "NoFileError", + "parse_python_version", +] + +import warnings + +from pylint.testutils.functional import ( + FunctionalTestFile, + NoFileError, + parse_python_version, +) + +warnings.warn( + "'pylint.testutils.functional_test_file' will be accessible by" + " the 'pylint.testutils.functional' API in pylint 3.0", + DeprecationWarning, +) diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 8edc638760..05d6f4a229 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -17,7 +17,7 @@ from pylint.lint import PyLinter from pylint.message.message import Message from pylint.testutils.constants import _EXPECTED_RE, _OPERATORS, UPDATE_OPTION -from pylint.testutils.functional_test_file import ( +from pylint.testutils.functional.test_file import ( FunctionalTestFile, NoFileError, parse_python_version, diff --git a/tests/test_functional.py b/tests/test_functional.py index d1d8d6d11d..45edb49fbd 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -34,7 +34,7 @@ from pylint import testutils from pylint.testutils import UPDATE_FILE, UPDATE_OPTION -from pylint.testutils.functional_test_file import FunctionalTestFile +from pylint.testutils.functional.test_file import FunctionalTestFile from pylint.utils import HAS_ISORT_5 # TODOs From 6c7fbc230bf77429423bbded6f7cb56ea7ef6a40 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 15:02:59 +0100 Subject: [PATCH 008/357] Move the function go retrieve test file to pylint.testutil.functional --- pylint/testutils/functional/__init__.py | 6 ++++ .../functional/find_functional_tests.py | 30 +++++++++++++++++++ tests/test_functional.py | 29 ++++-------------- 3 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 pylint/testutils/functional/find_functional_tests.py diff --git a/pylint/testutils/functional/__init__.py b/pylint/testutils/functional/__init__.py index 64c150d7e2..a97eea0bb8 100644 --- a/pylint/testutils/functional/__init__.py +++ b/pylint/testutils/functional/__init__.py @@ -3,10 +3,16 @@ __all__ = [ "FunctionalTestFile", + "REASONABLY_DISPLAYABLE_VERTICALLY", + "get_functional_test_files_from_directory", "NoFileError", "parse_python_version", ] +from pylint.testutils.functional.find_functional_tests import ( + REASONABLY_DISPLAYABLE_VERTICALLY, + get_functional_test_files_from_directory, +) from pylint.testutils.functional.test_file import ( FunctionalTestFile, NoFileError, diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py new file mode 100644 index 0000000000..6eab2e9e82 --- /dev/null +++ b/pylint/testutils/functional/find_functional_tests.py @@ -0,0 +1,30 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +import os +from pathlib import Path +from typing import List, Union + +from pylint.testutils.functional.test_file import FunctionalTestFile + +# 'Wet finger' number of files that are reasonable to display by an IDE +# 'Wet finger' as in 'in my settings there are precisely this many'. +REASONABLY_DISPLAYABLE_VERTICALLY = 48 + + +def get_functional_test_files_from_directory( + input_dir: Union[Path, str] +) -> List[FunctionalTestFile]: + suite = [] + for dirpath, _, filenames in os.walk(input_dir): + if dirpath.endswith("__pycache__"): + continue + + assert ( + len(filenames) <= REASONABLY_DISPLAYABLE_VERTICALLY + ), f"{dirpath} contain too much functional tests files." + + for filename in filenames: + if filename != "__init__.py" and filename.endswith(".py"): + suite.append(FunctionalTestFile(dirpath, filename)) + return suite diff --git a/tests/test_functional.py b/tests/test_functional.py index 45edb49fbd..d39d205832 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -26,7 +26,7 @@ import os import sys from pathlib import Path -from typing import List, Union +from typing import Union import pytest from _pytest.config import Config @@ -34,15 +34,16 @@ from pylint import testutils from pylint.testutils import UPDATE_FILE, UPDATE_OPTION -from pylint.testutils.functional.test_file import FunctionalTestFile +from pylint.testutils.functional import ( + FunctionalTestFile, + get_functional_test_files_from_directory, +) from pylint.utils import HAS_ISORT_5 # TODOs # - implement exhaustivity tests -# 'Wet finger' number of files that are reasonable to display by an IDE -# 'Wet finger' as in 'in my settings there are precisely this many'. -REASONABLY_DISPLAYABLE_VERTICALLY = 48 + FUNCTIONAL_DIR = Path(__file__).parent.resolve() / "functional" @@ -66,24 +67,6 @@ def _check_output_text(self, _, expected_output, actual_output): writer.writerow(line.to_csv()) -def get_functional_test_files_from_directory( - input_dir: Union[Path, str] -) -> List[FunctionalTestFile]: - suite = [] - for dirpath, _, filenames in os.walk(input_dir): - if dirpath.endswith("__pycache__"): - continue - - assert ( - len(filenames) <= REASONABLY_DISPLAYABLE_VERTICALLY - ), f"{dirpath} contain too much functional tests files." - - for filename in filenames: - if filename != "__init__.py" and filename.endswith(".py"): - suite.append(testutils.FunctionalTestFile(dirpath, filename)) - return suite - - # isort 5 has slightly different rules as isort 4. Testing both would be hard: test with isort 5 only. TESTS = [ t From cfa4f960426a41a34d29083bd6cc611086a4a39b Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 15:06:08 +0100 Subject: [PATCH 009/357] Move LintModuleOutputUpdate to pylint.testutil.functional --- pylint/testutils/functional/__init__.py | 2 ++ .../functional/lint_module_output_update.py | 27 +++++++++++++++++++ tests/test_functional.py | 23 +--------------- 3 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 pylint/testutils/functional/lint_module_output_update.py diff --git a/pylint/testutils/functional/__init__.py b/pylint/testutils/functional/__init__.py index a97eea0bb8..607c22cb45 100644 --- a/pylint/testutils/functional/__init__.py +++ b/pylint/testutils/functional/__init__.py @@ -7,12 +7,14 @@ "get_functional_test_files_from_directory", "NoFileError", "parse_python_version", + "LintModuleOutputUpdate", ] from pylint.testutils.functional.find_functional_tests import ( REASONABLY_DISPLAYABLE_VERTICALLY, get_functional_test_files_from_directory, ) +from pylint.testutils.functional.lint_module_output_update import LintModuleOutputUpdate from pylint.testutils.functional.test_file import ( FunctionalTestFile, NoFileError, diff --git a/pylint/testutils/functional/lint_module_output_update.py b/pylint/testutils/functional/lint_module_output_update.py new file mode 100644 index 0000000000..113f6448f8 --- /dev/null +++ b/pylint/testutils/functional/lint_module_output_update.py @@ -0,0 +1,27 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +import csv +import os + +from pylint.testutils.lint_module_test import LintModuleTest + + +class LintModuleOutputUpdate(LintModuleTest): + """If message files should be updated instead of checked.""" + + class TestDialect(csv.excel): + delimiter = ":" + lineterminator = "\n" + + csv.register_dialect("test", TestDialect) + + def _check_output_text(self, _, expected_output, actual_output): + if not expected_output and not actual_output: + if os.path.exists(self._test_file.expected_output): + os.remove(self._test_file.expected_output) + return + with open(self._test_file.expected_output, "w", encoding="utf-8") as f: + writer = csv.writer(f, dialect="test") + for line in actual_output: + writer.writerow(line.to_csv()) diff --git a/tests/test_functional.py b/tests/test_functional.py index d39d205832..c877db49bd 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -22,8 +22,6 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE """Functional full-module tests for PyLint.""" -import csv -import os import sys from pathlib import Path from typing import Union @@ -38,6 +36,7 @@ FunctionalTestFile, get_functional_test_files_from_directory, ) +from pylint.testutils.functional.lint_module_output_update import LintModuleOutputUpdate from pylint.utils import HAS_ISORT_5 # TODOs @@ -47,26 +46,6 @@ FUNCTIONAL_DIR = Path(__file__).parent.resolve() / "functional" -class LintModuleOutputUpdate(testutils.LintModuleTest): - """If message files should be updated instead of checked.""" - - class TestDialect(csv.excel): - delimiter = ":" - lineterminator = "\n" - - csv.register_dialect("test", TestDialect) - - def _check_output_text(self, _, expected_output, actual_output): - if not expected_output and not actual_output: - if os.path.exists(self._test_file.expected_output): - os.remove(self._test_file.expected_output) - return - with open(self._test_file.expected_output, "w", encoding="utf-8") as f: - writer = csv.writer(f, dialect="test") - for line in actual_output: - writer.writerow(line.to_csv()) - - # isort 5 has slightly different rules as isort 4. Testing both would be hard: test with isort 5 only. TESTS = [ t From d145f7a8208119570acfbb49a4b09c5ec41437d7 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 16:38:06 +0100 Subject: [PATCH 010/357] Add unit tests for LintModuleOutputUpdate --- .../test_lint_module_output_update.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/testutils/test_lint_module_output_update.py diff --git a/tests/testutils/test_lint_module_output_update.py b/tests/testutils/test_lint_module_output_update.py new file mode 100644 index 0000000000..bf566a473d --- /dev/null +++ b/tests/testutils/test_lint_module_output_update.py @@ -0,0 +1,63 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +# pylint: disable=redefined-outer-name +from pathlib import Path +from typing import Callable, Tuple + +import pytest + +from pylint.testutils import FunctionalTestFile +from pylint.testutils.functional import LintModuleOutputUpdate + + +@pytest.fixture() +def lint_module_fixture( + tmp_path: Path, +) -> Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]]: + def inner(base: str) -> Tuple[Path, Path, LintModuleOutputUpdate]: + filename = tmp_path / f"{base}.py" + expected_output_file = tmp_path / f"{base}.txt" + lmou = LintModuleOutputUpdate( + test_file=FunctionalTestFile(str(tmp_path), str(filename)) + ) + return filename, expected_output_file, lmou + + return inner + + +def test_lint_module_output_update_fail_before( + lint_module_fixture: Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]] +) -> None: + """There is a fail before the output need to be updated.""" + filename, expected_output_file, lmou = lint_module_fixture("foo") + filename.write_text("", encoding="utf8") + assert not expected_output_file.exists() + with pytest.raises(AssertionError, match="1: disallowed-name"): + lmou.runTest() + assert not expected_output_file.exists() + + +def test_lint_module_output_update_effective( + lint_module_fixture: Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]] +) -> None: + """The file is updated following a successful tests with wrong output.""" + filename, expected_output_file, lmou = lint_module_fixture("foo") + filename.write_text("# [disallowed-name]\n", encoding="utf8") + lmou.runTest() + assert (expected_output_file).exists() + assert ( + expected_output_file.read_text(encoding="utf8") + == 'disallowed-name:1:0:None:None::"Disallowed name ""foo""":UNDEFINED\n' + ) + + +def test_lint_module_output_update_remove_useless_txt( + lint_module_fixture: Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]] +) -> None: + """The file is updated following a successful tests with wrong output.""" + filename, expected_output_file, lmou = lint_module_fixture("fine_name") + expected_output_file.write_text("", encoding="utf8") + filename.write_text("", encoding="utf8") + lmou.runTest() + assert not (expected_output_file).exists() From bb3c4467374a31b713099a14f800fa3d5b6a4650 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 18:05:29 +0100 Subject: [PATCH 011/357] Add a warning in functional test update for bad python versions --- .../functional/find_functional_tests.py | 2 +- .../functional/lint_module_output_update.py | 16 ++++++++++++++++ pylint/testutils/functional_test_file.py | 4 ++-- .../testutils/test_lint_module_output_update.py | 12 ++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py index 6eab2e9e82..27e5880da3 100644 --- a/pylint/testutils/functional/find_functional_tests.py +++ b/pylint/testutils/functional/find_functional_tests.py @@ -22,7 +22,7 @@ def get_functional_test_files_from_directory( assert ( len(filenames) <= REASONABLY_DISPLAYABLE_VERTICALLY - ), f"{dirpath} contain too much functional tests files." + ), f"{dirpath} contains too many functional tests files." for filename in filenames: if filename != "__init__.py" and filename.endswith(".py"): diff --git a/pylint/testutils/functional/lint_module_output_update.py b/pylint/testutils/functional/lint_module_output_update.py index 113f6448f8..dfb8911894 100644 --- a/pylint/testutils/functional/lint_module_output_update.py +++ b/pylint/testutils/functional/lint_module_output_update.py @@ -3,7 +3,12 @@ import csv import os +from typing import Optional +from _pytest.config import Config + +from pylint.constants import PY38_PLUS +from pylint.testutils.functional.test_file import FunctionalTestFile from pylint.testutils.lint_module_test import LintModuleTest @@ -16,6 +21,17 @@ class TestDialect(csv.excel): csv.register_dialect("test", TestDialect) + def __init__( + self, test_file: FunctionalTestFile, config: Optional[Config] = None + ) -> None: + if not PY38_PLUS: + raise RuntimeError( + "You need at least python 3.8 for the functional test updater to work. " + "This is because python 3.8 includes a new AST parser, which amongst others " + "returns the end line and end column of most nodes." + ) + super().__init__(test_file, config) + def _check_output_text(self, _, expected_output, actual_output): if not expected_output and not actual_output: if os.path.exists(self._test_file.expected_output): diff --git a/pylint/testutils/functional_test_file.py b/pylint/testutils/functional_test_file.py index 5a6cad2df9..cb13cd21d8 100644 --- a/pylint/testutils/functional_test_file.py +++ b/pylint/testutils/functional_test_file.py @@ -16,7 +16,7 @@ ) warnings.warn( - "'pylint.testutils.functional_test_file' will be accessible by" - " the 'pylint.testutils.functional' API in pylint 3.0", + "'pylint.testutils.functional_test_file' will be accessible from" + " the 'pylint.testutils.functional' namespace in pylint 3.0.", DeprecationWarning, ) diff --git a/tests/testutils/test_lint_module_output_update.py b/tests/testutils/test_lint_module_output_update.py index bf566a473d..9f581ce763 100644 --- a/tests/testutils/test_lint_module_output_update.py +++ b/tests/testutils/test_lint_module_output_update.py @@ -7,6 +7,7 @@ import pytest +from pylint.constants import PY38_PLUS from pylint.testutils import FunctionalTestFile from pylint.testutils.functional import LintModuleOutputUpdate @@ -26,6 +27,15 @@ def inner(base: str) -> Tuple[Path, Path, LintModuleOutputUpdate]: return inner +@pytest.mark.skipif(PY38_PLUS, reason="Requires python 3.7 or lower") +def test_not_py38(tmp_path: Path) -> None: + with pytest.raises(RuntimeError, match="new, better AST in python 3.8"): + LintModuleOutputUpdate( + test_file=FunctionalTestFile(str(tmp_path), str(tmp_path / "filename.py")) + ) + + +@pytest.mark.skipif(not PY38_PLUS, reason="Requires python 3.8 or superior") def test_lint_module_output_update_fail_before( lint_module_fixture: Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]] ) -> None: @@ -38,6 +48,7 @@ def test_lint_module_output_update_fail_before( assert not expected_output_file.exists() +@pytest.mark.skipif(not PY38_PLUS, reason="Requires python 3.8 or superior") def test_lint_module_output_update_effective( lint_module_fixture: Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]] ) -> None: @@ -52,6 +63,7 @@ def test_lint_module_output_update_effective( ) +@pytest.mark.skipif(not PY38_PLUS, reason="Requires python 3.8 or superior") def test_lint_module_output_update_remove_useless_txt( lint_module_fixture: Callable[[str], Tuple[Path, Path, LintModuleOutputUpdate]] ) -> None: From 5bb0095aa122fd75108dc8049193b55b987ef224 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 19:50:59 +0100 Subject: [PATCH 012/357] Add typing to LintModuleOutputUpdate._check_output_text Better typing for test options and fix existing issue --- ChangeLog | 4 +- pylint/testutils/__init__.py | 2 +- .../functional/find_functional_tests.py | 1 + .../functional/lint_module_output_update.py | 19 ++++-- pylint/testutils/functional/test_file.py | 60 +++++++++++++++---- pylint/testutils/lint_module_test.py | 2 +- tests/functional/f/fixme.rc | 4 +- .../r/regression/regression_issue_4631.rc | 2 +- .../t/too/too_few_public_methods_excluded.rc | 2 +- .../too/too_many_ancestors_ignored_parents.rc | 2 +- tests/functional/t/too/too_many_statements.rc | 2 +- tests/test_functional.py | 2 +- .../test_lint_module_output_update.py | 2 +- 13 files changed, 76 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 83b0b15868..9cf285dc26 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,8 +11,8 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' -* Some file in ``pylint.testutils`` were deprecated, imports should be done from the - ``pylint.testutils`` API directly +* Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the + ``pylint.testutils.functional`` namespace directly. .. Insert your changelog randomly, it will reduce merge conflicts diff --git a/pylint/testutils/__init__.py b/pylint/testutils/__init__.py index 803a1ae160..53e301da3d 100644 --- a/pylint/testutils/__init__.py +++ b/pylint/testutils/__init__.py @@ -49,7 +49,7 @@ from pylint.testutils.checker_test_case import CheckerTestCase from pylint.testutils.constants import UPDATE_FILE, UPDATE_OPTION from pylint.testutils.decorator import set_config -from pylint.testutils.functional.test_file import FunctionalTestFile +from pylint.testutils.functional import FunctionalTestFile from pylint.testutils.get_test_info import _get_tests_info from pylint.testutils.global_test_linter import linter from pylint.testutils.lint_module_test import LintModuleTest diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py index 27e5880da3..610b05db1a 100644 --- a/pylint/testutils/functional/find_functional_tests.py +++ b/pylint/testutils/functional/find_functional_tests.py @@ -15,6 +15,7 @@ def get_functional_test_files_from_directory( input_dir: Union[Path, str] ) -> List[FunctionalTestFile]: + """Get all functional tests in the input_dir.""" suite = [] for dirpath, _, filenames in os.walk(input_dir): if dirpath.endswith("__pycache__"): diff --git a/pylint/testutils/functional/lint_module_output_update.py b/pylint/testutils/functional/lint_module_output_update.py index dfb8911894..0bd46fc0bf 100644 --- a/pylint/testutils/functional/lint_module_output_update.py +++ b/pylint/testutils/functional/lint_module_output_update.py @@ -3,19 +3,22 @@ import csv import os -from typing import Optional +from typing import List, Optional from _pytest.config import Config from pylint.constants import PY38_PLUS from pylint.testutils.functional.test_file import FunctionalTestFile -from pylint.testutils.lint_module_test import LintModuleTest +from pylint.testutils.lint_module_test import LintModuleTest, MessageCounter +from pylint.testutils.output_line import OutputLine class LintModuleOutputUpdate(LintModuleTest): - """If message files should be updated instead of checked.""" + """Class to be used if expected output files should be updated instead of checked.""" class TestDialect(csv.excel): + """Dialect used by the csv writer.""" + delimiter = ":" lineterminator = "\n" @@ -32,11 +35,19 @@ def __init__( ) super().__init__(test_file, config) - def _check_output_text(self, _, expected_output, actual_output): + def _check_output_text( + self, + _: MessageCounter, + expected_output: List[OutputLine], + actual_output: List[OutputLine], + ) -> None: + """Overwrite or remove the expected output file based on actual output.""" + # Remove the file if no output is actually expected and a file exists if not expected_output and not actual_output: if os.path.exists(self._test_file.expected_output): os.remove(self._test_file.expected_output) return + # Write file with expected output with open(self._test_file.expected_output, "w", encoding="utf-8") as f: writer = csv.writer(f, dialect="test") for line in actual_output: diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index 4bf0777f80..8aa0361d45 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -2,10 +2,13 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import configparser +import sys from os.path import basename, exists, join +from typing import List, Tuple -def parse_python_version(ver_str): +def parse_python_version(ver_str: str) -> Tuple[int, ...]: + """Convert python version to a tuple of integers for easy comparison.""" return tuple(int(digit) for digit in ver_str.split(".")) @@ -13,6 +16,33 @@ class NoFileError(Exception): pass +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict + + +class TestFileOptions(TypedDict): + min_pyver: Tuple[int, ...] + max_pyver: Tuple[int, ...] + min_pyver_end_position: Tuple[int, ...] + requires: List[str] + except_implementations: str # Type is actually comma separated list of string + exclude_platforms: str # Type is actually comma separated list of string + + +# mypy need something literal, we can't create this dynamically from TestFileOptions +POSSIBLE_TEST_OPTIONS = { + "min_pyver", + "max_pyver", + "min_pyver_end_position", + "requires", + "except_implementations", + "exclude_platforms", + "exclude_platforms", +} + + class FunctionalTestFile: """A single functional test case file with options.""" @@ -23,23 +53,23 @@ class FunctionalTestFile: "requires": lambda s: s.split(","), } - def __init__(self, directory, filename): + def __init__(self, directory: str, filename: str) -> None: self._directory = directory self.base = filename.replace(".py", "") - self.options = { + self.options: TestFileOptions = { "min_pyver": (2, 5), "max_pyver": (4, 0), "min_pyver_end_position": (3, 8), "requires": [], - "except_implementations": [], - "exclude_platforms": [], + "except_implementations": "", + "exclude_platforms": "", } self._parse_options() - def __repr__(self): + def __repr__(self) -> str: return f"FunctionalTest:{self.base}" - def _parse_options(self): + def _parse_options(self) -> None: cp = configparser.ConfigParser() cp.add_section("testoptions") try: @@ -49,26 +79,30 @@ def _parse_options(self): for name, value in cp.items("testoptions"): conv = self._CONVERTERS.get(name, lambda v: v) - self.options[name] = conv(value) + + assert ( + name in POSSIBLE_TEST_OPTIONS + ), f"[testoptions]' can only contains one of {POSSIBLE_TEST_OPTIONS}" + self.options[name] = conv(value) # type: ignore[misc] @property - def option_file(self): + def option_file(self) -> str: return self._file_type(".rc") @property - def module(self): + def module(self) -> str: package = basename(self._directory) return ".".join([package, self.base]) @property - def expected_output(self): + def expected_output(self) -> str: return self._file_type(".txt", check_exists=False) @property - def source(self): + def source(self) -> str: return self._file_type(".py") - def _file_type(self, ext, check_exists=True): + def _file_type(self, ext: str, check_exists: bool = True) -> str: name = join(self._directory, self.base + ext) if not check_exists or exists(name): return name diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 05d6f4a229..cbdedeecca 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -17,7 +17,7 @@ from pylint.lint import PyLinter from pylint.message.message import Message from pylint.testutils.constants import _EXPECTED_RE, _OPERATORS, UPDATE_OPTION -from pylint.testutils.functional.test_file import ( +from pylint.testutils.functional.test_file import ( # need to import from functional.test_file to avoid cyclic import FunctionalTestFile, NoFileError, parse_python_version, diff --git a/tests/functional/f/fixme.rc b/tests/functional/f/fixme.rc index 6b903c1bda..be1b23458e 100644 --- a/tests/functional/f/fixme.rc +++ b/tests/functional/f/fixme.rc @@ -1,3 +1,5 @@ -[testoptions] +[MISCELLANEOUS] +# List of note tags to take in consideration, separated by a comma. notes=XXX,TODO,./TODO +# Regular expression of note tags to take in consideration. notes-rgx=FIXME(?!.*ISSUE-\d+)|TO.*DO diff --git a/tests/functional/r/regression/regression_issue_4631.rc b/tests/functional/r/regression/regression_issue_4631.rc index 44c9b77eed..88cdb85bfa 100644 --- a/tests/functional/r/regression/regression_issue_4631.rc +++ b/tests/functional/r/regression/regression_issue_4631.rc @@ -1,2 +1,2 @@ -[testoptions] +[MASTER] limit-inference-results=0 diff --git a/tests/functional/t/too/too_few_public_methods_excluded.rc b/tests/functional/t/too/too_few_public_methods_excluded.rc index 00c025832c..b6a2cc9378 100644 --- a/tests/functional/t/too/too_few_public_methods_excluded.rc +++ b/tests/functional/t/too/too_few_public_methods_excluded.rc @@ -1,4 +1,4 @@ -[testoptions] +[DESIGN] min-public-methods=10 # to combat inherited methods exclude-too-few-public-methods=json.*,^.*Control$ diff --git a/tests/functional/t/too/too_many_ancestors_ignored_parents.rc b/tests/functional/t/too/too_many_ancestors_ignored_parents.rc index 1d06dad25d..aa652704b7 100644 --- a/tests/functional/t/too/too_many_ancestors_ignored_parents.rc +++ b/tests/functional/t/too/too_many_ancestors_ignored_parents.rc @@ -1,3 +1,3 @@ -[testoptions] +[DESIGN] max-parents=2 ignored-parents=functional.t.too.too_many_ancestors_ignored_parents.E diff --git a/tests/functional/t/too/too_many_statements.rc b/tests/functional/t/too/too_many_statements.rc index d6d22f2372..19e0d5151f 100644 --- a/tests/functional/t/too/too_many_statements.rc +++ b/tests/functional/t/too/too_many_statements.rc @@ -1,2 +1,2 @@ -[testoptions] +[DESIGN] max-statements=5 diff --git a/tests/test_functional.py b/tests/test_functional.py index c877db49bd..eb923401d8 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -34,9 +34,9 @@ from pylint.testutils import UPDATE_FILE, UPDATE_OPTION from pylint.testutils.functional import ( FunctionalTestFile, + LintModuleOutputUpdate, get_functional_test_files_from_directory, ) -from pylint.testutils.functional.lint_module_output_update import LintModuleOutputUpdate from pylint.utils import HAS_ISORT_5 # TODOs diff --git a/tests/testutils/test_lint_module_output_update.py b/tests/testutils/test_lint_module_output_update.py index 9f581ce763..ad414a9ddb 100644 --- a/tests/testutils/test_lint_module_output_update.py +++ b/tests/testutils/test_lint_module_output_update.py @@ -29,7 +29,7 @@ def inner(base: str) -> Tuple[Path, Path, LintModuleOutputUpdate]: @pytest.mark.skipif(PY38_PLUS, reason="Requires python 3.7 or lower") def test_not_py38(tmp_path: Path) -> None: - with pytest.raises(RuntimeError, match="new, better AST in python 3.8"): + with pytest.raises(RuntimeError, match="new AST parser"): LintModuleOutputUpdate( test_file=FunctionalTestFile(str(tmp_path), str(tmp_path / "filename.py")) ) From ef250da8ec64a26f4db6614531b0995778b5e983 Mon Sep 17 00:00:00 2001 From: yushao2 <36848472+yushao2@users.noreply.github.com> Date: Sat, 4 Dec 2021 02:32:52 +0800 Subject: [PATCH 013/357] fix(4716): fix false positive `unnecessary_dict_index_lookup` emitted when `del` is used (#5344) * fix(4716): fix false positive `unnecessary_dict_index_lookup` emitted when `del` is used Co-authored-by: Pierre Sassoulas --- ChangeLog | 14 +++++--------- doc/whatsnew/2.13.rst | 9 +++++++++ .../refactoring/refactoring_checker.py | 3 +++ .../unnecessary_dict_index_lookup.py | 18 ++++++++++++++++++ .../unnecessary_dict_index_lookup.txt | 1 + 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9cf285dc26..02cd4eea7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,18 +14,14 @@ Release date: TBA * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. -.. - Insert your changelog randomly, it will reduce merge conflicts - (Ie. not necessarily at the end) +* Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. + Closes #4716 -What's New in Pylint 2.12.3? -============================ -Release date: TBA - -.. - Put bug fixes that should not wait for a new minor version here +* Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables + or ``not in`` checks + Closes #5323 .. Insert your changelog randomly, it will reduce merge conflicts diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 60099b67ee..8222813d22 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -22,6 +22,15 @@ Extensions Other Changes ============= +* Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. + + Closes #4716 + +* Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables + or ``not in`` checks + + Closes #5323 + * Require Python ``3.6.2`` to run pylint. Closes #5065 diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 7568f3eeaa..9f28d510b4 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1873,6 +1873,9 @@ def _check_unnecessary_dict_index_lookup( # Ignore this subscript if it is the target of an assignment # Early termination; after reassignment dict index lookup will be necessary return + if isinstance(subscript.parent, nodes.Delete): + # Ignore this subscript if it's used with the delete keyword + return # Case where .items is assigned to k,v (i.e., for k, v in d.items()) if isinstance(value, nodes.Name): diff --git a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py index 55dd5688c0..423360c822 100644 --- a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py +++ b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py @@ -82,3 +82,21 @@ class Foo: if 'V' in d[k]: # [unnecessary-dict-index-lookup] d[k] = "value" print(d[k]) # This is fine + +# Test false positive described in #4716 +# Should not be emitted for del +# (https://github.com/PyCQA/pylint/issues/4716) +d = {} +for key, val in d.items(): + del d[key] + break + +for item in d.items(): + del d[item[0]] + break + +outer_dict = {"inner_dict": {}} +for key, val in outer_dict.items(): + for key_two, val_two in val.items(): + del outer_dict[key][key_two] # [unnecessary-dict-index-lookup] + break diff --git a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt index ca43a16c04..09ad9b6571 100644 --- a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt +++ b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt @@ -21,3 +21,4 @@ unnecessary-dict-index-lookup:57:10:57:20::Unnecessary dictionary index lookup, unnecessary-dict-index-lookup:60:1:60:11::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED unnecessary-dict-index-lookup:65:10:65:20::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED unnecessary-dict-index-lookup:82:14:82:18::Unnecessary dictionary index lookup, use '_' instead:UNDEFINED +unnecessary-dict-index-lookup:101:12:101:27::Unnecessary dictionary index lookup, use 'val' instead:UNDEFINED From 17a16d79ebcca54284db516651a07f61bdf17bbc Mon Sep 17 00:00:00 2001 From: oittaa <8972248+oittaa@users.noreply.github.com> Date: Fri, 3 Dec 2021 20:14:07 +0100 Subject: [PATCH 014/357] Docker: Python 3.10 and Alpine 3.15 (#5443) * Docker: Python 3.10 and Alpine 3.15 --- CONTRIBUTORS.txt | 2 ++ Dockerfile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 72df6038c5..54e31c6179 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -587,3 +587,5 @@ contributors: * Allan Chandler (allanc65): contributor - Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params + +* Eero Vuojolahti: contributor diff --git a/Dockerfile b/Dockerfile index aafe62a6ae..2667145da8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9.0-alpine3.12 +FROM python:3.10.0-alpine3.15 COPY ./ /tmp/build WORKDIR /tmp/build From 82f0f88c1807eb94eeab2d7670bf930f1850dedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 3 Dec 2021 20:15:12 +0100 Subject: [PATCH 015/357] Add typing to ``PyLinter.reporter`` (#5325) * Add ``messages`` attribute to ``MultiReporter`` * Make ``PyLinter`` always have a ``reporter`` attribute Co-authored-by: Pierre Sassoulas --- ChangeLog | 3 +++ doc/whatsnew/2.13.rst | 3 +++ pylint/lint/pylinter.py | 29 +++++++++++++++++++---------- pylint/reporters/multi_reporter.py | 1 + tests/test_self.py | 7 +++++++ tests/unittest_reporting.py | 2 +- 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 02cd4eea7e..f946dbbc5b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -27,6 +27,9 @@ Release date: TBA Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) +* The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` + as its reporter if none is provided. + What's New in Pylint 2.12.2? ============================ diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 8222813d22..0713e343ae 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -34,3 +34,6 @@ Other Changes * Require Python ``3.6.2`` to run pylint. Closes #5065 + +* The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` + as its reporter if none is provided. diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index c95aa680ec..3617d91415 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -39,6 +39,7 @@ prepare_crash_report, ) from pylint.message import Message, MessageDefinition, MessageDefinitionStore +from pylint.reporters.text import TextReporter from pylint.reporters.ureports import nodes as report_nodes from pylint.typing import ( FileItem, @@ -517,13 +518,25 @@ def make_options(): ("Reports", "Options related to output formatting and reporting"), ) - def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None): + def __init__( + self, + options=(), + reporter=None, + option_groups=(), + pylintrc=None, + ): """Some stuff has to be done before ancestors initialization... messages store / checkers / reporter / astroid manager""" - self.msgs_store = MessageDefinitionStore() - self.reporter = None + # Attributes for reporters + self.reporter: Union[reporters.BaseReporter, reporters.MultiReporter] + if reporter: + self.set_reporter(reporter) + else: + self.set_reporter(TextReporter()) self._reporter_names = None self._reporters = {} + + self.msgs_store = MessageDefinitionStore() self._checkers = collections.defaultdict(list) self._pragma_lineno = {} self._ignore_file = False @@ -572,16 +585,10 @@ def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None): self._dynamic_plugins = set() self._error_mode = False self.load_provider_defaults() - if reporter: - self.set_reporter(reporter) def load_default_plugins(self): checkers.initialize(self) reporters.initialize(self) - # Make sure to load the default reporter, because - # the option has been set before the plugins had been loaded. - if not self.reporter: - self._load_reporters() def load_plugin_modules(self, modnames): """take a list of module names which are pylint plugins and load @@ -654,7 +661,9 @@ def _load_reporter_by_name(self, reporter_name: str) -> reporters.BaseReporter: else: return reporter_class() - def set_reporter(self, reporter): + def set_reporter( + self, reporter: Union[reporters.BaseReporter, reporters.MultiReporter] + ) -> None: """set the reporter used to display messages and reports""" self.reporter = reporter reporter.linter = self diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index f674a29e47..cbdc36962c 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -41,6 +41,7 @@ def __init__( self._path_strip_prefix = os.getcwd() + os.sep self._linter: Optional[PyLinter] = None self.out = output + self.messages: List[Message] = [] @property def out(self): diff --git a/tests/test_self.py b/tests/test_self.py index 41bf423ecf..ebdb914437 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1260,6 +1260,13 @@ def test_enable_all_extensions() -> None: ) assert sorted(plugins) == sorted(runner.linter._dynamic_plugins) + @staticmethod + def test_load_text_repoter_if_not_provided() -> None: + """Test if PyLinter.reporter is a TextReporter if no reporter is provided""" + linter = PyLinter() + + assert isinstance(linter.reporter, TextReporter) + @staticmethod def test_regex_paths_csv_validator() -> None: """Test to see if _regexp_paths_csv_validator works. diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 08bd2a3457..7579873592 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -190,11 +190,11 @@ def test_multi_format_output(tmp_path): with redirect_stdout(text): linter = PyLinter() + linter.load_default_plugins() linter.set_option("persistent", False) linter.set_option("output-format", formats) linter.set_option("reports", True) linter.set_option("score", True) - linter.load_default_plugins() assert linter.reporter.linter is linter with pytest.raises(NotImplementedError): From 3ed7a8e3933d14bc6d07afa8e6ba992b5a4b6926 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 3 Dec 2021 17:02:00 -0500 Subject: [PATCH 016/357] Fix #5371: Correctly count arguments to static methods missing @staticmethod decorator (#5412) * Fix #5371: Correctly count arguments to static methods missing @staticmethod decorator * Implementations of MapReduceMixin.reduce_map_data were actually not classmethods --- ChangeLog | 5 +++++ pylint/checkers/classes.py | 2 +- pylint/checkers/mapreduce_checker.py | 3 +-- tests/functional/a/arguments_differ.py | 12 ++++++++++-- tests/functional/a/arguments_differ.txt | 13 +++++++------ tests/functional/a/arguments_differ_issue5371.py | 13 +++++++++++++ tests/test_check_parallel.py | 3 ++- 7 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 tests/functional/a/arguments_differ_issue5371.py diff --git a/ChangeLog b/ChangeLog index f946dbbc5b..f77b7e8706 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,11 @@ Release date: TBA Closes #5323 +* Fixed detection of ``arguments-differ`` when superclass static + methods lacked a ``@staticmethod`` decorator. + + Closes #5371 + .. Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 2d63b8bbd0..4bbc45f20f 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -177,7 +177,7 @@ def _definition_equivalent_to_call(definition, call): def _positional_parameters(method): positional = method.args.args - if method.type in {"classmethod", "method"}: + if method.is_bound() and method.type in {"classmethod", "method"}: positional = positional[1:] return positional diff --git a/pylint/checkers/mapreduce_checker.py b/pylint/checkers/mapreduce_checker.py index cb76decf9b..9b90c634a6 100644 --- a/pylint/checkers/mapreduce_checker.py +++ b/pylint/checkers/mapreduce_checker.py @@ -14,7 +14,6 @@ class MapReduceMixin(metaclass=abc.ABCMeta): def get_map_data(self): """Returns mergable/reducible data that will be examined""" - @classmethod @abc.abstractmethod - def reduce_map_data(cls, linter, data): + def reduce_map_data(self, linter, data): """For a given Checker, receives data for all mapped runs""" diff --git a/tests/functional/a/arguments_differ.py b/tests/functional/a/arguments_differ.py index b5b43505a4..d6689d9202 100644 --- a/tests/functional/a/arguments_differ.py +++ b/tests/functional/a/arguments_differ.py @@ -141,7 +141,7 @@ def close(self, attr): class StaticmethodChild2(Staticmethod): - def func(self, data): + def func(self, data): # [arguments-differ] super().func(data) @@ -151,15 +151,23 @@ class SuperClass(object): def impl(arg1, arg2, **kwargs): return arg1 + arg2 + def should_have_been_decorated_as_static(arg1, arg2): # pylint: disable=no-self-argument + return arg1 + arg2 + class MyClass(SuperClass): - def impl(self, *args, **kwargs): + @staticmethod + def impl(*args, **kwargs): """ Acceptable use of vararg in subclass because it does not violate LSP. """ super().impl(*args, **kwargs) + @staticmethod + def should_have_been_decorated_as_static(arg1, arg2): + return arg1 + arg2 + class FirstHasArgs(object): diff --git a/tests/functional/a/arguments_differ.txt b/tests/functional/a/arguments_differ.txt index ea0b74dfaf..5cce378e27 100644 --- a/tests/functional/a/arguments_differ.txt +++ b/tests/functional/a/arguments_differ.txt @@ -3,9 +3,10 @@ arguments-differ:23:4:24:12:ChildDefaults.test:Number of parameters was 3 in 'Pa arguments-differ:41:4:42:12:ClassmethodChild.func:Number of parameters was 2 in 'Classmethod.func' and is now 0 in overridden 'ClassmethodChild.func' method:UNDEFINED arguments-differ:68:4:69:64:VarargsChild.has_kwargs:Variadics removed in overridden 'VarargsChild.has_kwargs' method:UNDEFINED arguments-renamed:71:4:72:89:VarargsChild.no_kwargs:Parameter 'args' has been renamed to 'arg' in overridden 'VarargsChild.no_kwargs' method:UNDEFINED -arguments-differ:172:4:173:12:SecondChangesArgs.test:Number of parameters was 2 in 'FirstHasArgs.test' and is now 4 in overridden 'SecondChangesArgs.test' method:UNDEFINED -arguments-differ:298:4:299:60:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method:UNDEFINED -arguments-differ:301:4:302:82:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method:UNDEFINED -arguments-differ:304:4:305:32:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method:UNDEFINED -arguments-differ:307:4:308:32:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method:UNDEFINED -arguments-differ:310:4:311:41:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method:UNDEFINED +arguments-differ:144:4:145:26:StaticmethodChild2.func:Number of parameters was 1 in 'Staticmethod.func' and is now 2 in overridden 'StaticmethodChild2.func' method:UNDEFINED +arguments-differ:180:4:181:12:SecondChangesArgs.test:Number of parameters was 2 in 'FirstHasArgs.test' and is now 4 in overridden 'SecondChangesArgs.test' method:UNDEFINED +arguments-differ:306:4:307:60:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method:UNDEFINED +arguments-differ:309:4:310:82:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method:UNDEFINED +arguments-differ:312:4:313:32:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method:UNDEFINED +arguments-differ:315:4:316:32:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method:UNDEFINED +arguments-differ:318:4:319:41:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method:UNDEFINED diff --git a/tests/functional/a/arguments_differ_issue5371.py b/tests/functional/a/arguments_differ_issue5371.py new file mode 100644 index 0000000000..a0cd396138 --- /dev/null +++ b/tests/functional/a/arguments_differ_issue5371.py @@ -0,0 +1,13 @@ +"""https://github.com/PyCQA/pylint/issues/5371""" +from enum import Enum + + +class MyEnum(Enum): + """ + Enum._generate_next_value_() in the stdlib currently lacks a + @staticmethod decorator. + """ + + @staticmethod + def _generate_next_value_(name: str, start: int, count: int, last_values: list): + return 42 diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index df493ccb28..d1b5a64893 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -20,6 +20,7 @@ import pylint.interfaces import pylint.lint.parallel from pylint.checkers.base_checker import BaseChecker +from pylint.checkers.mapreduce_checker import MapReduceMixin from pylint.lint import PyLinter from pylint.lint.parallel import _worker_check_single_file as worker_check_single_file from pylint.lint.parallel import _worker_initialize as worker_initialize @@ -73,7 +74,7 @@ def process_module(self, _node: nodes.Module) -> None: self.data.append(record) -class ParallelTestChecker(BaseChecker): +class ParallelTestChecker(BaseChecker, MapReduceMixin): """A checker that does need to consolidate data. To simulate the need to consolidate data, this checker only From 15b1e5a9374fb51d94d578d2f27c3176d396cfb7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 4 Dec 2021 07:38:15 -0500 Subject: [PATCH 017/357] Add tempfile.TemporaryFile to CALLS_RETURNING_CONTEXT_MANAGERS --- pylint/checkers/refactoring/refactoring_checker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 9f28d510b4..7bd82d1f91 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -36,6 +36,7 @@ "tempfile.NamedTemporaryFile", "tempfile.SpooledTemporaryFile", "tempfile.TemporaryDirectory", + "tempfile.TemporaryFile", "zipfile.ZipFile", "zipfile.PyZipFile", "zipfile.ZipFile.open", From 7873b774b3dfb3183b630406d37bdb7044280ca9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 5 Dec 2021 09:12:14 +0100 Subject: [PATCH 018/357] Revert renaming extension tests (#5473) * Split py_version_35 test --- ...xpr.py => cs_consider_using_assignment_expr.py} | 0 ...xpr.rc => cs_consider_using_assignment_expr.rc} | 0 ...r.txt => cs_consider_using_assignment_expr.txt} | 0 ...> cs_consider_using_namedtuple_or_dataclass.py} | 0 ...> cs_consider_using_namedtuple_or_dataclass.rc} | 0 ... cs_consider_using_namedtuple_or_dataclass.txt} | 0 ...r_using_tuple.py => cs_consider_using_tuple.py} | 0 ...r_using_tuple.rc => cs_consider_using_tuple.rc} | 0 ...using_tuple.txt => cs_consider_using_tuple.txt} | 0 .../functional/ext/code_style/cs_py_version_35.py | 7 +++++++ .../{py_version_35.rc => cs_py_version_35.rc} | 0 tests/functional/ext/code_style/py_version_35.py | 14 -------------- ...ing_alias.py => typing_consider_using_alias.py} | 0 ...ing_alias.rc => typing_consider_using_alias.rc} | 0 ...g_alias.txt => typing_consider_using_alias.txt} | 0 ... typing_consider_using_alias_without_future.py} | 0 ... typing_consider_using_alias_without_future.rc} | 0 ...typing_consider_using_alias_without_future.txt} | 0 ...ing_union.py => typing_consider_using_union.py} | 0 ...ing_union.rc => typing_consider_using_union.rc} | 0 ...g_union.txt => typing_consider_using_union.txt} | 0 ...310.py => typing_consider_using_union_py310.py} | 0 ...310.rc => typing_consider_using_union_py310.rc} | 0 ...0.txt => typing_consider_using_union_py310.txt} | 0 ... typing_consider_using_union_without_future.py} | 0 ... typing_consider_using_union_without_future.rc} | 0 ...typing_consider_using_union_without_future.txt} | 0 ...recated_alias.py => typing_deprecated_alias.py} | 0 ...recated_alias.rc => typing_deprecated_alias.rc} | 0 ...cated_alias.txt => typing_deprecated_alias.txt} | 0 tests/functional/p/py_version_35.py | 5 +++++ tests/functional/p/py_version_35.rc | 2 ++ 32 files changed, 14 insertions(+), 14 deletions(-) rename tests/functional/ext/code_style/{consider_using_assignment_expr.py => cs_consider_using_assignment_expr.py} (100%) rename tests/functional/ext/code_style/{consider_using_assignment_expr.rc => cs_consider_using_assignment_expr.rc} (100%) rename tests/functional/ext/code_style/{consider_using_assignment_expr.txt => cs_consider_using_assignment_expr.txt} (100%) rename tests/functional/ext/code_style/{consider_using_namedtuple_or_dataclass.py => cs_consider_using_namedtuple_or_dataclass.py} (100%) rename tests/functional/ext/code_style/{consider_using_namedtuple_or_dataclass.rc => cs_consider_using_namedtuple_or_dataclass.rc} (100%) rename tests/functional/ext/code_style/{consider_using_namedtuple_or_dataclass.txt => cs_consider_using_namedtuple_or_dataclass.txt} (100%) rename tests/functional/ext/code_style/{consider_using_tuple.py => cs_consider_using_tuple.py} (100%) rename tests/functional/ext/code_style/{consider_using_tuple.rc => cs_consider_using_tuple.rc} (100%) rename tests/functional/ext/code_style/{consider_using_tuple.txt => cs_consider_using_tuple.txt} (100%) create mode 100644 tests/functional/ext/code_style/cs_py_version_35.py rename tests/functional/ext/code_style/{py_version_35.rc => cs_py_version_35.rc} (100%) delete mode 100644 tests/functional/ext/code_style/py_version_35.py rename tests/functional/ext/typing/{consider_using_alias.py => typing_consider_using_alias.py} (100%) rename tests/functional/ext/typing/{consider_using_alias.rc => typing_consider_using_alias.rc} (100%) rename tests/functional/ext/typing/{consider_using_alias.txt => typing_consider_using_alias.txt} (100%) rename tests/functional/ext/typing/{consider_using_alias_without_future.py => typing_consider_using_alias_without_future.py} (100%) rename tests/functional/ext/typing/{consider_using_alias_without_future.rc => typing_consider_using_alias_without_future.rc} (100%) rename tests/functional/ext/typing/{consider_using_alias_without_future.txt => typing_consider_using_alias_without_future.txt} (100%) rename tests/functional/ext/typing/{consider_using_union.py => typing_consider_using_union.py} (100%) rename tests/functional/ext/typing/{consider_using_union.rc => typing_consider_using_union.rc} (100%) rename tests/functional/ext/typing/{consider_using_union.txt => typing_consider_using_union.txt} (100%) rename tests/functional/ext/typing/{consider_using_union_py310.py => typing_consider_using_union_py310.py} (100%) rename tests/functional/ext/typing/{consider_using_union_py310.rc => typing_consider_using_union_py310.rc} (100%) rename tests/functional/ext/typing/{consider_using_union_py310.txt => typing_consider_using_union_py310.txt} (100%) rename tests/functional/ext/typing/{consider_using_union_without_future.py => typing_consider_using_union_without_future.py} (100%) rename tests/functional/ext/typing/{consider_using_union_without_future.rc => typing_consider_using_union_without_future.rc} (100%) rename tests/functional/ext/typing/{consider_using_union_without_future.txt => typing_consider_using_union_without_future.txt} (100%) rename tests/functional/ext/typing/{deprecated_alias.py => typing_deprecated_alias.py} (100%) rename tests/functional/ext/typing/{deprecated_alias.rc => typing_deprecated_alias.rc} (100%) rename tests/functional/ext/typing/{deprecated_alias.txt => typing_deprecated_alias.txt} (100%) create mode 100644 tests/functional/p/py_version_35.py create mode 100644 tests/functional/p/py_version_35.rc diff --git a/tests/functional/ext/code_style/consider_using_assignment_expr.py b/tests/functional/ext/code_style/cs_consider_using_assignment_expr.py similarity index 100% rename from tests/functional/ext/code_style/consider_using_assignment_expr.py rename to tests/functional/ext/code_style/cs_consider_using_assignment_expr.py diff --git a/tests/functional/ext/code_style/consider_using_assignment_expr.rc b/tests/functional/ext/code_style/cs_consider_using_assignment_expr.rc similarity index 100% rename from tests/functional/ext/code_style/consider_using_assignment_expr.rc rename to tests/functional/ext/code_style/cs_consider_using_assignment_expr.rc diff --git a/tests/functional/ext/code_style/consider_using_assignment_expr.txt b/tests/functional/ext/code_style/cs_consider_using_assignment_expr.txt similarity index 100% rename from tests/functional/ext/code_style/consider_using_assignment_expr.txt rename to tests/functional/ext/code_style/cs_consider_using_assignment_expr.txt diff --git a/tests/functional/ext/code_style/consider_using_namedtuple_or_dataclass.py b/tests/functional/ext/code_style/cs_consider_using_namedtuple_or_dataclass.py similarity index 100% rename from tests/functional/ext/code_style/consider_using_namedtuple_or_dataclass.py rename to tests/functional/ext/code_style/cs_consider_using_namedtuple_or_dataclass.py diff --git a/tests/functional/ext/code_style/consider_using_namedtuple_or_dataclass.rc b/tests/functional/ext/code_style/cs_consider_using_namedtuple_or_dataclass.rc similarity index 100% rename from tests/functional/ext/code_style/consider_using_namedtuple_or_dataclass.rc rename to tests/functional/ext/code_style/cs_consider_using_namedtuple_or_dataclass.rc diff --git a/tests/functional/ext/code_style/consider_using_namedtuple_or_dataclass.txt b/tests/functional/ext/code_style/cs_consider_using_namedtuple_or_dataclass.txt similarity index 100% rename from tests/functional/ext/code_style/consider_using_namedtuple_or_dataclass.txt rename to tests/functional/ext/code_style/cs_consider_using_namedtuple_or_dataclass.txt diff --git a/tests/functional/ext/code_style/consider_using_tuple.py b/tests/functional/ext/code_style/cs_consider_using_tuple.py similarity index 100% rename from tests/functional/ext/code_style/consider_using_tuple.py rename to tests/functional/ext/code_style/cs_consider_using_tuple.py diff --git a/tests/functional/ext/code_style/consider_using_tuple.rc b/tests/functional/ext/code_style/cs_consider_using_tuple.rc similarity index 100% rename from tests/functional/ext/code_style/consider_using_tuple.rc rename to tests/functional/ext/code_style/cs_consider_using_tuple.rc diff --git a/tests/functional/ext/code_style/consider_using_tuple.txt b/tests/functional/ext/code_style/cs_consider_using_tuple.txt similarity index 100% rename from tests/functional/ext/code_style/consider_using_tuple.txt rename to tests/functional/ext/code_style/cs_consider_using_tuple.txt diff --git a/tests/functional/ext/code_style/cs_py_version_35.py b/tests/functional/ext/code_style/cs_py_version_35.py new file mode 100644 index 0000000000..80f75b6544 --- /dev/null +++ b/tests/functional/ext/code_style/cs_py_version_35.py @@ -0,0 +1,7 @@ +"""No warnings should be emitted for features that require Python > 3.5""" +# pylint: disable=invalid-name + +# consider-using-assignment-expr -> requires Python 3.8 +a1 = 2 +if a1: + ... diff --git a/tests/functional/ext/code_style/py_version_35.rc b/tests/functional/ext/code_style/cs_py_version_35.rc similarity index 100% rename from tests/functional/ext/code_style/py_version_35.rc rename to tests/functional/ext/code_style/cs_py_version_35.rc diff --git a/tests/functional/ext/code_style/py_version_35.py b/tests/functional/ext/code_style/py_version_35.py deleted file mode 100644 index ee880c54a1..0000000000 --- a/tests/functional/ext/code_style/py_version_35.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Test warnings aren't emitted for features that require Python > 3.5""" -# pylint: disable=invalid-name - -# consider-using-f-string -> requires Python 3.6 -"Hello {}".format("World") - - -# ------ -# CodeStyle extension - -# consider-using-assignment-expr -> requires Python 3.8 -a1 = 2 -if a1: - ... diff --git a/tests/functional/ext/typing/consider_using_alias.py b/tests/functional/ext/typing/typing_consider_using_alias.py similarity index 100% rename from tests/functional/ext/typing/consider_using_alias.py rename to tests/functional/ext/typing/typing_consider_using_alias.py diff --git a/tests/functional/ext/typing/consider_using_alias.rc b/tests/functional/ext/typing/typing_consider_using_alias.rc similarity index 100% rename from tests/functional/ext/typing/consider_using_alias.rc rename to tests/functional/ext/typing/typing_consider_using_alias.rc diff --git a/tests/functional/ext/typing/consider_using_alias.txt b/tests/functional/ext/typing/typing_consider_using_alias.txt similarity index 100% rename from tests/functional/ext/typing/consider_using_alias.txt rename to tests/functional/ext/typing/typing_consider_using_alias.txt diff --git a/tests/functional/ext/typing/consider_using_alias_without_future.py b/tests/functional/ext/typing/typing_consider_using_alias_without_future.py similarity index 100% rename from tests/functional/ext/typing/consider_using_alias_without_future.py rename to tests/functional/ext/typing/typing_consider_using_alias_without_future.py diff --git a/tests/functional/ext/typing/consider_using_alias_without_future.rc b/tests/functional/ext/typing/typing_consider_using_alias_without_future.rc similarity index 100% rename from tests/functional/ext/typing/consider_using_alias_without_future.rc rename to tests/functional/ext/typing/typing_consider_using_alias_without_future.rc diff --git a/tests/functional/ext/typing/consider_using_alias_without_future.txt b/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt similarity index 100% rename from tests/functional/ext/typing/consider_using_alias_without_future.txt rename to tests/functional/ext/typing/typing_consider_using_alias_without_future.txt diff --git a/tests/functional/ext/typing/consider_using_union.py b/tests/functional/ext/typing/typing_consider_using_union.py similarity index 100% rename from tests/functional/ext/typing/consider_using_union.py rename to tests/functional/ext/typing/typing_consider_using_union.py diff --git a/tests/functional/ext/typing/consider_using_union.rc b/tests/functional/ext/typing/typing_consider_using_union.rc similarity index 100% rename from tests/functional/ext/typing/consider_using_union.rc rename to tests/functional/ext/typing/typing_consider_using_union.rc diff --git a/tests/functional/ext/typing/consider_using_union.txt b/tests/functional/ext/typing/typing_consider_using_union.txt similarity index 100% rename from tests/functional/ext/typing/consider_using_union.txt rename to tests/functional/ext/typing/typing_consider_using_union.txt diff --git a/tests/functional/ext/typing/consider_using_union_py310.py b/tests/functional/ext/typing/typing_consider_using_union_py310.py similarity index 100% rename from tests/functional/ext/typing/consider_using_union_py310.py rename to tests/functional/ext/typing/typing_consider_using_union_py310.py diff --git a/tests/functional/ext/typing/consider_using_union_py310.rc b/tests/functional/ext/typing/typing_consider_using_union_py310.rc similarity index 100% rename from tests/functional/ext/typing/consider_using_union_py310.rc rename to tests/functional/ext/typing/typing_consider_using_union_py310.rc diff --git a/tests/functional/ext/typing/consider_using_union_py310.txt b/tests/functional/ext/typing/typing_consider_using_union_py310.txt similarity index 100% rename from tests/functional/ext/typing/consider_using_union_py310.txt rename to tests/functional/ext/typing/typing_consider_using_union_py310.txt diff --git a/tests/functional/ext/typing/consider_using_union_without_future.py b/tests/functional/ext/typing/typing_consider_using_union_without_future.py similarity index 100% rename from tests/functional/ext/typing/consider_using_union_without_future.py rename to tests/functional/ext/typing/typing_consider_using_union_without_future.py diff --git a/tests/functional/ext/typing/consider_using_union_without_future.rc b/tests/functional/ext/typing/typing_consider_using_union_without_future.rc similarity index 100% rename from tests/functional/ext/typing/consider_using_union_without_future.rc rename to tests/functional/ext/typing/typing_consider_using_union_without_future.rc diff --git a/tests/functional/ext/typing/consider_using_union_without_future.txt b/tests/functional/ext/typing/typing_consider_using_union_without_future.txt similarity index 100% rename from tests/functional/ext/typing/consider_using_union_without_future.txt rename to tests/functional/ext/typing/typing_consider_using_union_without_future.txt diff --git a/tests/functional/ext/typing/deprecated_alias.py b/tests/functional/ext/typing/typing_deprecated_alias.py similarity index 100% rename from tests/functional/ext/typing/deprecated_alias.py rename to tests/functional/ext/typing/typing_deprecated_alias.py diff --git a/tests/functional/ext/typing/deprecated_alias.rc b/tests/functional/ext/typing/typing_deprecated_alias.rc similarity index 100% rename from tests/functional/ext/typing/deprecated_alias.rc rename to tests/functional/ext/typing/typing_deprecated_alias.rc diff --git a/tests/functional/ext/typing/deprecated_alias.txt b/tests/functional/ext/typing/typing_deprecated_alias.txt similarity index 100% rename from tests/functional/ext/typing/deprecated_alias.txt rename to tests/functional/ext/typing/typing_deprecated_alias.txt diff --git a/tests/functional/p/py_version_35.py b/tests/functional/p/py_version_35.py new file mode 100644 index 0000000000..2772501831 --- /dev/null +++ b/tests/functional/p/py_version_35.py @@ -0,0 +1,5 @@ +"""No warnings should be emitted for features that require Python > 3.5""" +# pylint: disable=invalid-name + +# consider-using-f-string -> requires Python 3.6 +"Hello {}".format("World") diff --git a/tests/functional/p/py_version_35.rc b/tests/functional/p/py_version_35.rc new file mode 100644 index 0000000000..2d8d651971 --- /dev/null +++ b/tests/functional/p/py_version_35.rc @@ -0,0 +1,2 @@ +[MASTER] +py-version=3.5 From a7302dbe6b58209388f88fdfeae41736e0986136 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Sun, 5 Dec 2021 13:07:14 +0100 Subject: [PATCH 019/357] Treat `typing.NamedTuple` self as a sequence (#5346) * Treat `typing.NamedTuple` self as a sequence Closes #5312 * Support emmitting `unbalanced-tuple-unpacking` with `typing.NamedTuple` * Add typehint and default to `None` * Use Marc's suggested improvements --- ChangeLog | 4 ++++ pylint/checkers/variables.py | 10 +++++++- .../u/unbalanced_tuple_unpacking.py | 24 ++++++++++++++++++- .../u/unbalanced_tuple_unpacking.txt | 12 ++++++---- tests/functional/u/unpacking_non_sequence.py | 10 ++++++++ tests/functional/u/unpacking_non_sequence.txt | 20 ++++++++-------- 6 files changed, 63 insertions(+), 17 deletions(-) diff --git a/ChangeLog b/ChangeLog index f77b7e8706..8ddac4258f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,10 @@ Release date: TBA Closes #4716 +* Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. + + Closes #5312 + * Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables or ``not in`` checks diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index f5605260a4..f2bba355c8 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2130,9 +2130,17 @@ def _check_unpacking(self, inferred, node, targets): ): # Variable-length argument, we can't determine the length. return + + # Attempt to check unpacking is properly balanced + values: Optional[List] = None if isinstance(inferred, (nodes.Tuple, nodes.List)): - # attempt to check unpacking is properly balanced values = inferred.itered() + elif isinstance(inferred, astroid.Instance) and any( + ancestor.qname() == "typing.NamedTuple" for ancestor in inferred.ancestors() + ): + values = [i for i in inferred.values() if isinstance(i, nodes.AssignName)] + + if values: if len(targets) != len(values): # Check if we have starred nodes. if any(isinstance(target, nodes.Starred) for target in targets): diff --git a/tests/functional/u/unbalanced_tuple_unpacking.py b/tests/functional/u/unbalanced_tuple_unpacking.py index ed807c0d7b..4deb6ce37a 100644 --- a/tests/functional/u/unbalanced_tuple_unpacking.py +++ b/tests/functional/u/unbalanced_tuple_unpacking.py @@ -1,8 +1,9 @@ """Check possible unbalanced tuple unpacking """ from __future__ import absolute_import +from typing import NamedTuple from functional.u.unpacking import unpack -# pylint: disable=using-constant-test, useless-object-inheritance,import-outside-toplevel +# pylint: disable=missing-class-docstring, missing-function-docstring, using-constant-test, useless-object-inheritance,import-outside-toplevel def do_stuff(): """This is not right.""" @@ -106,3 +107,24 @@ def test_issue_559(): from ctypes import c_int root_x, root_y, win_x, win_y = [c_int()] * 4 return root_x, root_y, win_x, win_y + + +class MyClass(NamedTuple): + first: float + second: float + third: float = 1.0 + + def my_sum(self): + """Unpack 3 variables""" + first, second, third = self + return first + second + third + + def sum_unpack_3_into_4(self): + """Attempt to unpack 3 variables into 4""" + first, second, third, fourth = self # [unbalanced-tuple-unpacking] + return first + second + third + fourth + + def sum_unpack_3_into_2(self): + """Attempt to unpack 3 variables into 2""" + first, second = self # [unbalanced-tuple-unpacking] + return first + second diff --git a/tests/functional/u/unbalanced_tuple_unpacking.txt b/tests/functional/u/unbalanced_tuple_unpacking.txt index 20aa5f40f8..c64c1c2ad1 100644 --- a/tests/functional/u/unbalanced_tuple_unpacking.txt +++ b/tests/functional/u/unbalanced_tuple_unpacking.txt @@ -1,5 +1,7 @@ -unbalanced-tuple-unpacking:9:4:9:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:14:4:14:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:19:4:19:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:69:4:69:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:81:8:81:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:10:4:10:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:15:4:15:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:20:4:20:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:70:4:70:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:82:8:82:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:124:8:124:43:MyClass.sum_unpack_3_into_4:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 4 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:129:8:129:28:MyClass.sum_unpack_3_into_2:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 2 label(s), right side has 3 value(s)":UNDEFINED diff --git a/tests/functional/u/unpacking_non_sequence.py b/tests/functional/u/unpacking_non_sequence.py index b7ea2189c6..e9c23b388d 100644 --- a/tests/functional/u/unpacking_non_sequence.py +++ b/tests/functional/u/unpacking_non_sequence.py @@ -4,6 +4,7 @@ # pylint: disable=using-constant-test, no-init, missing-docstring, wrong-import-order,wrong-import-position,no-else-return, useless-object-inheritance from os import rename as nonseq_func from functional.u.unpacking import nonseq +from typing import NamedTuple __revision__ = 0 @@ -137,3 +138,12 @@ def flow_control_unpacking(var=None): var0, var1 = var return var0, var1 return None + + +class MyClass(NamedTuple): + x: float + y: float + + def sum(self): + x, y = self + return x + y diff --git a/tests/functional/u/unpacking_non_sequence.txt b/tests/functional/u/unpacking_non_sequence.txt index 7a8b8a85c4..d657af1776 100644 --- a/tests/functional/u/unpacking_non_sequence.txt +++ b/tests/functional/u/unpacking_non_sequence.txt @@ -1,10 +1,10 @@ -unpacking-non-sequence:77:0:77:15::Attempting to unpack a non-sequence defined at line 74:UNDEFINED -unpacking-non-sequence:78:0:78:17::Attempting to unpack a non-sequence:UNDEFINED -unpacking-non-sequence:79:0:79:11::Attempting to unpack a non-sequence None:UNDEFINED -unpacking-non-sequence:80:0:80:8::Attempting to unpack a non-sequence 1:UNDEFINED -unpacking-non-sequence:81:0:81:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED -unpacking-non-sequence:82:0:82:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED -unpacking-non-sequence:83:0:83:18::Attempting to unpack a non-sequence:UNDEFINED -unpacking-non-sequence:98:8:98:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 74:UNDEFINED -unpacking-non-sequence:99:8:99:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED -unpacking-non-sequence:100:8:100:31:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED +unpacking-non-sequence:78:0:78:15::Attempting to unpack a non-sequence defined at line 75:UNDEFINED +unpacking-non-sequence:79:0:79:17::Attempting to unpack a non-sequence:UNDEFINED +unpacking-non-sequence:80:0:80:11::Attempting to unpack a non-sequence None:UNDEFINED +unpacking-non-sequence:81:0:81:8::Attempting to unpack a non-sequence 1:UNDEFINED +unpacking-non-sequence:82:0:82:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED +unpacking-non-sequence:83:0:83:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED +unpacking-non-sequence:84:0:84:18::Attempting to unpack a non-sequence:UNDEFINED +unpacking-non-sequence:99:8:99:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 75:UNDEFINED +unpacking-non-sequence:100:8:100:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED +unpacking-non-sequence:101:8:101:31:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED From 0624eb574c830646895630bc279caabcf7dbab3d Mon Sep 17 00:00:00 2001 From: Antonio Quarta Date: Sun, 5 Dec 2021 19:35:38 +0100 Subject: [PATCH 020/357] fix deleting files for pyreverse tests (#5476) --- tests/pyreverse/test_writer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index bbfd364111..b436b65e9b 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -150,7 +150,13 @@ def _setup( writer.write(dd) yield for fname in ( - DOT_FILES + COLORIZED_DOT_FILES + VCG_FILES + PUML_FILES + COLORIZED_PUML_FILES + DOT_FILES + + COLORIZED_DOT_FILES + + VCG_FILES + + PUML_FILES + + COLORIZED_PUML_FILES + + MMD_FILES + + HTML_FILES ): try: os.remove(fname) From d99e44c02cb984ffd137715a7126cc03719ef2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 14:32:52 +0100 Subject: [PATCH 021/357] Update pre-commit requirement from ~=2.15 to ~=2.16 (#5482) Updates the requirements on [pre-commit](https://github.com/pre-commit/pre-commit) to permit the latest version. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.15.0...v2.16.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 3afe3a5d97..5041a1d3b1 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -2,7 +2,7 @@ -r requirements_test_min.txt coveralls~=3.3 coverage~=6.2 -pre-commit~=2.15;python_full_version>="3.6.2" +pre-commit~=2.16;python_full_version>="3.6.2" tbump~=6.6.0 pyenchant~=3.2 pytest-cov~=3.0 From f6946780b9b2f45fe3b8272ab4d3e27ca5aa60f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 14:33:17 +0100 Subject: [PATCH 022/357] Bump black from 21.11b1 to 21.12b0 (#5481) Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test_pre_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 4a380d6e8d..217002d4d6 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,6 +1,6 @@ # Everything in this file should reflect the pre-commit configuration # in .pre-commit-config.yaml -black==21.11b1 +black==21.12b0 flake8==4.0.1 flake8-typing-imports==1.11.0 isort==5.10.1 From 71444989a93a6979fd7bd1eab4b6667180e528ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 23:36:22 +0100 Subject: [PATCH 023/357] [pre-commit.ci] pre-commit autoupdate (#5487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 21.11b1 → 21.12b0](https://github.com/psf/black/compare/21.11b1...21.12b0) - [github.com/pre-commit/mirrors-prettier: v2.5.0 → v2.5.1](https://github.com/pre-commit/mirrors-prettier/compare/v2.5.0...v2.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 429c04b33d..7d5596eedb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 21.11b1 + rev: 21.12b0 hooks: - id: black args: [--safe, --quiet] @@ -90,7 +90,7 @@ repos: ["platformdirs==2.2.0", "types-pkg_resources==0.1.3", "types-toml==0.1.3"] exclude: tests/functional/|tests/input|tests(/.*)*/data|tests/regrtest_data/|tests/data/|tests(/.*)+/conftest.py|doc/|bin/ - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.5.0 + rev: v2.5.1 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] From 7523ad1c6710ff00ed2d500ed5d9b80c6d9007a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 7 Dec 2021 16:13:55 +0100 Subject: [PATCH 024/357] Move tests for Google docstrings from ``TestParamDocChecker`` to functional tests (#5484) Co-authored-by: Pierre Sassoulas --- tests/extensions/test_check_docs.py | 836 +----------------- .../missing_param_doc_required_Google.py | 393 +++++++- .../missing_param_doc_required_Google.rc | 1 + .../missing_param_doc_required_Google.txt | 26 + .../raise/missing_raises_doc_Google.py | 56 +- .../raise/missing_raises_doc_Google.txt | 5 + .../return/missing_return_doc_Google.py | 100 ++- .../return/missing_return_doc_Google.txt | 10 +- .../missing_return_doc_required_Google.py | 36 +- .../missing_return_doc_required_Google.txt | 5 + 10 files changed, 639 insertions(+), 829 deletions(-) create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt diff --git a/tests/extensions/test_check_docs.py b/tests/extensions/test_check_docs.py index 0f7f6c252b..a5293b821d 100644 --- a/tests/extensions/test_check_docs.py +++ b/tests/extensions/test_check_docs.py @@ -48,203 +48,6 @@ class TestParamDocChecker(CheckerTestCase): "docstring_min_length": -1, } - def test_missing_func_params_in_google_docstring(self) -> None: - """Example of a function with missing Google style parameter - documentation in the docstring - """ - node = astroid.extract_node( - """ - def function_foo(x, y, z): - '''docstring ... - - Args: - x: bla - z (int): bar - - some other stuff - ''' - pass - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("y",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("x, y",)), - ): - self.checker.visit_functiondef(node) - - def test_missing_type_doc_google_docstring_exempt_kwonly_args(self) -> None: - node = astroid.extract_node( - """ - def identifier_kwarg_method(arg1: int, arg2: int, *, value1: str, value2: str): - '''Code to show failure in missing-type-doc - - Args: - arg1: First argument. - arg2: Second argument. - value1: First kwarg. - value2: Second kwarg. - ''' - print("NOTE: It doesn't like anything after the '*'.") - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_missing_func_params_with_annotations_in_google_docstring(self) -> None: - """Example of a function with missing Google style parameter - documentation in the docstring. - """ - node = astroid.extract_node( - """ - def function_foo(x: int, y: bool, z): - '''docstring ... - - Args: - x: bla - y: blah blah - z (int): bar - - some other stuff - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_default_arg_with_annotations_in_google_docstring(self) -> None: - """Example of a function with missing Google style parameter - documentation in the docstring. - """ - node = astroid.extract_node( - """ - def function_foo(x: int, y: bool, z: int = 786): - '''docstring ... - - Args: - x: bla - y: blah blah - z: bar - - some other stuff - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_missing_func_params_with_partial_annotations_in_google_docstring( - self, - ) -> None: - """Example of a function with missing Google style parameter - documentation in the docstring. - """ - node = astroid.extract_node( - """ - def function_foo(x, y: bool, z): - '''docstring ... - - Args: - x: bla - y: blah blah - z (int): bar - - some other stuff - ''' - pass - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-type-doc", node=node, args=("x",)) - ): - self.checker.visit_functiondef(node) - - def test_non_builtin_annotations_in_google_docstring(self) -> None: - """Example of a function with missing Google style parameter - documentation in the docstring. - """ - node = astroid.extract_node( - """ - def area(bottomleft: Point, topright: Point) -> float: - '''Calculate area of fake rectangle. - Args: - bottomleft: bottom left point of rectangle - topright: top right point of rectangle - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_non_builtin_annotations_for_returntype_in_google_docstring(self) -> None: - """Example of a function with missing Google style parameter - documentation in the docstring. - """ - node = astroid.extract_node( - """ - def get_midpoint(bottomleft: Point, topright: Point) -> Point: - '''Calculate midpoint of fake rectangle. - Args: - bottomleft: bottom left point of rectangle - topright: top right point of rectangle - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_func_params_and_keyword_params_in_google_docstring(self) -> None: - """Example of a function with Google style parameter split - in Args and Keyword Args in the docstring - """ - node = astroid.extract_node( - """ - def my_func(this, other, that=True): - '''Prints this, other and that - - Args: - this (str): Printed first - other (int): Other args - - Keyword Args: - that (bool): Printed second - ''' - print(this, that, other) - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_func_params_and_wrong_keyword_params_in_google_docstring(self) -> None: - """Example of a function with Google style parameter split - in Args and Keyword Args in the docstring but with wrong keyword args - """ - node = astroid.extract_node( - """ - def my_func(this, other, that=True): - '''Prints this, other and that - - Args: - this (str): Printed first - other (int): Other args - - Keyword Args: - these (bool): Printed second - ''' - print(this, that, other) - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("that",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("that",)), - MessageTest(msg_id="differing-param-doc", node=node, args=("these",)), - MessageTest(msg_id="differing-type-doc", node=node, args=("these",)), - ): - self.checker.visit_functiondef(node) - def test_missing_func_params_in_numpy_docstring(self) -> None: """Example of a function with missing NumPy style parameter documentation in the docstring @@ -336,31 +139,6 @@ def _visit_methods_of_class(self, node: nodes.ClassDef) -> None: if isinstance(body_item, nodes.FunctionDef) and hasattr(body_item, "name"): self.checker.visit_functiondef(body_item) - def test_missing_method_params_in_google_docstring(self) -> None: - """Example of a class method with missing parameter documentation in - the Google style docstring - """ - node = astroid.extract_node( - """ - class Foo(object): - def method_foo(self, x, y): - '''docstring ... - - missing parameter documentation - - Args: - x: bla - ''' - pass - """ - ) - method_node = node.body[0] - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=method_node, args=("y",)), - MessageTest(msg_id="missing-type-doc", node=method_node, args=("x, y",)), - ): - self._visit_methods_of_class(node) - def test_missing_method_params_in_numpy_docstring(self) -> None: """Example of a class method with missing parameter documentation in the Numpy style docstring @@ -388,32 +166,6 @@ def method_foo(self, x, y): ): self._visit_methods_of_class(node) - def test_existing_func_params_in_google_docstring(self) -> None: - """Example of a function with correctly documented parameters and - return values (Google style) - """ - node = astroid.extract_node( - """ - def function_foo(xarg, yarg, zarg, warg): - '''function foo ... - - Args: - xarg (int): bla xarg - yarg (my.qualified.type): bla - bla yarg - - zarg (int): bla zarg - warg (my.qualified.type): bla warg - - Returns: - float: sum - ''' - return xarg + yarg - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_existing_func_params_in_numpy_docstring(self) -> None: """Example of a function with correctly documented parameters and return values (Numpy style) @@ -446,53 +198,6 @@ def function_foo(xarg, yarg, zarg, warg): with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_wrong_name_of_func_params_in_google_docstring(self) -> None: - """Example of functions with inconsistent parameter names in the - signature and in the Google style documentation - """ - node = astroid.extract_node( - """ - def function_foo(xarg, yarg, zarg): - '''function foo ... - - Args: - xarg1 (int): bla xarg - yarg (float): bla yarg - - zarg1 (str): bla zarg - ''' - return xarg + yarg - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("xarg, zarg",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("xarg, zarg",)), - MessageTest( - msg_id="differing-param-doc", node=node, args=("xarg1, zarg1",) - ), - MessageTest(msg_id="differing-type-doc", node=node, args=("xarg1, zarg1",)), - ): - self.checker.visit_functiondef(node) - - node = astroid.extract_node( - """ - def function_foo(xarg, yarg): - '''function foo ... - - Args: - yarg1 (float): bla yarg - - For the other parameters, see bla. - ''' - return xarg + yarg - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="differing-param-doc", node=node, args=("yarg1",)), - MessageTest(msg_id="differing-type-doc", node=node, args=("yarg1",)), - ): - self.checker.visit_functiondef(node) - def test_wrong_name_of_func_params_in_numpy_docstring(self) -> None: """Example of functions with inconsistent parameter names in the signature and in the Numpy style documentation @@ -546,27 +251,6 @@ def function_foo(xarg, yarg): ): self.checker.visit_functiondef(node) - def test_see_sentence_for_func_params_in_google_docstring(self) -> None: - """Example for the usage of "For the other parameters, see" to avoid - too many repetitions, e.g. in functions or methods adhering to a - given interface (Google style) - """ - node = astroid.extract_node( - """ - def function_foo(xarg, yarg): - '''function foo ... - - Args: - yarg (float): bla yarg - - For the other parameters, see :func:`bla` - ''' - return xarg + yarg - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_see_sentence_for_func_params_in_numpy_docstring(self) -> None: """Example for the usage of "For the other parameters, see" to avoid too many repetitions, e.g. in functions or methods adhering to a @@ -590,34 +274,6 @@ def function_foo(xarg, yarg): with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_constr_params_in_class_google(self) -> None: - """Example of a class with missing constructor parameter documentation - (Google style) - - Everything is completely analogous to functions. - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - '''docstring foo - - Args: - y: bla - - missing constructor parameter documentation - ''' - - def __init__(self, x, y): - pass - - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("x",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("x, y",)), - ): - self._visit_methods_of_class(node) - def test_constr_params_in_class_numpy(self) -> None: """Example of a class with missing constructor parameter documentation (Numpy style) @@ -673,36 +329,6 @@ def __init__(self, foo): with self.assertNoMessages(): self._visit_methods_of_class(node) - def test_constr_params_in_init_google(self) -> None: - """Example of a class with missing constructor parameter documentation - (Google style) - - Everything is completely analogous to functions. - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - def __init__(self, x, y): - '''docstring foo constructor - - Args: - y: bla - - missing constructor parameter documentation - ''' - pass - - """ - ) - constructor_node = node.body[0] - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=constructor_node, args=("x",)), - MessageTest( - msg_id="missing-type-doc", node=constructor_node, args=("x, y",) - ), - ): - self._visit_methods_of_class(node) - def test_constr_params_in_init_numpy(self) -> None: """Example of a class with missing constructor parameter documentation (Numpy style) @@ -773,50 +399,6 @@ def __init__(self, x, y): with self.assertNoMessages(): self._visit_methods_of_class(node) - def test_constr_params_in_class_and_init_google(self) -> None: - """Example of a class with missing constructor parameter documentation - in both the init docstring and the class docstring - (Google style) - - Everything is completely analogous to functions. - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - '''docstring foo - - Args: - y: bla - - missing constructor parameter documentation - ''' - - def __init__(self, x, y): - '''docstring foo - - Args: - y: bla - - missing constructor parameter documentation - ''' - pass - - """ - ) - constructor_node = node.body[0] - with self.assertAddsMessages( - MessageTest( - msg_id="multiple-constructor-doc", node=node, args=(node.name,) - ), - MessageTest(msg_id="missing-param-doc", node=node, args=("x",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("x, y",)), - MessageTest(msg_id="missing-param-doc", node=constructor_node, args=("x",)), - MessageTest( - msg_id="missing-type-doc", node=constructor_node, args=("x, y",) - ), - ): - self._visit_methods_of_class(node) - def test_constr_params_in_class_and_init_numpy(self) -> None: """Example of a class with missing constructor parameter documentation in both the init docstring and the class docstring @@ -884,48 +466,6 @@ def my_func(arg, *, kwonly, missing_kwonly): ): self.checker.visit_functiondef(node) - def test_warns_missing_args_google(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, *args): - """The docstring - - Args: - named_arg (object): Returned - - Returns: - object or None: Maybe named_arg - """ - if args: - return named_arg - ''' - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("*args",)) - ): - self.checker.visit_functiondef(node) - - def test_warns_missing_kwargs_google(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, **kwargs): - """The docstring - - Args: - named_arg (object): Returned - - Returns: - object or None: Maybe named_arg - """ - if kwargs: - return named_arg - ''' - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("**kwargs",)) - ): - self.checker.visit_functiondef(node) - def test_warns_missing_args_numpy(self) -> None: node = astroid.extract_node( ''' @@ -948,72 +488,32 @@ def my_func(named_arg, *args): ) with self.assertAddsMessages( MessageTest(msg_id="missing-param-doc", node=node, args=("*args",)) - ): - self.checker.visit_functiondef(node) - - def test_warns_missing_kwargs_numpy(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, **kwargs): - """The docstring - - Args - ---- - named_arg : object - Returned - - Returns - ------- - object or None - Maybe named_arg - """ - if kwargs: - return named_arg - ''' - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("**kwargs",)) - ): - self.checker.visit_functiondef(node) - - def test_finds_args_without_type_google(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, *args): - """The docstring - - Args: - named_arg (object): Returned - *args: Optional arguments - - Returns: - object or None: Maybe named_arg - """ - if args: - return named_arg - ''' - ) - with self.assertNoMessages(): + ): self.checker.visit_functiondef(node) - def test_finds_kwargs_without_type_google(self) -> None: + def test_warns_missing_kwargs_numpy(self) -> None: node = astroid.extract_node( ''' def my_func(named_arg, **kwargs): """The docstring - Args: - named_arg (object): Returned - **kwargs: Keyword arguments + Args + ---- + named_arg : object + Returned - Returns: - object or None: Maybe named_arg + Returns + ------- + object or None + Maybe named_arg """ if kwargs: return named_arg ''' ) - with self.assertNoMessages(): + with self.assertAddsMessages( + MessageTest(msg_id="missing-param-doc", node=node, args=("**kwargs",)) + ): self.checker.visit_functiondef(node) def test_finds_args_without_type_numpy(self) -> None: @@ -1047,26 +547,6 @@ def my_func(named_arg, typed_arg: bool, untyped_arg, *args): ): self.checker.visit_functiondef(node) - def test_finds_args_with_xref_type_google(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, **kwargs): - """The docstring - - Args: - named_arg (`example.value`): Returned - **kwargs: Keyword arguments - - Returns: - `example.value`: Maybe named_arg - """ - if kwargs: - return named_arg - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_finds_args_with_xref_type_numpy(self) -> None: node = astroid.extract_node( ''' @@ -1117,14 +597,11 @@ def my_func(named_arg, **kwargs): with self.assertNoMessages(): self.checker.visit_functiondef(node) - CONTAINER_TYPES = [ + COMPLEX_TYPES = [ "dict(str,str)", "dict[str,str]", "tuple(int)", "list[tokenize.TokenInfo]", - ] - - COMPLEX_TYPES = CONTAINER_TYPES + [ "dict(str, str)", "dict[str, str]", "int or str", @@ -1133,25 +610,6 @@ def my_func(named_arg, **kwargs): "tuple(int or str) or list(int or str)", ] - @pytest.mark.parametrize("complex_type", COMPLEX_TYPES) - def test_finds_multiple_types_google(self, complex_type): - node = astroid.extract_node( - f''' - def my_func(named_arg): - """The docstring - - Args: - named_arg ({complex_type}): Returned - - Returns: - {complex_type}: named_arg - """ - return named_arg - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - @pytest.mark.parametrize("complex_type", COMPLEX_TYPES) def test_finds_multiple_types_numpy(self, complex_type): node = astroid.extract_node( @@ -1175,29 +633,6 @@ def my_func(named_arg): with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_ignores_optional_specifier_google(self) -> None: - node = astroid.extract_node( - ''' - def do_something(param1, param2, param3=(), param4=[], param5=[], param6=True): - """Do something. - - Args: - param1 (str): Description. - param2 (dict(str, int)): Description. - param3 (tuple(str), optional): Defaults to empty. Description. - param4 (List[str], optional): Defaults to empty. Description. - param5 (list[tuple(str)], optional): Defaults to empty. Description. - param6 (bool, optional): Defaults to True. Description. - - Returns: - int: Description. - """ - return param1, param2, param3, param4, param5, param6 - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_ignores_optional_specifier_numpy(self) -> None: node = astroid.extract_node( ''' @@ -1239,40 +674,6 @@ def do_something(): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_finds_missing_raises_from_setter_google(self) -> None: - """Example of a setter having missing raises documentation in - the Google style docstring of the property - """ - property_node, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): #@ - '''int: docstring - - Include a "Raises" section so that this is identified - as a Google docstring and not a Numpy docstring. - - Raises: - RuntimeError: Always - ''' - raise RuntimeError() - return 10 - - @foo.setter - def foo(self, value): - raise AttributeError() #@ - """ - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-raises-doc", - node=property_node, - args=("AttributeError",), - ) - ): - self.checker.visit_raise(node) - def test_finds_missing_raises_from_setter_numpy(self) -> None: """Example of a setter having missing raises documentation in the Numpy style docstring of the property @@ -1309,42 +710,6 @@ def foo(self, value): ): self.checker.visit_raise(node) - def test_finds_missing_raises_from_setter_google_2(self) -> None: - """Example of a setter having missing raises documentation in - its own Google style docstring of the property - """ - setter_node, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): - '''int: docstring ... - - Raises: - RuntimeError: Always - ''' - raise RuntimeError() - return 10 - - @foo.setter - def foo(self, value): #@ - '''setter docstring ... - - Raises: - RuntimeError: Never - ''' - if True: - raise AttributeError() #@ - raise RuntimeError() - """ - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-raises-doc", node=setter_node, args=("AttributeError",) - ) - ): - self.checker.visit_raise(node) - def test_finds_missing_raises_from_setter_numpy_2(self) -> None: """Example of a setter having missing raises documentation in its own Numpy style docstring of the property @@ -1385,27 +750,6 @@ def foo(self, value): #@ ): self.checker.visit_raise(node) - def test_finds_property_return_type_google(self) -> None: - """Example of a property having return documentation in - a Google style docstring - """ - node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): #@ - '''int: docstring ... - - Raises: - RuntimeError: Always - ''' - raise RuntimeError() - return 10 - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_finds_property_return_type_numpy(self) -> None: """Example of a property having return documentation in a numpy style docstring @@ -1429,51 +773,6 @@ def foo(self): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_finds_annotation_property_return_type_google(self) -> None: - """Example of a property having return documentation in - a Google style docstring - """ - _, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self) -> int: #@ - '''docstring ... - - Raises: - RuntimeError: Always - ''' - raise RuntimeError() - return 10 #@ - """ - ) - with self.assertNoMessages(): - self.checker.visit_return(node) - - @set_config(accept_no_return_doc="no") - def test_finds_missing_property_return_type_google(self) -> None: - """Example of a property having return documentation in - a Google style docstring - """ - property_node, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): #@ - '''docstring ... - - Raises: - RuntimeError: Always - ''' - raise RuntimeError() - return 10 #@ - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-return-type-doc", node=property_node) - ): - self.checker.visit_return(node) - @set_config(accept_no_return_doc="no") def test_finds_missing_property_return_type_numpy(self) -> None: """Example of a property having return documentation in @@ -1500,30 +799,6 @@ def foo(self): #@ ): self.checker.visit_return(node) - @set_config(accept_no_return_doc="no") - def test_ignores_non_property_return_type_google(self) -> None: - """Example of a class function trying to use `type` as return - documentation in a Google style docstring - """ - func_node, node = astroid.extract_node( - """ - class Foo(object): - def foo(self): #@ - '''int: docstring ... - - Raises: - RuntimeError: Always - ''' - raise RuntimeError() - return 10 #@ - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-return-doc", node=func_node), - MessageTest(msg_id="missing-return-type-doc", node=func_node), - ): - self.checker.visit_return(node) - @set_config(accept_no_return_doc="no") def test_ignores_non_property_return_type_numpy(self) -> None: """Example of a class function trying to use `type` as return @@ -1575,27 +850,6 @@ def foo(self) -> int: #@ ): self.checker.visit_return(node) - def test_ignores_return_in_abstract_method_google(self) -> None: - """Example of an abstract method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - import abc - class Foo(object): - @abc.abstractmethod - def foo(self): #@ - '''docstring ... - - Returns: - int: Ten - ''' - return 10 - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_ignores_return_in_abstract_method_numpy(self) -> None: """Example of an abstract method documenting the return type that an implementation should return. @@ -1619,25 +873,6 @@ def foo(self): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_ignores_return_in_abstract_method_google_2(self) -> None: - """Example of a method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - class Foo(object): - def foo(self, arg): #@ - '''docstring ... - - Args: - arg (int): An argument. - ''' - raise NotImplementedError() - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_ignores_return_in_abstract_method_numpy_2(self) -> None: """Example of a method documenting the return type that an implementation should return. @@ -1659,25 +894,6 @@ def foo(self, arg): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_ignores_ignored_argument_names_google(self) -> None: - """Example of a method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - class Foo(object): - def foo(self, arg, _): #@ - '''docstring ... - - Args: - arg (int): An argument. - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_ignores_ignored_argument_names_numpy(self) -> None: """Example of a method documenting the return type that an implementation should return. @@ -1699,30 +915,6 @@ def foo(self, arg, _): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_useless_docs_ignored_argument_names_google(self) -> None: - """Example of a method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - class Foo(object): - def foo(self, arg, _, _ignored): #@ - '''docstring ... - - Args: - arg (int): An argument. - _ (float): Another argument. - _ignored: Ignored argument. - ''' - pass - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="useless-type-doc", node=node, args=("_",)), - MessageTest(msg_id="useless-param-doc", node=node, args=("_, _ignored",)), - ): - self.checker.visit_functiondef(node) - def test_useless_docs_ignored_argument_names_numpy(self) -> None: """Example of a method documenting the return type that an implementation should return. diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py index 0e5bc8e4f3..6e291ce835 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py @@ -4,7 +4,10 @@ Styleguide: https://google.github.io/styleguide/pyguide.html#doc-function-args """ -# pylint: disable=invalid-name +# pylint: disable=invalid-name, unused-argument, undefined-variable +# pylint: disable=line-too-long, too-few-public-methods, missing-class-docstring +# pylint: disable=missing-function-docstring, function-redefined, inconsistent-return-statements +# pylint: disable=dangerous-default-value, too-many-arguments def test_multi_line_parameters(param: int) -> None: @@ -16,3 +19,391 @@ def test_multi_line_parameters(param: int) -> None: a description """ print(param) + + +def test_missing_func_params_in_google_docstring( # [missing-param-doc, missing-type-doc] + x, y, z +): + """Example of a function with missing Google style parameter + documentation in the docstring + + Args: + x: bla + z (int): bar + + some other stuff + """ + + +def test_missing_func_params_with_annotations_in_google_docstring(x: int, y: bool, z): + """Example of a function with missing Google style parameter + documentation in the docstring. + + Args: + x: bla + y: blah blah + z (int): bar + + some other stuff + """ + + +def test_missing_type_doc_google_docstring_exempt_kwonly_args( + arg1: int, arg2: int, *, value1: str, value2: str +): + """Code to show failure in missing-type-doc + + Args: + arg1: First argument. + arg2: Second argument. + value1: First kwarg. + value2: Second kwarg. + """ + print("NOTE: It doesn't like anything after the '*'.") + + +def test_default_arg_with_annotations_in_google_docstring( + x: int, y: bool, z: int = 786 +): + """Example of a function with missing Google style parameter + documentation in the docstring. + + Args: + x: bla + y: blah blah + z: bar + + some other stuff + """ + + +def test_missing_func_params_with_partial_annotations_in_google_docstring( # [missing-type-doc] + x, y: bool, z +): + """Example of a function with missing Google style parameter + documentation in the docstring. + + Args: + x: bla + y: blah blah + z (int): bar + + some other stuff + """ + + +def test_non_builtin_annotations_in_google_docstring( + bottomleft: Point, topright: Point +) -> float: + """Example of a function with missing Google style parameter + documentation in the docstring. + Args: + bottomleft: bottom left point of rectangle + topright: top right point of rectangle + """ + + +def test_non_builtin_annotations_for_returntype_in_google_docstring(bottomleft: Point, topright: Point) -> Point: + """Example of a function with missing Google style parameter + documentation in the docstring. + Args: + bottomleft: bottom left point of rectangle + topright: top right point of rectangle + """ + + +def test_func_params_and_keyword_params_in_google_docstring(this, other, that=True): + """Example of a function with Google style parameter split + in Args and Keyword Args in the docstring + + Args: + this (str): Printed first + other (int): Other args + + Keyword Args: + that (bool): Printed second + """ + print(this, that, other) + + +def test_func_params_and_wrong_keyword_params_in_google_docstring( # [missing-param-doc, missing-type-doc, differing-param-doc, differing-type-doc] + this, other, that=True +): + """Example of a function with Google style parameter split + in Args and Keyword Args in the docstring but with wrong keyword args + + Args: + this (str): Printed first + other (int): Other args + + Keyword Args: + these (bool): Printed second + """ + print(this, that, other) + + +class Foo: + def test_missing_method_params_in_google_docstring( # [missing-param-doc, missing-type-doc] + self, x, y + ): + """Example of a class method with missing parameter documentation in + the Google style docstring + + missing parameter documentation + + Args: + x: bla + """ + + +def test_existing_func_params_in_google_docstring(xarg, yarg, zarg, warg): + """Example of a function with correctly documented parameters and + return values (Google style) + + Args: + xarg (int): bla xarg + yarg (my.qualified.type): bla + bla yarg + + zarg (int): bla zarg + warg (my.qualified.type): bla warg + + Returns: + float: sum + """ + return xarg + yarg + + +def test_wrong_name_of_func_params_in_google_docstring_one( # [missing-param-doc, missing-type-doc, differing-param-doc, differing-type-doc] + xarg, yarg, zarg +): + """Example of functions with inconsistent parameter names in the + signature and in the Google style documentation + + Args: + xarg1 (int): bla xarg + yarg (float): bla yarg + + zarg1 (str): bla zarg + """ + return xarg + yarg + + +def test_wrong_name_of_func_params_in_google_docstring_two( # [differing-param-doc, differing-type-doc] + xarg, yarg +): + """Example of functions with inconsistent parameter names in the + signature and in the Google style documentation + + Args: + yarg1 (float): bla yarg + + For the other parameters, see bla. + """ + return xarg + yarg + + +def test_see_sentence_for_func_params_in_google_docstring(xarg, yarg): + """Example for the usage of "For the other parameters, see" to avoid + too many repetitions, e.g. in functions or methods adhering to a + given interface (Google style) + + Args: + yarg (float): bla yarg + + For the other parameters, see :func:`bla` + """ + return xarg + yarg + + +class ClassFoo: # [missing-param-doc, missing-type-doc] + """test_constr_params_in_class_google + Example of a class with missing constructor parameter documentation + (Google style) + + Everything is completely analogous to functions. + + Args: + y: bla + + missing constructor parameter documentation + """ + + def __init__(self, x, y): + pass + + +class ClassFoo: + def __init__(self, x, y): # [missing-param-doc, missing-type-doc] + """test_constr_params_in_init_google + Example of a class with missing constructor parameter documentation + (Google style) + + Args: + y: bla + + missing constructor parameter documentation + """ + + +class ClassFoo: # [multiple-constructor-doc,missing-param-doc, missing-type-doc] + """test_constr_params_in_class_and_init_google + Example of a class with missing constructor parameter documentation + in both the init docstring and the class docstring + (Google style) + + Everything is completely analogous to functions. + + Args: + y: bla + + missing constructor parameter documentation + """ + + def __init__(self, x, y): # [missing-param-doc, missing-type-doc] + """docstring foo + + Args: + y: bla + + missing constructor parameter documentation + """ + + +def test_warns_missing_args_google(named_arg, *args): # [missing-param-doc] + """The docstring + + Args: + named_arg (object): Returned + + Returns: + object or None: Maybe named_arg + """ + if args: + return named_arg + + +def test_warns_missing_kwargs_google(named_arg, **kwargs): # [missing-param-doc] + """The docstring + + Args: + named_arg (object): Returned + + Returns: + object or None: Maybe named_arg + """ + if kwargs: + return named_arg + + +def test_finds_args_without_type_google(named_arg, *args): + """The docstring + + Args: + named_arg (object): Returned + *args: Optional arguments + + Returns: + object or None: Maybe named_arg + """ + if args: + return named_arg + + +def test_finds_kwargs_without_type_google(named_arg, **kwargs): + """The docstring + + Args: + named_arg (object): Returned + **kwargs: Keyword arguments + + Returns: + object or None: Maybe named_arg + """ + if kwargs: + return named_arg + + +def test_finds_args_with_xref_type_google(named_arg, **kwargs): + """The docstring + + Args: + named_arg (`example.value`): Returned + **kwargs: Keyword arguments + + Returns: + `example.value`: Maybe named_arg + """ + if kwargs: + return named_arg + + +def test_ignores_optional_specifier_google( + param1, param2, param3=(), param4=[], param5=[], param6=True +): + """Do something. + + Args: + param1 (str): Description. + param2 (dict(str, int)): Description. + param3 (tuple(str), optional): Defaults to empty. Description. + param4 (List[str], optional): Defaults to empty. Description. + param5 (list[tuple(str)], optional): Defaults to empty. Description. + param6 (bool, optional): Defaults to True. Description. + + Returns: + int: Description. + """ + return param1, param2, param3, param4, param5, param6 + + +def test_finds_multiple_complex_types_google( + named_arg_one, + named_arg_two, + named_arg_three, + named_arg_four, + named_arg_five, + named_arg_six, + named_arg_seven, + named_arg_eight, + named_arg_nine, + named_arg_ten, +): + """The google docstring + + Args: + named_arg_one (dict(str, str)): Returned + named_arg_two (dict[str, str]): Returned + named_arg_three (int or str): Returned + named_arg_four (tuple(int or str)): Returned + named_arg_five (tuple(int) or list(int)): Returned + named_arg_six (tuple(int or str) or list(int or str)): Returned + named_arg_seven (dict(str,str)): Returned + named_arg_eight (dict[str,str]): Returned + named_arg_nine (tuple(int)): Returned + named_arg_ten (list[tokenize.TokenInfo]): Returned + + Returns: + dict(str, str): named_arg_one + dict[str, str]: named_arg_two + int or str: named_arg_three + tuple(int or str): named_arg_four + tuple(int) or list(int): named_arg_five + tuple(int or str) or list(int or str): named_arg_six + dict(str,str): named_arg_seven + dict[str,str]: named_arg_eight + tuple(int): named_arg_nine + list[tokenize.TokenInfo]: named_arg_ten + """ + return ( + named_arg_one, + named_arg_two, + named_arg_three, + named_arg_four, + named_arg_five, + named_arg_six, + named_arg_seven, + named_arg_eight, + named_arg_nine, + named_arg_ten, + ) diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.rc index dd77ffed41..24cdf9adaf 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.rc +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.rc @@ -4,3 +4,4 @@ load-plugins = pylint.extensions.docparams [BASIC] accept-no-param-doc=no docstring-min-length: -1 +no-docstring-rgx=^$ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt new file mode 100644 index 0000000000..6efbd6b1c5 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt @@ -0,0 +1,26 @@ +missing-param-doc:24:0:35:7:test_missing_func_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:24:0:35:7:test_missing_func_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-type-doc:80:0:92:7:test_missing_func_params_with_partial_annotations_in_google_docstring:"""x"" missing in parameter type documentation":UNDEFINED +differing-param-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter documentation":UNDEFINED +differing-type-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter type documentation":UNDEFINED +missing-param-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter documentation":UNDEFINED +missing-type-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter type documentation":UNDEFINED +missing-param-doc:146:4:156:11:Foo.test_missing_method_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:146:4:156:11:Foo.test_missing_method_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +differing-param-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter documentation":UNDEFINED +missing-type-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED +differing-param-doc:192:0:203:22:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:192:0:203:22:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:219:0:233:12:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:219:0:233:12:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:237:4:246:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:237:4:246:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:249:0:270:11:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:249:0:270:11:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +multiple-constructor-doc:249:0:270:11:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED +missing-param-doc:263:4:270:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:263:4:270:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:273:0:283:24:test_warns_missing_args_google:"""*args"" missing in parameter documentation":UNDEFINED +missing-param-doc:286:0:296:24:test_warns_missing_kwargs_google:"""**kwargs"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py index ca261ff42d..22dbcadaa3 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py @@ -1,6 +1,6 @@ """Tests for missing-raises-doc and missing-raises-type-doc for Google style docstrings""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring -# pylint: disable=unused-argument, import-outside-toplevel, import-error, try-except-raise +# pylint: disable=unused-argument, import-outside-toplevel, import-error, try-except-raise, too-few-public-methods def test_find_missing_google_raises(self): # [missing-raises-doc] @@ -136,3 +136,57 @@ def test_ignores_caught_google_raises(self): pass raise NameError("hi") + + +class Foo: + """test_finds_missing_raises_from_setter_google + Example of a setter having missing raises documentation in + the Google style docstring of the property + """ + + @property + def foo_method(self): # [missing-raises-doc] + """int: docstring + + Include a "Raises" section so that this is identified + as a Google docstring and not a Numpy docstring. + + Raises: + RuntimeError: Always + """ + raise RuntimeError() + return 10 # [unreachable] + + @foo_method.setter + def foo_method(self, value): + print(self) + raise AttributeError() + + +class Foo: + """test_finds_missing_raises_from_setter_google_2 + Example of a setter having missing raises documentation in + its own Google style docstring of the property. + """ + + @property + def foo_method(self): + """int: docstring ... + + Raises: + RuntimeError: Always + """ + raise RuntimeError() + return 10 # [unreachable] + + @foo_method.setter + def foo_method(self, value): # [missing-raises-doc] + """setter docstring ... + + Raises: + RuntimeError: Never + """ + print(self) + if True: # [using-constant-test] + raise AttributeError() + raise RuntimeError() diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt index 17fbc279d9..0ac9505593 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt @@ -7,3 +7,8 @@ unreachable:95:4:95:30:test_find_multiple_google_raises:Unreachable code:UNDEFIN unreachable:96:4:96:27:test_find_multiple_google_raises:Unreachable code:UNDEFINED missing-raises-doc:99:0:110:25:test_find_rethrown_google_raises:"""RuntimeError"" not documented as being raised":UNDEFINED missing-raises-doc:113:0:124:25:test_find_rethrown_google_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED +missing-raises-doc:148:4:158:17:Foo.foo_method:"""AttributeError"" not documented as being raised":UNDEFINED +unreachable:158:8:158:17:Foo.foo_method:Unreachable code:UNDEFINED +unreachable:180:8:180:17:Foo.foo_method:Unreachable code:UNDEFINED +missing-raises-doc:183:4:192:28:Foo.foo_method:"""AttributeError"" not documented as being raised":UNDEFINED +using-constant-test:190:8:191:34:Foo.foo_method:Using a conditional statement with a constant value:UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Google.py b/tests/functional/ext/docparams/return/missing_return_doc_Google.py index ba530484b4..498dbf1190 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Google.py +++ b/tests/functional/ext/docparams/return/missing_return_doc_Google.py @@ -1,6 +1,7 @@ """Tests for missing-return-doc and missing-return-type-doc for Google style docstrings""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring -# pylint: disable=unused-argument +# pylint: disable=unused-argument, too-few-public-methods, unnecessary-pass +import abc def my_func(self): @@ -75,3 +76,100 @@ def my_func(self): if a_func(): return None return 1 + + +class Foo: + """test_finds_property_return_type_google + Example of a property having return documentation in + a Google style docstring + """ + + @property + def foo_method(self): + """int: docstring ... + + Raises: + RuntimeError: Always + """ + raise RuntimeError() + return 10 # [unreachable] + + +class Foo: + """test_finds_annotation_property_return_type_google + Example of a property having return documentation in + a Google style docstring + """ + + @property + def foo_method(self) -> int: + """docstring ... + + Raises: + RuntimeError: Always + """ + raise RuntimeError() + return 10 # [unreachable] + + +class Foo: + """test_ignores_return_in_abstract_method_google + Example of an abstract method documenting the return type that an + implementation should return. + """ + + @abc.abstractmethod + def foo_method(self): + """docstring ... + + Returns: + int: Ten + """ + return 10 + + +class Foo: + """test_ignores_return_in_abstract_method_google_2 + Example of a method documenting the return type that an + implementation should return. + """ + + def foo_method(self, arg): + """docstring ... + + Args: + arg (int): An argument. + """ + raise NotImplementedError() + + +class Foo: + """test_ignores_ignored_argument_names_google + Example of a method documenting the return type that an + implementation should return. + """ + + def foo_method(self, arg, _): + """docstring ... + + Args: + arg (int): An argument. + """ + pass + + +class Foo: + """test_useless_docs_ignored_argument_names_google + Example of a method documenting the return type that an + implementation should return. + """ + + def foo_method(self, arg, _, _ignored): # [useless-type-doc, useless-param-doc] + """docstring ... + + Args: + arg (int): An argument. + _ (float): Another argument. + _ignored: Ignored argument. + """ + pass diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Google.txt b/tests/functional/ext/docparams/return/missing_return_doc_Google.txt index 1c226ddcbb..ea8e81200b 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Google.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_Google.txt @@ -1,3 +1,7 @@ -redundant-returns-doc:42:0:48:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:51:0:57:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:60:0:66:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:43:0:49:15:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:52:0:58:15:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:61:0:67:11:my_func:Redundant returns documentation:UNDEFINED +unreachable:95:8:95:17:Foo.foo_method:Unreachable code:UNDEFINED +unreachable:112:8:112:17:Foo.foo_method:Unreachable code:UNDEFINED +useless-param-doc:167:4:175:12:Foo.foo_method:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:167:4:175:12:Foo.foo_method:"""_"" useless ignored parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Google.py b/tests/functional/ext/docparams/return/missing_return_doc_required_Google.py index 55cb2e6f3b..37546b34f9 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Google.py +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Google.py @@ -1,7 +1,7 @@ """Tests for missing-return-doc and missing-return-type-doc for Google style docstrings with accept-no-returns-doc = no""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring -# pylint: disable=unused-argument +# pylint: disable=unused-argument, too-few-public-methods def my_func(self): # [missing-return-type-doc] @@ -38,3 +38,37 @@ def my_func(self): # [missing-return-doc] list(:class:`mymodule.Class`): """ return [mymodule.Class()] + + +class Foo: + """test_finds_missing_property_return_type_google + Example of a property having return documentation in + a Google style docstring + """ + + @property + def foo_method(self): # [missing-return-type-doc] + """docstring ... + + Raises: + RuntimeError: Always + """ + raise RuntimeError() + return 10 # [unreachable] + + +class Foo: + """test_ignores_non_property_return_type_google + Example of a class function trying to use `type` as return + documentation in a Google style docstring + """ + + def foo_method(self): # [missing-return-doc, missing-return-type-doc] + """int: docstring ... + + Raises: + RuntimeError: Always + """ + print(self) + raise RuntimeError() + return 10 # [unreachable] diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt b/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt index 16587df8bc..aa6775f5f0 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt @@ -3,3 +3,8 @@ missing-return-doc:16:0:22:16:my_func:Missing return documentation:UNDEFINED missing-return-doc:25:0:31:16:my_func:Missing return documentation:UNDEFINED missing-return-type-doc:25:0:31:16:my_func:Missing return type documentation:UNDEFINED missing-return-doc:34:0:40:29:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:50:4:57:17:Foo.foo_method:Missing return type documentation:UNDEFINED +unreachable:57:8:57:17:Foo.foo_method:Unreachable code:UNDEFINED +missing-return-doc:66:4:74:17:Foo.foo_method:Missing return documentation:UNDEFINED +missing-return-type-doc:66:4:74:17:Foo.foo_method:Missing return type documentation:UNDEFINED +unreachable:74:8:74:17:Foo.foo_method:Unreachable code:UNDEFINED From a51a5486ebff7543ae4fb6943fac2558947fa974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 8 Dec 2021 19:42:10 +0100 Subject: [PATCH 025/357] Move some of the Numpy tests out of ``TestParamDocChecker`` (#5492) --- tests/extensions/test_check_docs.py | 585 ------------------ .../missing_param_doc_required_Numpy.py | 374 +++++++++++ .../missing_param_doc_required_Numpy.rc | 7 + .../missing_param_doc_required_Numpy.txt | 22 + .../return/missing_return_doc_Numpy.py | 74 ++- .../return/missing_return_doc_Numpy.txt | 8 +- 6 files changed, 481 insertions(+), 589 deletions(-) create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt diff --git a/tests/extensions/test_check_docs.py b/tests/extensions/test_check_docs.py index a5293b821d..b0eeeb4838 100644 --- a/tests/extensions/test_check_docs.py +++ b/tests/extensions/test_check_docs.py @@ -25,12 +25,10 @@ in particular the parameter documentation checker `DocstringChecker` """ -# pylint: disable=too-many-public-methods import re import astroid -import pytest from astroid import nodes from pylint.extensions.docparams import DocstringParameterChecker @@ -48,33 +46,6 @@ class TestParamDocChecker(CheckerTestCase): "docstring_min_length": -1, } - def test_missing_func_params_in_numpy_docstring(self) -> None: - """Example of a function with missing NumPy style parameter - documentation in the docstring - """ - node = astroid.extract_node( - """ - def function_foo(x, y, z): - '''docstring ... - - Parameters - ---------- - x: - bla - z: int - bar - - some other stuff - ''' - pass - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("y",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("x, y",)), - ): - self.checker.visit_functiondef(node) - @set_config(accept_no_param_doc=True) def test_tolerate_no_param_documentation_at_all(self) -> None: """Example of a function with no parameter documentation at all @@ -139,228 +110,6 @@ def _visit_methods_of_class(self, node: nodes.ClassDef) -> None: if isinstance(body_item, nodes.FunctionDef) and hasattr(body_item, "name"): self.checker.visit_functiondef(body_item) - def test_missing_method_params_in_numpy_docstring(self) -> None: - """Example of a class method with missing parameter documentation in - the Numpy style docstring - """ - node = astroid.extract_node( - """ - class Foo(object): - def method_foo(self, x, y): - '''docstring ... - - missing parameter documentation - - Parameters - ---------- - x: - bla - ''' - pass - """ - ) - method_node = node.body[0] - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=method_node, args=("y",)), - MessageTest(msg_id="missing-type-doc", node=method_node, args=("x, y",)), - ): - self._visit_methods_of_class(node) - - def test_existing_func_params_in_numpy_docstring(self) -> None: - """Example of a function with correctly documented parameters and - return values (Numpy style) - """ - node = astroid.extract_node( - """ - def function_foo(xarg, yarg, zarg, warg): - '''function foo ... - - Parameters - ---------- - xarg: int - bla xarg - yarg: my.qualified.type - bla yarg - - zarg: int - bla zarg - warg: my.qualified.type - bla warg - - Returns - ------- - float - sum - ''' - return xarg + yarg - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_wrong_name_of_func_params_in_numpy_docstring(self) -> None: - """Example of functions with inconsistent parameter names in the - signature and in the Numpy style documentation - """ - node = astroid.extract_node( - """ - def function_foo(xarg, yarg, zarg): - '''function foo ... - - Parameters - ---------- - xarg1: int - bla xarg - yarg: float - bla yarg - - zarg1: str - bla zarg - ''' - return xarg + yarg - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("xarg, zarg",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("xarg, zarg",)), - MessageTest( - msg_id="differing-param-doc", node=node, args=("xarg1, zarg1",) - ), - MessageTest(msg_id="differing-type-doc", node=node, args=("xarg1, zarg1",)), - ): - self.checker.visit_functiondef(node) - - node = astroid.extract_node( - """ - def function_foo(xarg, yarg): - '''function foo ... - - Parameters - ---------- - yarg1: float - bla yarg - - For the other parameters, see bla. - ''' - return xarg + yarg - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="differing-param-doc", node=node, args=("yarg1",)), - MessageTest(msg_id="differing-type-doc", node=node, args=("yarg1",)), - ): - self.checker.visit_functiondef(node) - - def test_see_sentence_for_func_params_in_numpy_docstring(self) -> None: - """Example for the usage of "For the other parameters, see" to avoid - too many repetitions, e.g. in functions or methods adhering to a - given interface (Numpy style) - """ - node = astroid.extract_node( - """ - def function_foo(xarg, yarg): - '''function foo ... - - Parameters - ---------- - yarg: float - bla yarg - - For the other parameters, see :func:`bla` - ''' - return xarg + yarg - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_constr_params_in_class_numpy(self) -> None: - """Example of a class with missing constructor parameter documentation - (Numpy style) - - Everything is completely analogous to functions. - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - '''docstring foo - - Parameters - ---------- - y: - bla - - missing constructor parameter documentation - ''' - - def __init__(self, x, y): - pass - - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("x",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("x, y",)), - ): - self._visit_methods_of_class(node) - - def test_constr_params_and_attributes_in_class_numpy(self) -> None: - """Example of a class with correct constructor parameter documentation - and an attributes section (Numpy style) - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - ''' - Parameters - ---------- - foo : str - Something. - - Attributes - ---------- - bar : str - Something. - ''' - def __init__(self, foo): - self.bar = None - """ - ) - with self.assertNoMessages(): - self._visit_methods_of_class(node) - - def test_constr_params_in_init_numpy(self) -> None: - """Example of a class with missing constructor parameter documentation - (Numpy style) - - Everything is completely analogous to functions. - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - def __init__(self, x, y): - '''docstring foo constructor - - Parameters - ---------- - y: - bla - - missing constructor parameter documentation - ''' - pass - - """ - ) - constructor_node = node.body[0] - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=constructor_node, args=("x",)), - MessageTest( - msg_id="missing-type-doc", node=constructor_node, args=("x, y",) - ), - ): - self._visit_methods_of_class(node) - def test_see_sentence_for_constr_params_in_class(self) -> None: """Example usage of "For the parameters, see" in class docstring""" node = astroid.extract_node( @@ -399,54 +148,6 @@ def __init__(self, x, y): with self.assertNoMessages(): self._visit_methods_of_class(node) - def test_constr_params_in_class_and_init_numpy(self) -> None: - """Example of a class with missing constructor parameter documentation - in both the init docstring and the class docstring - (Numpy style) - - Everything is completely analogous to functions. - """ - node = astroid.extract_node( - """ - class ClassFoo(object): - '''docstring foo - - Parameters - ---------- - y: - bla - - missing constructor parameter documentation - ''' - - def __init__(self, x, y): - '''docstring foo - - Parameters - ---------- - y: - bla - - missing constructor parameter documentation - ''' - pass - - """ - ) - constructor_node = node.body[0] - with self.assertAddsMessages( - MessageTest( - msg_id="multiple-constructor-doc", node=node, args=(node.name,) - ), - MessageTest(msg_id="missing-param-doc", node=node, args=("x",)), - MessageTest(msg_id="missing-type-doc", node=node, args=("x, y",)), - MessageTest(msg_id="missing-param-doc", node=constructor_node, args=("x",)), - MessageTest( - msg_id="missing-type-doc", node=constructor_node, args=("x, y",) - ), - ): - self._visit_methods_of_class(node) - def test_kwonlyargs_are_taken_in_account(self) -> None: node = astroid.extract_node( ''' @@ -466,197 +167,6 @@ def my_func(arg, *, kwonly, missing_kwonly): ): self.checker.visit_functiondef(node) - def test_warns_missing_args_numpy(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, *args): - """The docstring - - Args - ---- - named_arg : object - Returned - - Returns - ------- - object or None - Maybe named_arg - """ - if args: - return named_arg - ''' - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("*args",)) - ): - self.checker.visit_functiondef(node) - - def test_warns_missing_kwargs_numpy(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, **kwargs): - """The docstring - - Args - ---- - named_arg : object - Returned - - Returns - ------- - object or None - Maybe named_arg - """ - if kwargs: - return named_arg - ''' - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-param-doc", node=node, args=("**kwargs",)) - ): - self.checker.visit_functiondef(node) - - def test_finds_args_without_type_numpy(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, typed_arg: bool, untyped_arg, *args): - """The docstring - - Args - ---- - named_arg : object - Returned - typed_arg - Other argument without numpy type annotation - untyped_arg - Other argument without any type annotation - *args : - Optional Arguments - - Returns - ------- - object or None - Maybe named_arg - """ - if args: - return named_arg - ''' - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-type-doc", node=node, args=("untyped_arg",)) - ): - self.checker.visit_functiondef(node) - - def test_finds_args_with_xref_type_numpy(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, *args): - """The docstring - - Args - ---- - named_arg : `example.value` - Returned - *args : - Optional Arguments - - Returns - ------- - `example.value` - Maybe named_arg - """ - if args: - return named_arg - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_finds_kwargs_without_type_numpy(self) -> None: - node = astroid.extract_node( - ''' - def my_func(named_arg, **kwargs): - """The docstring - - Args - ---- - named_arg : object - Returned - **kwargs : - Keyword arguments - - Returns - ------- - object or None - Maybe named_arg - """ - if kwargs: - return named_arg - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - COMPLEX_TYPES = [ - "dict(str,str)", - "dict[str,str]", - "tuple(int)", - "list[tokenize.TokenInfo]", - "dict(str, str)", - "dict[str, str]", - "int or str", - "tuple(int or str)", - "tuple(int) or list(int)", - "tuple(int or str) or list(int or str)", - ] - - @pytest.mark.parametrize("complex_type", COMPLEX_TYPES) - def test_finds_multiple_types_numpy(self, complex_type): - node = astroid.extract_node( - f''' - def my_func(named_arg): - """The docstring - - Args - ---- - named_arg : {complex_type} - Returned - - Returns - ------- - {complex_type} - named_arg - """ - return named_arg - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_ignores_optional_specifier_numpy(self) -> None: - node = astroid.extract_node( - ''' - def do_something(param, param2='all'): - """Do something. - - Parameters - ---------- - param : str - Description. - param2 : str, optional - Description (the default is 'all'). - - Returns - ------- - int - Description. - """ - return param, param2 - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - def test_finds_short_name_exception(self) -> None: node = astroid.extract_node( ''' @@ -850,101 +360,6 @@ def foo(self) -> int: #@ ): self.checker.visit_return(node) - def test_ignores_return_in_abstract_method_numpy(self) -> None: - """Example of an abstract method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - import abc - class Foo(object): - @abc.abstractmethod - def foo(self): #@ - '''docstring ... - - Returns - ------- - int - Ten - ''' - return 10 - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_ignores_return_in_abstract_method_numpy_2(self) -> None: - """Example of a method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - class Foo(object): - def foo(self, arg): #@ - '''docstring ... - - Parameters - ---------- - arg : int - An argument. - ''' - raise NotImplementedError() - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_ignores_ignored_argument_names_numpy(self) -> None: - """Example of a method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - class Foo(object): - def foo(self, arg, _): #@ - '''docstring ... - - Parameters - ---------- - arg : int - An argument. - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_useless_docs_ignored_argument_names_numpy(self) -> None: - """Example of a method documenting the return type that an - implementation should return. - """ - node = astroid.extract_node( - """ - class Foo(object): - def foo(self, arg, _, _ignored): #@ - '''docstring ... - - Parameters - ---------- - arg : int - An argument. - - _ : float - Another argument. - - _ignored : - Ignored Argument - ''' - pass - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="useless-type-doc", node=node, args=("_",)), - MessageTest(msg_id="useless-param-doc", node=node, args=("_, _ignored",)), - ): - self.checker.visit_functiondef(node) - @set_config_directly(no_docstring_rgx=re.compile(r"^_(?!_).*$")) def test_skip_no_docstring_rgx(self) -> None: """Example of a function that matches the default 'no-docstring-rgx' config option diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py new file mode 100644 index 0000000000..e780ac0f5c --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py @@ -0,0 +1,374 @@ +"""Tests for missing-param-doc and missing-type-doc for Numpy style docstrings +with accept-no-param-doc = no +""" +# pylint: disable=invalid-name, unused-argument, undefined-variable, too-many-arguments +# pylint: disable=line-too-long, too-few-public-methods, missing-class-docstring +# pylint: disable=missing-function-docstring, function-redefined, inconsistent-return-statements + + +def test_missing_func_params_in_numpy_docstring( # [missing-param-doc, missing-type-doc] + x, y, z +): + """Example of a function with missing NumPy style parameter + documentation in the docstring + + Parameters + ---------- + x: + bla + z: int + bar + + some other stuff + """ + + +class Foo: + def test_missing_method_params_in_numpy_docstring( # [missing-param-doc, missing-type-doc] + self, x, y + ): + """Example of a class method with missing parameter documentation in + the Numpy style docstring + + missing parameter documentation + + Parameters + ---------- + x: + bla + """ + + +def test_existing_func_params_in_numpy_docstring(xarg, yarg, zarg, warg): + """Example of a function with correctly documented parameters and + return values (Numpy style) + + Parameters + ---------- + xarg: int + bla xarg + yarg: my.qualified.type + bla yarg + + zarg: int + bla zarg + warg: my.qualified.type + bla warg + + Returns + ------- + float + sum + """ + return xarg + yarg + + +def test_wrong_name_of_func_params_in_numpy_docstring( # [missing-param-doc, missing-type-doc, differing-param-doc, differing-type-doc] + xarg, yarg, zarg +): + """Example of functions with inconsistent parameter names in the + signature and in the Numpy style documentation + + Parameters + ---------- + xarg1: int + bla xarg + yarg: float + bla yarg + + zarg1: str + bla zarg + """ + return xarg + yarg + + +def test_wrong_name_of_func_params_in_numpy_docstring_two( # [differing-param-doc, differing-type-doc] + xarg, yarg +): + """Example of functions with inconsistent parameter names in the + signature and in the Numpy style documentation + + Parameters + ---------- + yarg1: float + bla yarg + + For the other parameters, see bla. + """ + return xarg + yarg + + +def test_see_sentence_for_func_params_in_numpy_docstring(xarg, yarg): + """Example for the usage of "For the other parameters, see" to avoid + too many repetitions, e.g. in functions or methods adhering to a + given interface (Numpy style) + + Parameters + ---------- + yarg: float + bla yarg + + For the other parameters, see :func:`bla` + """ + return xarg + yarg + + +class ClassFoo: # [missing-param-doc, missing-type-doc] + """test_constr_params_in_class_numpy + Example of a class with missing constructor parameter documentation + (Numpy style) + + Everything is completely analogous to functions. + + Parameters + ---------- + y: + bla + + missing constructor parameter documentation + """ + + def __init__(self, x, y): + pass + + +class ClassFoo: + """test_constr_params_and_attributes_in_class_numpy + Example of a class with correct constructor parameter documentation + and an attributes section (Numpy style) + + Parameters + ---------- + foobar : str + Something. + + Attributes + ---------- + barfoor : str + Something. + """ + + def __init__(self, foobar): + self.barfoo = None + + +class ClassFoo: + def __init__(self, x, y): # [missing-param-doc, missing-type-doc] + """test_constr_params_in_init_numpy + Example of a class with missing constructor parameter documentation + (Numpy style) + + Everything is completely analogous to functions. + + Parameters + ---------- + y: + bla + + missing constructor parameter documentation + """ + + +class ClassFoo: # [multiple-constructor-doc, missing-param-doc, missing-type-doc] + """test_constr_params_in_class_and_init_numpy + Example of a class with missing constructor parameter documentation + in both the init docstring and the class docstring + (Numpy style) + + Everything is completely analogous to functions. + + Parameters + ---------- + y: + bla + + missing constructor parameter documentation + """ + + def __init__(self, x, y): # [missing-param-doc, missing-type-doc] + """docstring foo + + Parameters + ---------- + y: + bla + + missing constructor parameter documentation + """ + + +def test_warns_missing_args_numpy(named_arg, *args): # [missing-param-doc] + """The docstring + + Args + ---- + named_arg : object + Returned + + Returns + ------- + object or None + Maybe named_arg + """ + if args: + return named_arg + + +def test_warns_missing_kwargs_numpy(named_arg, **kwargs): # [missing-param-doc] + """The docstring + + Args + ---- + named_arg : object + Returned + + Returns + ------- + object or None + Maybe named_arg + """ + if kwargs: + return named_arg + + +def test_finds_args_without_type_numpy( # [missing-type-doc] + named_arg, typed_arg: bool, untyped_arg, *args +): + """The docstring + + Args + ---- + named_arg : object + Returned + typed_arg + Other argument without numpy type annotation + untyped_arg + Other argument without any type annotation + *args : + Optional Arguments + + Returns + ------- + object or None + Maybe named_arg + """ + if args: + return named_arg + + +def test_finds_args_with_xref_type_numpy(named_arg, *args): + """The docstring + + Args + ---- + named_arg : `example.value` + Returned + *args : + Optional Arguments + + Returns + ------- + `example.value` + Maybe named_arg + """ + if args: + return named_arg + + +def test_finds_kwargs_without_type_numpy(named_arg, **kwargs): + """The docstring + + Args + ---- + named_arg : object + Returned + **kwargs : + Keyword arguments + + Returns + ------- + object or None + Maybe named_arg + """ + if kwargs: + return named_arg + + +def my_func( + named_arg_one, + named_arg_two, + named_arg_three, + named_arg_four, + named_arg_five, + named_arg_six, + named_arg_seven, + named_arg_eight, +): + """The docstring + + Args + ---- + named_arg_one : dict(str,str) + Returned + named_arg_two : dict[str,str] + Returned + named_arg_three : tuple(int) + Returned + named_arg_four : list[tokenize.TokenInfo] + Returned + named_arg_five : int or str + Returned + named_arg_six : tuple(int or str) + Returned + named_arg_seven : tuple(int) or list(int) + Returned + named_arg_eight : tuple(int or str) or list(int or str) + Returned + + Returns + ------- + dict(str,str) + named_arg_one + dict[str,str] + named_arg_two + tuple(int) + named_arg_three + list[tokenize.TokenInfo] + named_arg_four + int or str + named_arg_five + tuple(int or str) + named_arg_six + tuple(int) or list(int) + named_arg_seven + tuple(int or str) or list(int or str) + named_arg_eight + """ + return ( + named_arg_one, + named_arg_two, + named_arg_three, + named_arg_four, + named_arg_five, + named_arg_six, + named_arg_seven, + named_arg_eight, + ) + + +def test_ignores_optional_specifier_numpy(param, param2="all"): + """Do something. + + Parameters + ---------- + param : str + Description. + param2 : str, optional + Description (the default is 'all'). + + Returns + ------- + int + Description. + """ + return param, param2 diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.rc new file mode 100644 index 0000000000..24cdf9adaf --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length: -1 +no-docstring-rgx=^$ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt new file mode 100644 index 0000000000..d4f8be211d --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt @@ -0,0 +1,22 @@ +missing-param-doc:9:0:23:7:test_missing_func_params_in_numpy_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:9:0:23:7:test_missing_func_params_in_numpy_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:27:4:39:11:Foo.test_missing_method_params_in_numpy_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:27:4:39:11:Foo.test_missing_method_params_in_numpy_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +differing-param-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg, zarg"" missing in parameter documentation":UNDEFINED +missing-type-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED +differing-param-doc:85:0:98:22:test_wrong_name_of_func_params_in_numpy_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:85:0:98:22:test_wrong_name_of_func_params_in_numpy_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:116:0:132:12:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:116:0:132:12:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:156:4:169:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:156:4:169:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:172:0:197:11:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:172:0:197:11:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +multiple-constructor-doc:172:0:197:11:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED +missing-param-doc:188:4:197:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:188:4:197:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:200:0:214:24:test_warns_missing_args_numpy:"""*args"" missing in parameter documentation":UNDEFINED +missing-param-doc:217:0:231:24:test_warns_missing_kwargs_numpy:"""**kwargs"" missing in parameter documentation":UNDEFINED +missing-type-doc:234:0:256:24:test_finds_args_without_type_numpy:"""untyped_arg"" missing in parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Numpy.py b/tests/functional/ext/docparams/return/missing_return_doc_Numpy.py index 269adb4241..47f6f4ae07 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Numpy.py +++ b/tests/functional/ext/docparams/return/missing_return_doc_Numpy.py @@ -1,6 +1,7 @@ """Tests for missing-return-doc and missing-return-type-doc for Numpy style docstrings""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring -# pylint: disable=unused-argument +# pylint: disable=unused-argument, too-few-public-methods, disallowed-name +import abc def my_func(self): @@ -103,3 +104,74 @@ def my_func(self): # [redundant-returns-doc] One """ yield 1 + + +class Foo: + """test_ignores_return_in_abstract_method_numpy + Example of an abstract method documenting the return type that an + implementation should return.""" + + @abc.abstractmethod + def foo(self): + """docstring ... + + Returns + ------- + int + Ten + """ + return 10 + + +class Foo: + """test_ignores_return_in_abstract_method_numpy_2 + Example of a method documenting the return type that an + implementation should return.""" + + def foo(self, arg): + """docstring ... + + Parameters + ---------- + arg : int + An argument. + """ + raise NotImplementedError() + + +class Foo: + """test_ignores_ignored_argument_names_numpy + Example of a method documenting the return type that an + implementation should return. + """ + + def foo(self, arg, _): + """docstring ... + + Parameters + ---------- + arg : int + An argument. + """ + + +class Foo: + """test_useless_docs_ignored_argument_names_numpy + Example of a method documenting the return type that an + implementation should return. + """ + + def foo(self, arg, _, _ignored): # [useless-type-doc, useless-param-doc] + """docstring ... + + Parameters + ---------- + arg : int + An argument. + + _ : float + Another argument. + + _ignored : + Ignored Argument + """ diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt b/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt index 1c17a80af5..d137612e6e 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt @@ -1,3 +1,5 @@ -redundant-returns-doc:61:0:69:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:72:0:79:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:97:0:105:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:62:0:70:15:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:73:0:80:15:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:98:0:106:11:my_func:Redundant returns documentation:UNDEFINED +useless-param-doc:164:4:177:11:Foo.foo:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:164:4:177:11:Foo.foo:"""_"" useless ignored parameter type documentation":UNDEFINED From bd55b27d41542e3ca1f031f986b6151f6cac457f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 11 Dec 2021 03:10:28 -0500 Subject: [PATCH 026/357] Fix #4761: Emit `used-before-assignment` where single assignment only made in except blocks (#5402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- ChangeLog | 13 +++- doc/whatsnew/2.13.rst | 7 ++ pylint/checkers/variables.py | 67 +++++++++++++++++-- pylintrc | 2 +- .../u/undefined/undefined_variable.py | 4 +- .../u/undefined/undefined_variable.txt | 2 + .../u/use/used_before_assignment_issue4761.py | 12 ++++ .../use/used_before_assignment_issue4761.txt | 1 + tests/lint/test_utils.py | 2 +- 9 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 tests/functional/u/use/used_before_assignment_issue4761.py create mode 100644 tests/functional/u/use/used_before_assignment_issue4761.txt diff --git a/ChangeLog b/ChangeLog index 8ddac4258f..22c918048b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,13 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* ``used-before-assignment`` now assumes that assignments in except blocks + may not have occurred and warns accordingly. + + Closes #4761 + +* ``used-before-assignment`` now checks names in try blocks. + * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. @@ -18,6 +25,9 @@ Release date: TBA Closes #4716 +* The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` + as its reporter if none is provided. + * Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. Closes #5312 @@ -36,9 +46,6 @@ Release date: TBA Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) -* The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` - as its reporter if none is provided. - What's New in Pylint 2.12.2? ============================ diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 0713e343ae..55535638ec 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -31,6 +31,13 @@ Other Changes Closes #5323 +* ``used-before-assignment`` now assumes that assignments in except blocks + may not have occurred and warns accordingly. + + Closes #4761 + +* ``used-before-assignment`` now checks names in try blocks. + * Require Python ``3.6.2`` to run pylint. Closes #5065 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index f2bba355c8..13b8589afb 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -534,7 +534,7 @@ def _has_locals_call_after_node(stmt, scope): ScopeConsumer = collections.namedtuple( - "ScopeConsumer", "to_consume consumed scope_type" + "ScopeConsumer", "to_consume consumed consumed_uncertain scope_type" ) @@ -544,17 +544,24 @@ class NamesConsumer: """ def __init__(self, node, scope_type): - self._atomic = ScopeConsumer(copy.copy(node.locals), {}, scope_type) + self._atomic = ScopeConsumer( + copy.copy(node.locals), {}, collections.defaultdict(list), scope_type + ) self.node = node def __repr__(self): to_consumes = [f"{k}->{v}" for k, v in self._atomic.to_consume.items()] consumed = [f"{k}->{v}" for k, v in self._atomic.consumed.items()] + consumed_uncertain = [ + f"{k}->{v}" for k, v in self._atomic.consumed_uncertain.items() + ] to_consumes = ", ".join(to_consumes) consumed = ", ".join(consumed) + consumed_uncertain = ", ".join(consumed_uncertain) return f""" to_consume : {to_consumes} consumed : {consumed} +consumed_uncertain: {consumed_uncertain} scope_type : {self._atomic.scope_type} """ @@ -569,6 +576,19 @@ def to_consume(self): def consumed(self): return self._atomic.consumed + @property + def consumed_uncertain(self) -> DefaultDict[str, List[nodes.NodeNG]]: + """ + Retrieves nodes filtered out by get_next_to_consume() that may not + have executed, such as statements in except blocks. Checkers that + want to treat the statements as executed (e.g. for unused-variable) + may need to add them back. + + TODO: A pending PR will extend this to nodes in try blocks when + evaluating their corresponding except and finally blocks. + """ + return self._atomic.consumed_uncertain + @property def scope_type(self): return self._atomic.scope_type @@ -589,7 +609,9 @@ def mark_as_consumed(self, name, consumed_nodes): def get_next_to_consume(self, node): """ - Return a list of the nodes that define `node` from this scope. + Return a list of the nodes that define `node` from this scope. If it is + uncertain whether a node will be consumed, such as for statements in + except blocks, add it to self.consumed_uncertain instead of returning it. Return None to indicate a special case that needs to be handled by the caller. """ name = node.name @@ -617,9 +639,28 @@ def get_next_to_consume(self, node): found_nodes = [ n for n in found_nodes - if not isinstance(n.statement(), nodes.ExceptHandler) - or n.statement().parent_of(node) + if not isinstance(n.statement(future=True), nodes.ExceptHandler) + or n.statement(future=True).parent_of(node) + ] + + # Filter out assignments in an Except clause that the node is not + # contained in, assuming they may fail + if found_nodes: + filtered_nodes = [ + n + for n in found_nodes + if not ( + isinstance(n.statement(future=True).parent, nodes.ExceptHandler) + and isinstance( + n.statement(future=True).parent.parent, nodes.TryExcept + ) + ) + or n.statement(future=True).parent.parent_of(node) ] + filtered_nodes_set = set(filtered_nodes) + difference = [n for n in found_nodes if n not in filtered_nodes_set] + self.consumed_uncertain[node.name] += difference + found_nodes = filtered_nodes return found_nodes @@ -1042,6 +1083,14 @@ def _undefined_and_used_before_checker( if action is VariableVisitConsumerAction.CONTINUE: continue if action is VariableVisitConsumerAction.CONSUME: + # pylint: disable-next=fixme + # TODO: remove assert after _check_consumer return value better typed + assert found_nodes is not None, "Cannot consume an empty list of nodes." + # Any nodes added to consumed_uncertain by get_next_to_consume() + # should be added back so that they are marked as used. + # They will have already had a chance to emit used-before-assignment. + # We check here instead of before every single return in _check_consumer() + found_nodes += current_consumer.consumed_uncertain[node.name] current_consumer.mark_as_consumed(node.name, found_nodes) if action in { VariableVisitConsumerAction.RETURN, @@ -1135,6 +1184,12 @@ def _check_consumer( return (VariableVisitConsumerAction.CONTINUE, None) if not found_nodes: self.add_message("used-before-assignment", args=node.name, node=node) + if current_consumer.consumed_uncertain[node.name]: + # If there are nodes added to consumed_uncertain by + # get_next_to_consume() because they might not have executed, + # return a CONSUME action so that _undefined_and_used_before_checker() + # will mark them as used + return (VariableVisitConsumerAction.CONSUME, found_nodes) return (VariableVisitConsumerAction.RETURN, found_nodes) self._check_late_binding_closure(node) @@ -2370,7 +2425,7 @@ def _check_classdef_metaclasses(self, klass, parent_node): name = METACLASS_NAME_TRANSFORMS.get(name, name) if name: # check enclosing scopes starting from most local - for scope_locals, _, _ in self._to_consume[::-1]: + for scope_locals, _, _, _ in self._to_consume[::-1]: found_nodes = scope_locals.get(name, []) for found_node in found_nodes: if found_node.lineno <= klass.lineno: diff --git a/pylintrc b/pylintrc index a38a35aedf..7e983510e2 100644 --- a/pylintrc +++ b/pylintrc @@ -332,7 +332,7 @@ max-locals=25 max-returns=11 # Maximum number of branch for function / method body -max-branches=26 +max-branches=27 # Maximum number of statements in function / method body max-statements=100 diff --git a/tests/functional/u/undefined/undefined_variable.py b/tests/functional/u/undefined/undefined_variable.py index f4f22e5238..6ce9aaa6ec 100644 --- a/tests/functional/u/undefined/undefined_variable.py +++ b/tests/functional/u/undefined/undefined_variable.py @@ -35,7 +35,7 @@ def bad_default(var, default=unknown2): # [undefined-variable] LMBD2 = lambda x, y: x+z # [undefined-variable] try: - POUET # don't catch me + POUET # [used-before-assignment] except NameError: POUET = 'something' @@ -45,7 +45,7 @@ def bad_default(var, default=unknown2): # [undefined-variable] POUETT = 'something' try: - POUETTT # don't catch me + POUETTT # [used-before-assignment] except: # pylint:disable = bare-except POUETTT = 'something' diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt index c5a70f7a76..bee5ab475a 100644 --- a/tests/functional/u/undefined/undefined_variable.txt +++ b/tests/functional/u/undefined/undefined_variable.txt @@ -8,7 +8,9 @@ undefined-variable:31:4:31:10:bad_default:Undefined variable 'augvar':UNDEFINED undefined-variable:32:8:32:14:bad_default:Undefined variable 'vardel':UNDEFINED undefined-variable:34:19:34:31::Undefined variable 'doesnotexist':UNDEFINED undefined-variable:35:23:35:24::Undefined variable 'z':UNDEFINED +used-before-assignment:38:4:38:9::Using variable 'POUET' before assignment:UNDEFINED used-before-assignment:43:4:43:10::Using variable 'POUETT' before assignment:UNDEFINED +used-before-assignment:48:4:48:11::Using variable 'POUETTT' before assignment:UNDEFINED used-before-assignment:56:4:56:9::Using variable 'PLOUF' before assignment:UNDEFINED used-before-assignment:65:11:65:14:if_branch_test:Using variable 'xxx' before assignment:UNDEFINED used-before-assignment:91:23:91:32:test_arguments:Using variable 'TestClass' before assignment:UNDEFINED diff --git a/tests/functional/u/use/used_before_assignment_issue4761.py b/tests/functional/u/use/used_before_assignment_issue4761.py new file mode 100644 index 0000000000..ab6fc765b4 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_issue4761.py @@ -0,0 +1,12 @@ +"""used-before-assignment (E0601)""" +def function(): + """Consider that except blocks may not execute.""" + try: + pass + except ValueError: + some_message = 'some message' + + if not some_message: # [used-before-assignment] + return 1 + + return some_message diff --git a/tests/functional/u/use/used_before_assignment_issue4761.txt b/tests/functional/u/use/used_before_assignment_issue4761.txt new file mode 100644 index 0000000000..cf8fd209a8 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_issue4761.txt @@ -0,0 +1 @@ +used-before-assignment:9:11:9:23:function:Using variable 'some_message' before assignment:UNDEFINED diff --git a/tests/lint/test_utils.py b/tests/lint/test_utils.py index f7294f353c..44703982e1 100644 --- a/tests/lint/test_utils.py +++ b/tests/lint/test_utils.py @@ -15,7 +15,7 @@ def test_prepare_crash_report(tmp_path: PosixPath) -> None: template_path = prepare_crash_report( ex, str(python_file), str(tmp_path / "pylint-crash-%Y.txt") ) - assert str(tmp_path) in str(template_path) + assert str(tmp_path) in str(template_path) # pylint: disable=used-before-assignment with open(template_path, encoding="utf8") as f: template_content = f.read() assert python_content in template_content From 80285a9304f3f8e3ed339525cac95f6da6b6ce58 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 12 Dec 2021 11:25:35 -0500 Subject: [PATCH 027/357] Fix #5504: Fix crash if the output of items() is assigned to a 1-tuple (#5505) * Fix #5504: Fix crash if the output of items() is assigned to a 1-tuple --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/refactoring/refactoring_checker.py | 2 ++ .../u/unnecessary/unnecessary_dict_index_lookup.py | 7 +++++++ 4 files changed, 19 insertions(+) diff --git a/ChangeLog b/ChangeLog index 22c918048b..9bc848b59c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,11 @@ Release date: TBA Closes #4716 +* Fix crash in ``unnecessary-dict-index-lookup`` checker if the output of + ``items()`` is assigned to a 1-tuple. + + Closes #5504 + * The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` as its reporter if none is provided. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 55535638ec..ec1ea0af5a 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -26,6 +26,11 @@ Other Changes Closes #4716 +* Fix crash in ``unnecessary-dict-index-lookup`` checker if the output of + ``items()`` is assigned to a 1-tuple. + + Closes #5504 + * Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables or ``not in`` checks diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 7bd82d1f91..2c687d418d 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1882,6 +1882,8 @@ def _check_unnecessary_dict_index_lookup( if isinstance(value, nodes.Name): if ( not isinstance(node.target, nodes.Tuple) + # Ignore 1-tuples: for k, in d.items() + or len(node.target.elts) < 2 or value.name != node.target.elts[0].name or iterating_object_name != subscript.value.as_string() ): diff --git a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py index 423360c822..56e65e5986 100644 --- a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py +++ b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py @@ -100,3 +100,10 @@ class Foo: for key_two, val_two in val.items(): del outer_dict[key][key_two] # [unnecessary-dict-index-lookup] break + +# Test partial unpacking of items +# https://github.com/PyCQA/pylint/issues/5504 + +d = {} +for key, in d.items(): + print(d[key]) From cbf39764474529b03eea206801fb8f456a2da9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 12 Dec 2021 22:14:03 +0100 Subject: [PATCH 028/357] Fix incorrect classification of property docstrings in Numpy-style (#5498) --- ChangeLog | 5 ++ doc/whatsnew/2.13.rst | 7 +++ pylint/extensions/_check_docs_utils.py | 52 +++++++++++-------- pylint/extensions/docparams.py | 2 +- tests/extensions/test_check_docs.py | 36 ------------- .../raise/missing_raises_doc_Numpy.py | 28 ++++++++++ .../raise/missing_raises_doc_Numpy.txt | 14 ++--- 7 files changed, 80 insertions(+), 64 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9bc848b59c..738d8acda9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -42,6 +42,11 @@ Release date: TBA Closes #5323 +* Fixed incorrect classification of Numpy-style docstring as Google-style docstring for + docstrings with property setter documentation. + Docstring classification is now based on the highest amount of matched sections instead + of the order in which the docstring styles were tried. + * Fixed detection of ``arguments-differ`` when superclass static methods lacked a ``@staticmethod`` decorator. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index ec1ea0af5a..001b6b9762 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -19,6 +19,13 @@ Extensions * Pyreverse - add output in mermaid-js format and html which is an mermaid js diagram with html boilerplate +* ``DocstringParameterChecker`` + + * Fixed incorrect classification of Numpy-style docstring as Google-style docstring for + docstrings with property setter documentation. + Docstring classification is now based on the highest amount of matched sections instead + of the order in which the docstring styles were tried. + Other Changes ============= diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 27c2a755ff..bfab0e1e4b 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -172,7 +172,8 @@ def possible_exc_types(node): return set() -def docstringify(docstring, default_type="default"): +def docstringify(docstring: str, default_type: str = "default") -> "Docstring": + best_match = (0, DOCSTRING_TYPES.get(default_type, Docstring)(docstring)) for docstring_type in ( SphinxDocstring, EpytextDocstring, @@ -180,11 +181,11 @@ def docstringify(docstring, default_type="default"): NumpyDocstring, ): instance = docstring_type(docstring) - if instance.is_valid(): - return instance + matching_sections = instance.matching_sections() + if matching_sections > best_match[0]: + best_match = (matching_sections, instance) - docstring_type = DOCSTRING_TYPES.get(default_type, Docstring) - return docstring_type(docstring) + return best_match[1] class Docstring: @@ -210,8 +211,9 @@ def __init__(self, doc): def __repr__(self) -> str: return f"<{self.__class__.__name__}:'''{self.doc}'''>" - def is_valid(self): - return False + def matching_sections(self) -> int: + """Returns the number of matching docstring sections""" + return 0 def exceptions(self): return set() @@ -322,13 +324,17 @@ class SphinxDocstring(Docstring): supports_yields = False - def is_valid(self): - return bool( - self.re_param_in_docstring.search(self.doc) - or self.re_raise_in_docstring.search(self.doc) - or self.re_rtype_in_docstring.search(self.doc) - or self.re_returns_in_docstring.search(self.doc) - or self.re_property_type_in_docstring.search(self.doc) + def matching_sections(self) -> int: + """Returns the number of matching docstring sections""" + return sum( + bool(i) + for i in ( + self.re_param_in_docstring.search(self.doc), + self.re_raise_in_docstring.search(self.doc), + self.re_rtype_in_docstring.search(self.doc), + self.re_returns_in_docstring.search(self.doc), + self.re_property_type_in_docstring.search(self.doc), + ) ) def exceptions(self): @@ -526,13 +532,17 @@ class GoogleDocstring(Docstring): supports_yields = True - def is_valid(self): - return bool( - self.re_param_section.search(self.doc) - or self.re_raise_section.search(self.doc) - or self.re_returns_section.search(self.doc) - or self.re_yields_section.search(self.doc) - or self.re_property_returns_line.search(self._first_line()) + def matching_sections(self) -> int: + """Returns the number of matching docstring sections""" + return sum( + bool(i) + for i in ( + self.re_param_section.search(self.doc), + self.re_raise_section.search(self.doc), + self.re_returns_section.search(self.doc), + self.re_yields_section.search(self.doc), + self.re_property_returns_line.search(self._first_line()), + ) ) def has_params(self): diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 92e598c5d4..e7b293988c 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -307,7 +307,7 @@ def visit_raise(self, node: nodes.Raise) -> None: func_node = property_ doc = utils.docstringify(func_node.doc, self.config.default_docstring_type) - if not doc.is_valid(): + if not doc.matching_sections(): if doc.doc: self._handle_no_raise_doc(expected_excs, func_node) return diff --git a/tests/extensions/test_check_docs.py b/tests/extensions/test_check_docs.py index b0eeeb4838..37a5e62bce 100644 --- a/tests/extensions/test_check_docs.py +++ b/tests/extensions/test_check_docs.py @@ -184,42 +184,6 @@ def do_something(): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_finds_missing_raises_from_setter_numpy(self) -> None: - """Example of a setter having missing raises documentation in - the Numpy style docstring of the property - """ - property_node, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): #@ - '''int: docstring - - Include a "Raises" section so that this is identified - as a Numpy docstring and not a Google docstring. - - Raises - ------ - RuntimeError - Always - ''' - raise RuntimeError() - return 10 - - @foo.setter - def foo(self, value): - raise AttributeError() #@ - """ - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-raises-doc", - node=property_node, - args=("AttributeError",), - ) - ): - self.checker.visit_raise(node) - def test_finds_missing_raises_from_setter_numpy_2(self) -> None: """Example of a setter having missing raises documentation in its own Numpy style docstring of the property diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py index e4a9308bd6..dc42940ea2 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py @@ -1,6 +1,7 @@ """Tests for missing-raises-doc and missing-raises-type-doc for Numpy style docstrings""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring # pylint: disable=unused-argument, try-except-raise, import-outside-toplevel +# pylint: disable=too-few-public-methods, disallowed-name def test_find_missing_numpy_raises(self): # [missing-raises-doc] @@ -129,3 +130,30 @@ def test_find_invalid_missing_numpy_attr_raises(self): from re import error raise error("hi") + + +class Foo: + """test_finds_missing_raises_from_setter_numpy + Example of a setter having missing raises documentation in + the Numpy style docstring of the property + """ + + @property + def foo(self): # [missing-raises-doc] + """int: docstring + + Include a "Raises" section so that this is identified + as a Numpy docstring and not a Google docstring. + + Raises + ------ + RuntimeError + Always + """ + raise RuntimeError() + return 10 # [unreachable] + + @foo.setter + def foo(self, value): + print(self) + raise AttributeError() diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt index b6573e38aa..69dcffb211 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt @@ -1,6 +1,8 @@ -missing-raises-doc:6:0:15:25:test_find_missing_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -unreachable:15:4:15:25:test_find_missing_numpy_raises:Unreachable code:UNDEFINED -unreachable:29:4:29:25:test_find_all_numpy_raises:Unreachable code:UNDEFINED -missing-raises-doc:32:0:45:25:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -missing-raises-doc:48:0:61:25:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED -missing-raises-doc:106:0:116:21:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":UNDEFINED +missing-raises-doc:7:0:16:25:test_find_missing_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +unreachable:16:4:16:25:test_find_missing_numpy_raises:Unreachable code:UNDEFINED +unreachable:30:4:30:25:test_find_all_numpy_raises:Unreachable code:UNDEFINED +missing-raises-doc:33:0:46:25:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:49:0:62:25:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED +missing-raises-doc:107:0:117:21:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":UNDEFINED +missing-raises-doc:142:4:154:17:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +unreachable:154:8:154:17:Foo.foo:Unreachable code:UNDEFINED From f8b23eb9cccf4bda452bf56dc1c71b8a046e571b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 12 Dec 2021 23:26:43 +0100 Subject: [PATCH 029/357] Move ``Numpy`` tests from ``TestParamDocChecker`` to functional tests (#5507) --- tests/extensions/test_check_docs.py | 140 ------------------ .../raise/missing_raises_doc_Numpy.py | 60 +++++++- .../raise/missing_raises_doc_Numpy.txt | 19 ++- .../missing_return_doc_required_Numpy.py | 59 +++++++- .../missing_return_doc_required_Numpy.txt | 7 + 5 files changed, 134 insertions(+), 151 deletions(-) diff --git a/tests/extensions/test_check_docs.py b/tests/extensions/test_check_docs.py index 37a5e62bce..aab3bd761e 100644 --- a/tests/extensions/test_check_docs.py +++ b/tests/extensions/test_check_docs.py @@ -184,146 +184,6 @@ def do_something(): #@ with self.assertNoMessages(): self.checker.visit_functiondef(node) - def test_finds_missing_raises_from_setter_numpy_2(self) -> None: - """Example of a setter having missing raises documentation in - its own Numpy style docstring of the property - """ - setter_node, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): - '''int: docstring ... - - Raises - ------ - RuntimeError - Always - ''' - raise RuntimeError() - return 10 - - @foo.setter - def foo(self, value): #@ - '''setter docstring ... - - Raises - ------ - RuntimeError - Never - ''' - if True: - raise AttributeError() #@ - raise RuntimeError() - """ - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-raises-doc", node=setter_node, args=("AttributeError",) - ) - ): - self.checker.visit_raise(node) - - def test_finds_property_return_type_numpy(self) -> None: - """Example of a property having return documentation in - a numpy style docstring - """ - node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): #@ - '''int: docstring ... - - Raises - ------ - RuntimeError - Always - ''' - raise RuntimeError() - return 10 - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - @set_config(accept_no_return_doc="no") - def test_finds_missing_property_return_type_numpy(self) -> None: - """Example of a property having return documentation in - a numpy style docstring - """ - property_node, node = astroid.extract_node( - """ - class Foo(object): - @property - def foo(self): #@ - '''docstring ... - - Raises - ------ - RuntimeError - Always - ''' - raise RuntimeError() - return 10 #@ - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-return-type-doc", node=property_node) - ): - self.checker.visit_return(node) - - @set_config(accept_no_return_doc="no") - def test_ignores_non_property_return_type_numpy(self) -> None: - """Example of a class function trying to use `type` as return - documentation in a numpy style docstring - """ - func_node, node = astroid.extract_node( - """ - class Foo(object): - def foo(self): #@ - '''int: docstring ... - - Raises - ------ - RuntimeError - Always - ''' - raise RuntimeError() - return 10 #@ - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-return-doc", node=func_node), - MessageTest(msg_id="missing-return-type-doc", node=func_node), - ): - self.checker.visit_return(node) - - @set_config(accept_no_return_doc="no") - def test_non_property_annotation_return_type_numpy(self) -> None: - """Example of a class function trying to use `type` as return - documentation in a numpy style docstring - """ - func_node, node = astroid.extract_node( - """ - class Foo(object): - def foo(self) -> int: #@ - '''int: docstring ... - - Raises - ------ - RuntimeError - Always - ''' - raise RuntimeError() - return 10 #@ - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-return-doc", node=func_node) - ): - self.checker.visit_return(node) - @set_config_directly(no_docstring_rgx=re.compile(r"^_(?!_).*$")) def test_skip_no_docstring_rgx(self) -> None: """Example of a function that matches the default 'no-docstring-rgx' config option diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py index dc42940ea2..8cf8e041f1 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py @@ -1,7 +1,11 @@ -"""Tests for missing-raises-doc and missing-raises-type-doc for Numpy style docstrings""" +"""Tests for missing-raises-doc and missing-raises-type-doc for Numpy style docstrings + +Styleguide: +https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard +""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring # pylint: disable=unused-argument, try-except-raise, import-outside-toplevel -# pylint: disable=too-few-public-methods, disallowed-name +# pylint: disable=too-few-public-methods, disallowed-name, using-constant-test def test_find_missing_numpy_raises(self): # [missing-raises-doc] @@ -157,3 +161,55 @@ def foo(self): # [missing-raises-doc] def foo(self, value): print(self) raise AttributeError() + + +class Foo: + """test_finds_missing_raises_from_setter_numpy_2 + Example of a setter having missing raises documentation in + its own Numpy style docstring of the property + """ + + @property + def foo(self): + """int: docstring ... + + Raises + ------ + RuntimeError + Always + """ + raise RuntimeError() + return 10 # [unreachable] + + @foo.setter + def foo(self, value): # [missing-raises-doc] + """setter docstring ... + + Raises + ------ + RuntimeError + Never + """ + print(self) + if True: + raise AttributeError() + raise RuntimeError() + + +class Foo: + """test_finds_property_return_type_numpy + Example of a property having return documentation in + a numpy style docstring + """ + + @property + def foo(self): + """int: docstring ... + + Raises + ------ + RuntimeError + Always + """ + raise RuntimeError() + return 10 # [unreachable] diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt index 69dcffb211..8e63c4d9be 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt @@ -1,8 +1,11 @@ -missing-raises-doc:7:0:16:25:test_find_missing_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -unreachable:16:4:16:25:test_find_missing_numpy_raises:Unreachable code:UNDEFINED -unreachable:30:4:30:25:test_find_all_numpy_raises:Unreachable code:UNDEFINED -missing-raises-doc:33:0:46:25:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -missing-raises-doc:49:0:62:25:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED -missing-raises-doc:107:0:117:21:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":UNDEFINED -missing-raises-doc:142:4:154:17:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED -unreachable:154:8:154:17:Foo.foo:Unreachable code:UNDEFINED +missing-raises-doc:11:0:20:25:test_find_missing_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +unreachable:20:4:20:25:test_find_missing_numpy_raises:Unreachable code:UNDEFINED +unreachable:34:4:34:25:test_find_all_numpy_raises:Unreachable code:UNDEFINED +missing-raises-doc:37:0:50:25:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:53:0:66:25:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED +missing-raises-doc:111:0:121:21:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":UNDEFINED +missing-raises-doc:146:4:158:17:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +unreachable:158:8:158:17:Foo.foo:Unreachable code:UNDEFINED +unreachable:182:8:182:17:Foo.foo:Unreachable code:UNDEFINED +missing-raises-doc:185:4:196:28:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +unreachable:215:8:215:17:Foo.foo:Unreachable code:UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.py b/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.py index 9b160c1774..8cc59f0ac1 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.py +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.py @@ -1,7 +1,7 @@ """Tests for missing-return-doc and missing-return-type-doc for Numpy style docstrings with accept-no-returns-doc = no""" # pylint: disable=function-redefined, invalid-name, undefined-variable, missing-function-docstring -# pylint: disable=unused-argument +# pylint: disable=unused-argument, too-few-public-methods def my_func(self, doc_type): # [missing-return-doc] @@ -38,3 +38,60 @@ def my_func(self): # [missing-return-doc] list(:class:`mymodule.Class`) """ return [mymodule.Class()] + + +class Foo: + """test_finds_missing_property_return_type_numpy + Example of a property having return documentation in + a numpy style docstring + """ + + @property + def foo_prop(self): # [missing-return-type-doc] + """docstring ... + + Raises + ------ + RuntimeError + Always + """ + raise RuntimeError() + return 10 # [unreachable] + + +class Foo: + """test_ignores_non_property_return_type_numpy + Example of a class function trying to use `type` as return + documentation in a numpy style docstring + """ + + def foo_method(self): # [missing-return-doc, missing-return-type-doc] + """int: docstring ... + + Raises + ------ + RuntimeError + Always + """ + print(self) + raise RuntimeError() + return 10 # [unreachable] + + +class Foo: + """test_non_property_annotation_return_type_numpy + Example of a class function trying to use `type` as return + documentation in a numpy style docstring + """ + + def foo_method(self) -> int: # [missing-return-doc] + """int: docstring ... + + Raises + ------ + RuntimeError + Always + """ + print(self) + raise RuntimeError() + return 10 # [unreachable] diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt b/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt index e3872d206c..f3dc18f8bb 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt @@ -2,3 +2,10 @@ missing-return-doc:7:0:19:16:my_func:Missing return documentation:UNDEFINED missing-return-doc:22:0:30:16:my_func:Missing return documentation:UNDEFINED missing-return-type-doc:22:0:30:16:my_func:Missing return type documentation:UNDEFINED missing-return-doc:33:0:40:29:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:50:4:59:17:Foo.foo_prop:Missing return type documentation:UNDEFINED +unreachable:59:8:59:17:Foo.foo_prop:Unreachable code:UNDEFINED +missing-return-doc:68:4:78:17:Foo.foo_method:Missing return documentation:UNDEFINED +missing-return-type-doc:68:4:78:17:Foo.foo_method:Missing return type documentation:UNDEFINED +unreachable:78:8:78:17:Foo.foo_method:Unreachable code:UNDEFINED +missing-return-doc:87:4:97:17:Foo.foo_method:Missing return documentation:UNDEFINED +unreachable:97:8:97:17:Foo.foo_method:Unreachable code:UNDEFINED From 6c6a7aa36da74a35ac6e2ff543c10457c008f9e7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 13 Dec 2021 03:05:58 -0500 Subject: [PATCH 030/357] Emit `used-before-assignment` in final or except blocks where try statements could have failed (#5384) * Emit `used-before-assignment` in final or except blocks where try statements could have failed Fix #85, #2615 --- ChangeLog | 5 ++ doc/whatsnew/2.13.rst | 5 ++ pylint/checkers/variables.py | 54 ++++++++++++++++--- .../u/use/used_before_assignment_issue2615.py | 9 ++++ .../use/used_before_assignment_issue2615.txt | 1 + .../u/use/used_before_assignment_issue85.py | 9 ++++ .../u/use/used_before_assignment_issue85.txt | 1 + 7 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 tests/functional/u/use/used_before_assignment_issue2615.py create mode 100644 tests/functional/u/use/used_before_assignment_issue2615.txt create mode 100644 tests/functional/u/use/used_before_assignment_issue85.py create mode 100644 tests/functional/u/use/used_before_assignment_issue85.txt diff --git a/ChangeLog b/ChangeLog index 738d8acda9..a90ade1cc0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,11 @@ Release date: TBA Closes #4761 +* ``used-before-assignment`` now considers that assignments in a try block + may not have occurred when the except or finally blocks are executed. + + Closes #85, #2615 + * ``used-before-assignment`` now checks names in try blocks. * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 001b6b9762..b3372278d0 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -48,6 +48,11 @@ Other Changes Closes #4761 +* ``used-before-assignment`` now considers that assignments in a try block + may not have occurred when the except or finally blocks are executed. + + Closes #85, #2615 + * ``used-before-assignment`` now checks names in try blocks. * Require Python ``3.6.2`` to run pylint. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 13b8589afb..0a4a21c182 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -405,7 +405,10 @@ def _has_locals_call_after_node(stmt, scope): "E0601": ( "Using variable %r before assignment", "used-before-assignment", - "Used when a local variable is accessed before its assignment.", + "Emitted when a local variable is accessed before its assignment took place. " + "Assignments in try blocks are assumed not to have occurred when evaluating " + "associated except/finally blocks. Assignments in except blocks are assumed " + "not to have occurred when evaluating statements outside the block.", ), "E0602": ( "Undefined variable %r", @@ -580,12 +583,10 @@ def consumed(self): def consumed_uncertain(self) -> DefaultDict[str, List[nodes.NodeNG]]: """ Retrieves nodes filtered out by get_next_to_consume() that may not - have executed, such as statements in except blocks. Checkers that - want to treat the statements as executed (e.g. for unused-variable) - may need to add them back. - - TODO: A pending PR will extend this to nodes in try blocks when - evaluating their corresponding except and finally blocks. + have executed, such as statements in except blocks, or statements + in try blocks (when evaluating their corresponding except and finally + blocks). Checkers that want to treat the statements as executed + (e.g. for unused-variable) may need to add them back. """ return self._atomic.consumed_uncertain @@ -617,6 +618,7 @@ def get_next_to_consume(self, node): name = node.name parent_node = node.parent found_nodes = self.to_consume.get(name) + node_statement = node.statement(future=True) if ( found_nodes and isinstance(parent_node, nodes.Assign) @@ -662,6 +664,44 @@ def get_next_to_consume(self, node): self.consumed_uncertain[node.name] += difference found_nodes = filtered_nodes + # If this node is in a Finally block of a Try/Finally, + # filter out assignments in the try portion, assuming they may fail + if ( + found_nodes + and isinstance(node_statement.parent, nodes.TryFinally) + and node_statement in node_statement.parent.finalbody + ): + filtered_nodes = [ + n + for n in found_nodes + if not ( + n.statement(future=True).parent is node_statement.parent + and n.statement(future=True) in n.statement(future=True).parent.body + ) + ] + filtered_nodes_set = set(filtered_nodes) + difference = [n for n in found_nodes if n not in filtered_nodes_set] + self.consumed_uncertain[node.name] += difference + found_nodes = filtered_nodes + + # If this node is in an ExceptHandler, + # filter out assignments in the try portion, assuming they may fail + if found_nodes and isinstance(node_statement.parent, nodes.ExceptHandler): + filtered_nodes = [ + n + for n in found_nodes + if not ( + isinstance(n.statement(future=True).parent, nodes.TryExcept) + and n.statement(future=True) in n.statement(future=True).parent.body + and node_statement.parent + in n.statement(future=True).parent.handlers + ) + ] + filtered_nodes_set = set(filtered_nodes) + difference = [n for n in found_nodes if n not in filtered_nodes_set] + self.consumed_uncertain[node.name] += difference + found_nodes = filtered_nodes + return found_nodes diff --git a/tests/functional/u/use/used_before_assignment_issue2615.py b/tests/functional/u/use/used_before_assignment_issue2615.py new file mode 100644 index 0000000000..912c713878 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_issue2615.py @@ -0,0 +1,9 @@ +"""https://github.com/PyCQA/pylint/issues/2615""" +def main(): + """When evaluating except blocks, assume try statements fail.""" + try: + res = 1 / 0 + res = 42 + except ZeroDivisionError: + print(res) # [used-before-assignment] + print(res) diff --git a/tests/functional/u/use/used_before_assignment_issue2615.txt b/tests/functional/u/use/used_before_assignment_issue2615.txt new file mode 100644 index 0000000000..ce6e4b9d06 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_issue2615.txt @@ -0,0 +1 @@ +used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED diff --git a/tests/functional/u/use/used_before_assignment_issue85.py b/tests/functional/u/use/used_before_assignment_issue85.py new file mode 100644 index 0000000000..58d8e38d85 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_issue85.py @@ -0,0 +1,9 @@ +"""https://github.com/PyCQA/pylint/issues/85""" +def main(): + """When evaluating finally blocks, assume try statements fail.""" + try: + res = 1 / 0 + res = 42 + finally: + print(res) # [used-before-assignment] + print(res) diff --git a/tests/functional/u/use/used_before_assignment_issue85.txt b/tests/functional/u/use/used_before_assignment_issue85.txt new file mode 100644 index 0000000000..ce6e4b9d06 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_issue85.txt @@ -0,0 +1 @@ +used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED From df99320cae2f0727c8e105a326d2d307b7d42a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 12:58:53 +0100 Subject: [PATCH 031/357] Move tests from ``TestParamDocChecker`` to functional tests (#5509) Co-authored-by: Pierre Sassoulas --- tests/extensions/test_check_docs.py | 301 ------------------ .../docparams/parameter/missing_param_doc.py | 12 + .../docparams/parameter/missing_param_doc.rc | 7 + .../parameter/missing_param_doc_required.py | 51 +++ .../parameter/missing_param_doc_required.rc | 7 + .../parameter/missing_param_doc_required.txt | 3 + .../missing_param_doc_required_min_length.py | 9 + .../missing_param_doc_required_min_length.rc | 7 + ...aram_doc_required_no_doc_rgx_check_init.py | 18 ++ ...aram_doc_required_no_doc_rgx_check_init.rc | 7 + ...ram_doc_required_no_doc_rgx_check_init.txt | 1 + ...aram_doc_required_no_doc_rgx_check_none.py | 16 + ...aram_doc_required_no_doc_rgx_check_none.rc | 7 + ...g_param_doc_required_no_doc_rgx_default.py | 11 + ...g_param_doc_required_no_doc_rgx_default.rc | 6 + ..._param_doc_required_no_doc_rgx_test_all.py | 28 ++ ..._param_doc_required_no_doc_rgx_test_all.rc | 7 + ...param_doc_required_no_doc_rgx_test_all.txt | 1 + .../ext/docparams/raise/missing_raises_doc.py | 10 + .../docparams/raise/missing_raises_doc.txt | 8 +- 20 files changed, 212 insertions(+), 305 deletions(-) delete mode 100644 tests/extensions/test_check_docs.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required.txt create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.py create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.rc create mode 100644 tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt diff --git a/tests/extensions/test_check_docs.py b/tests/extensions/test_check_docs.py deleted file mode 100644 index aab3bd761e..0000000000 --- a/tests/extensions/test_check_docs.py +++ /dev/null @@ -1,301 +0,0 @@ -# Copyright (c) 2014-2015 Bruno Daniel -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2016-2019 Ashley Whetter -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2017 Mitar -# Copyright (c) 2017 John Paraskevopoulos -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Adrian Chirieac -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Luigi -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> -# Copyright (c) 2021 Ville Skyttä -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Logan Miller <14319179+komodo472@users.noreply.github.com> - -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE - -"""Unit tests for the pylint checkers in :mod:`pylint.extensions.check_docs`, -in particular the parameter documentation checker `DocstringChecker` -""" - - -import re - -import astroid -from astroid import nodes - -from pylint.extensions.docparams import DocstringParameterChecker -from pylint.testutils import CheckerTestCase, MessageTest, set_config -from pylint.testutils.decorator import set_config_directly - - -class TestParamDocChecker(CheckerTestCase): - """Tests for pylint_plugin.ParamDocChecker""" - - CHECKER_CLASS = DocstringParameterChecker - CONFIG = { - "accept_no_param_doc": False, - "no_docstring_rgx": re.compile("^$"), - "docstring_min_length": -1, - } - - @set_config(accept_no_param_doc=True) - def test_tolerate_no_param_documentation_at_all(self) -> None: - """Example of a function with no parameter documentation at all - - No error message is emitted. - """ - node = astroid.extract_node( - """ - def function_foo(x, y): - '''docstring ... - - missing parameter documentation''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_don_t_tolerate_no_param_documentation_at_all(self) -> None: - """Example of a function with no parameter documentation at all - - Missing documentation error message is emitted. - """ - node = astroid.extract_node( - """ - def function_foo(x, y): - '''docstring ... - - missing parameter documentation''' - pass - """ - ) - with self.assertAddsMessages( - MessageTest(msg_id="missing-any-param-doc", node=node, args=(node.name)), - ): - self.checker.visit_functiondef(node) - - def test_see_tolerate_no_param_documentation_at_all(self) -> None: - """Example for the usage of "For the parameters, see" - to suppress missing-param warnings. - """ - node = astroid.extract_node( - """ - def function_foo(x, y): - '''docstring ... - - For the parameters, see :func:`blah` - ''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def _visit_methods_of_class(self, node: nodes.ClassDef) -> None: - """Visit all methods of a class node - - :param node: class node - :type node: :class:`nodes.Class` - """ - for body_item in node.body: - if isinstance(body_item, nodes.FunctionDef) and hasattr(body_item, "name"): - self.checker.visit_functiondef(body_item) - - def test_see_sentence_for_constr_params_in_class(self) -> None: - """Example usage of "For the parameters, see" in class docstring""" - node = astroid.extract_node( - """ - class ClassFoo(object): - '''docstring foo - - For the parameters, see :func:`bla` - ''' - - def __init__(self, x, y): - '''init''' - pass - - """ - ) - with self.assertNoMessages(): - self._visit_methods_of_class(node) - - def test_see_sentence_for_constr_params_in_init(self) -> None: - """Example usage of "For the parameters, see" in init docstring""" - node = astroid.extract_node( - """ - class ClassFoo(object): - '''foo''' - - def __init__(self, x, y): - '''docstring foo constructor - - For the parameters, see :func:`bla` - ''' - pass - - """ - ) - with self.assertNoMessages(): - self._visit_methods_of_class(node) - - def test_kwonlyargs_are_taken_in_account(self) -> None: - node = astroid.extract_node( - ''' - def my_func(arg, *, kwonly, missing_kwonly): - """The docstring - - :param int arg: The argument. - :param bool kwonly: A keyword-arg. - """ - ''' - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-param-doc", node=node, args=("missing_kwonly",) - ), - MessageTest(msg_id="missing-type-doc", node=node, args=("missing_kwonly",)), - ): - self.checker.visit_functiondef(node) - - def test_finds_short_name_exception(self) -> None: - node = astroid.extract_node( - ''' - from fake_package import BadError - - def do_something(): #@ - """Do something. - - Raises: - ~fake_package.exceptions.BadError: When something bad happened. - """ - raise BadError("A bad thing happened.") - ''' - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - @set_config_directly(no_docstring_rgx=re.compile(r"^_(?!_).*$")) - def test_skip_no_docstring_rgx(self) -> None: - """Example of a function that matches the default 'no-docstring-rgx' config option - - No error message is emitted. - """ - node = astroid.extract_node( - """ - def _private_function_foo(x, y): - '''docstring ... - - missing parameter documentation''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) - - def test_all_docstring_rgx(self) -> None: - """Function that matches "check all functions" 'no-docstring-rgx' config option - No error message is emitted. - """ - node = astroid.extract_node( - """ - #pylint disable=missing-module-docstring, too-few-public-methods, - class MyClass: - def __init__(self, my_param: int) -> None: - ''' - My init docstring - :param my_param: My first param - ''' - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node.body[0]) - - def test_fail_empty_docstring_rgx(self) -> None: - """Function that matches "check all functions" 'no-docstring-rgx' config option - An error message is emitted. - """ - node = astroid.extract_node( - """ - #pylint disable=missing-module-docstring, too-few-public-methods, - class MyClass: - def __init__(self, my_param: int) -> None: - ''' - My init docstring - ''' - """ - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-param-doc", node=node.body[0], args=("my_param",) - ), - ): - self.checker.visit_functiondef(node.body[0]) - - @set_config_directly(no_docstring_rgx=re.compile("^(?!__init__$)_")) - def test_fail_docparams_check_init(self) -> None: - """Check that __init__ is checked correctly, but other private methods aren't""" - node = astroid.extract_node( - """ - #pylint disable=missing-module-docstring, too-few-public-methods, - class MyClass: - def __init__(self, my_param: int) -> None: - ''' - My init docstring - ''' - - def _private_method(self, my_param: int) -> None: - ''' - My private method - ''' - """ - ) - with self.assertAddsMessages( - MessageTest( - msg_id="missing-param-doc", node=node.body[0], args=("my_param",) - ) - ): - self.checker.visit_functiondef(node.body[0]) - - @set_config_directly(no_docstring_rgx=re.compile("")) - def test_no_docstring_rgx(self) -> None: - """Function that matches "check no functions" 'no-docstring-rgx' config option - No error message is emitted. - """ - node = astroid.extract_node( - """ - #pylint disable=missing-module-docstring, too-few-public-methods, - class MyClass: - def __init__(self, my_param: int) -> None: - ''' - My init docstring - ''' - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node.body[0]) - - @set_config_directly(docstring_min_length=3) - def test_skip_docstring_min_length(self) -> None: - """Example of a function that is less than 'docstring-min-length' config option - - No error message is emitted. - """ - node = astroid.extract_node( - """ - def function_foo(x, y): - '''function is too short and is missing parameter documentation''' - pass - """ - ) - with self.assertNoMessages(): - self.checker.visit_functiondef(node) diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc.py b/tests/functional/ext/docparams/parameter/missing_param_doc.py new file mode 100644 index 0000000000..6039c4b200 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc.py @@ -0,0 +1,12 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = yes +""" +# pylint: disable=invalid-name, unused-argument + + +def test_tolerate_no_param_documentation_at_all(x, y): + """Example of a function with no parameter documentation at all + + No error message is emitted. + + missing parameter documentation""" diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc.rc b/tests/functional/ext/docparams/parameter/missing_param_doc.rc new file mode 100644 index 0000000000..4a81d2781a --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=yes +docstring-min-length: -1 +no-docstring-rgx=^$ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required.py new file mode 100644 index 0000000000..0726455051 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required.py @@ -0,0 +1,51 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = no +""" +# pylint: disable=invalid-name, unused-argument, too-few-public-methods + + +def test_don_t_tolerate_no_param_documentation_at_all(x, y): # [missing-any-param-doc] + """Example of a function with no parameter documentation at all + + Missing documentation error message is emitted. + + missing parameter documentation""" + + +def test_see_tolerate_no_param_documentation_at_all(x, y): + """Example for the usage of "For the parameters, see" + to suppress missing-param warnings. + + For the parameters, see :func:`blah` + """ + + +class ClassFoo: + """Example usage of "For the parameters, see" in init docstring""" + + def __init__(self, x, y): + """docstring foo constructor + + For the parameters, see :func:`bla` + """ + + +class ClassFooTwo: + """test_see_sentence_for_constr_params_in_class + Example usage of "For the parameters, see" in class docstring + + For the parameters, see :func:`bla` + """ + + def __init__(self, x, y): + """init""" + + +def test_kwonlyargs_are_taken_in_account( # [missing-param-doc, missing-type-doc] + arg, *, kwonly, missing_kwonly +): + """The docstring + + :param int arg: The argument. + :param bool kwonly: A keyword-arg. + """ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required.rc new file mode 100644 index 0000000000..24cdf9adaf --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length: -1 +no-docstring-rgx=^$ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt new file mode 100644 index 0000000000..e1672e427c --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt @@ -0,0 +1,3 @@ +missing-any-param-doc:7:0:12:38:test_don_t_tolerate_no_param_documentation_at_all:"Missing any documentation in ""test_don_t_tolerate_no_param_documentation_at_all""":UNDEFINED +missing-param-doc:44:0:51:7:test_kwonlyargs_are_taken_in_account:"""missing_kwonly"" missing in parameter documentation":UNDEFINED +missing-type-doc:44:0:51:7:test_kwonlyargs_are_taken_in_account:"""missing_kwonly"" missing in parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.py new file mode 100644 index 0000000000..765bb2d6c8 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.py @@ -0,0 +1,9 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = no and docstring-min-length = 3 +""" +# pylint: disable=invalid-name, unused-argument + +# Example of a function that is less than 'docstring-min-length' config option +# No error message is emitted. +def test_skip_docstring_min_length(x, y): + """function is too short and is missing parameter documentation""" diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.rc new file mode 100644 index 0000000000..a0538bf1b9 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_min_length.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length=3 +no-docstring-rgx=^$ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.py new file mode 100644 index 0000000000..1d6d2c70bc --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.py @@ -0,0 +1,18 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = no and no-docstring-rgx = ^(?!__init__$)_ +""" +# pylint: disable=invalid-name, unused-argument, too-few-public-methods, missing-class-docstring + + +# test_fail_docparams_check_init +# Check that __init__ is checked correctly, but other private methods aren't +class MyClass: + def __init__(self, my_param: int) -> None: # [missing-param-doc] + """ + My init docstring + """ + + def _private_method(self, my_param: int) -> None: + """ + My private method + """ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.rc new file mode 100644 index 0000000000..3cba99ef40 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length=-1 +no-docstring-rgx=^(?!__init__$)_ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt new file mode 100644 index 0000000000..7cebb0a3cd --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt @@ -0,0 +1 @@ +missing-param-doc:10:4:13:11:MyClass.__init__:"""my_param"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.py new file mode 100644 index 0000000000..ca6eb2f7ea --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.py @@ -0,0 +1,16 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = no and no-docstring-rgx = "" +""" +# pylint: disable=invalid-name, unused-argument, too-few-public-methods + + +class MyClass: + """test_no_docstring_rgx + Function that matches "check no functions" 'no-docstring-rgx' config option + No error message is emitted. + """ + + def __init__(self, my_param: int) -> None: + """ + My init docstring + """ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.rc new file mode 100644 index 0000000000..1196467cd4 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_none.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length=-1 +no-docstring-rgx= diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.py new file mode 100644 index 0000000000..1d2daa1a1b --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.py @@ -0,0 +1,11 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = no and the default value of no-docstring-rgx +""" +# pylint: disable=invalid-name, unused-argument + + +def _test_skip_no_docstring_rgx(x, y): + """Example of a function that matches the default 'no-docstring-rgx' config option + + No error message is emitted. + """ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.rc new file mode 100644 index 0000000000..c81f8bd6b2 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_default.rc @@ -0,0 +1,6 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length=-1 diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.py new file mode 100644 index 0000000000..829119a09a --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.py @@ -0,0 +1,28 @@ +"""Tests for missing-param-doc and missing-type-doc for non-specified style docstrings +with accept-no-param-doc = no and no-docstring-rgx = ^$ +""" +# pylint: disable=invalid-name, unused-argument, too-few-public-methods, function-redefined +# pylint: disable=missing-class-docstring + + +class MyClass: + """test_all_docstring_rgx + Function that matches "check all functions" 'no-docstring-rgx' config option + No error message is emitted. + """ + + def __init__(self, my_param: int) -> None: + """ + My init docstring + :param my_param: My first param + """ + + +# test_fail_empty_docstring_rgx +# Function that matches "check all functions" 'no-docstring-rgx' config option +# An error message is emitted. +class MyClass: + def __init__(self, my_param: int) -> None: # [missing-param-doc] + """ + My init docstring + """ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.rc new file mode 100644 index 0000000000..a616b1a66b --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.rc @@ -0,0 +1,7 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-param-doc=no +docstring-min-length=-1 +no-docstring-rgx=^$ diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt new file mode 100644 index 0000000000..d79aa94dd5 --- /dev/null +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt @@ -0,0 +1 @@ +missing-param-doc:25:4:28:11:MyClass.__init__:"""my_param"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc.py b/tests/functional/ext/docparams/raise/missing_raises_doc.py index 280d9f8e7b..6ba112bd5f 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc.py @@ -3,6 +3,7 @@ # pylint: disable=unused-argument, import-error, unused-variable, no-member, try-except-raise import collections +from fake_package import BadError from unknown import Unknown @@ -96,3 +97,12 @@ def test_no_error_notimplemented_documented(): NotImplementedError: When called. """ raise NotImplementedError + + +def test_finds_short_name_exception(): + """Do something. + + Raises: + ~fake_package.exceptions.BadError: When something bad happened. + """ + raise BadError("A bad thing happened.") diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc.txt b/tests/functional/ext/docparams/raise/missing_raises_doc.txt index 6b11b43d0e..6da4d57be8 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc.txt @@ -1,4 +1,4 @@ -unreachable:24:4:24:25:test_ignores_raise_uninferable:Unreachable code:UNDEFINED -missing-raises-doc:27:0:41:25:test_ignores_returns_from_inner_functions:"""RuntimeError"" not documented as being raised":UNDEFINED -unreachable:41:4:41:25:test_ignores_returns_from_inner_functions:Unreachable code:UNDEFINED -raising-bad-type:53:4:53:22:test_ignores_returns_use_only_names:Raising int while only classes or instances are allowed:UNDEFINED +unreachable:25:4:25:25:test_ignores_raise_uninferable:Unreachable code:UNDEFINED +missing-raises-doc:28:0:42:25:test_ignores_returns_from_inner_functions:"""RuntimeError"" not documented as being raised":UNDEFINED +unreachable:42:4:42:25:test_ignores_returns_from_inner_functions:Unreachable code:UNDEFINED +raising-bad-type:54:4:54:22:test_ignores_returns_use_only_names:Raising int while only classes or instances are allowed:UNDEFINED From ee60bc1ae9a2f5329f7649dd4aa4fdad09587afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:41:35 +0100 Subject: [PATCH 032/357] Add a DeprecationWarning to set_config_directly (#5511) --- pylint/testutils/decorator.py | 9 +++++++++ tests/testutils/test_decorator.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/testutils/test_decorator.py diff --git a/pylint/testutils/decorator.py b/pylint/testutils/decorator.py index 87fbd7afa0..4cee70302f 100644 --- a/pylint/testutils/decorator.py +++ b/pylint/testutils/decorator.py @@ -3,6 +3,7 @@ import functools import optparse # pylint: disable=deprecated-module +import warnings from pylint.lint import PyLinter from pylint.testutils.checker_test_case import CheckerTestCase @@ -63,6 +64,14 @@ def set_config_directly(**kwargs): Passing the args and kwargs back to the test function itself allows this decorator to be used on parametrized test cases. """ + # pylint: disable=fixme + # TODO: Remove this function in 2.14 + warnings.warn( + "The set_config_directly decorator will be removed in 2.14. To decorate " + "unittests you can use set_config. If this causes a duplicate KeyError " + "you can consider writing the tests using the functional test framework.", + DeprecationWarning, + ) def _wrapper(fun): @functools.wraps(fun) diff --git a/tests/testutils/test_decorator.py b/tests/testutils/test_decorator.py new file mode 100644 index 0000000000..0bd1fb1199 --- /dev/null +++ b/tests/testutils/test_decorator.py @@ -0,0 +1,15 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + + +import pytest + +from pylint.testutils.decorator import set_config_directly + + +def test_deprecation_of_set_config_directly() -> None: + """Test that the deprecation of set_config_directly works as expected""" + + with pytest.warns(DeprecationWarning) as records: + set_config_directly() + assert len(records) == 1 From 2920947fbd730c39282e2070f073e103f52ae580 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 13 Dec 2021 11:13:16 +0100 Subject: [PATCH 033/357] Style - Remove unrelated error in tests for unnecessary-dict-index-lookup So it's easier to focus on the real error we're testing. --- .../unnecessary_dict_index_lookup.py | 45 ++++++++++-------- .../unnecessary_dict_index_lookup.txt | 46 +++++++++---------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py index 56e65e5986..f594d9e0f8 100644 --- a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py +++ b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py @@ -11,20 +11,23 @@ print(a_dict[k]) # Should not emit warning, v != a_dict[k] for k, v in b_dict.items(): + print(k) k = "another key" print(b_dict[k]) # This is fine, key reassigned # Tests on comprehensions -{v: 1 for k, v in a_dict.items() if a_dict[k]} # [unnecessary-dict-index-lookup] -{v: 1 for k, v in a_dict.items() if k} # This is fine, no indexing -{a_dict[k]: 1 for k, v in a_dict.items() if k} # [unnecessary-dict-index-lookup] -{a_dict[k]: 1 for k, v in a_dict.items() if a_dict[k]} # [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +A = {v: 1 for k, v in a_dict.items() if a_dict[k]} # [unnecessary-dict-index-lookup] +B = {v: 1 for k, v in a_dict.items() if k} # This is fine, no indexing +C = {a_dict[k]: 1 for k, v in a_dict.items() if k} # [unnecessary-dict-index-lookup] +# +1: [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +D = {a_dict[k]: 1 for k, v in a_dict.items() if a_dict[k]} -[v for k, v in a_dict.items() if a_dict[k]] # [unnecessary-dict-index-lookup] -[v for k, v in a_dict.items() if k] # This is fine, no indexing -[a_dict[k] for k, v in a_dict.items() if k] # [unnecessary-dict-index-lookup] -[a_dict[k] for k, v in a_dict.items() if a_dict[k]] # [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +E = [v for k, v in a_dict.items() if a_dict[k]] # [unnecessary-dict-index-lookup] +F = [v for k, v in a_dict.items() if k] # This is fine, no indexing +G = [a_dict[k] for k, v in a_dict.items() if k] # [unnecessary-dict-index-lookup] +# +1: [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +H = [a_dict[k] for k, v in a_dict.items() if a_dict[k]] # Tests on dict attribute of a class @@ -35,20 +38,22 @@ class Foo: for k, v in Foo.c_dict.items(): print(b_dict[k]) # Should not emit warning, accessing other dictionary print(Foo.c_dict[k]) # [unnecessary-dict-index-lookup] - unnecessary = 0 # pylint: disable=invalid-name + unnecessary = 0 # pylint: disable=invalid-name unnecessary += Foo.c_dict[k] # [unnecessary-dict-index-lookup] Foo.c_dict[k] += v # key access necessary # Tests on comprehensions -{v: 1 for k, v in Foo.c_dict.items() if Foo.c_dict[k]} # [unnecessary-dict-index-lookup] -{v: 1 for k, v in Foo.c_dict.items() if k} # This is fine, no indexing -{Foo.c_dict[k]: 1 for k, v in Foo.c_dict.items() if k} # [unnecessary-dict-index-lookup] -{Foo.c_dict[k]: 1 for k, v in Foo.c_dict.items() if Foo.c_dict[k]} # [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] - -[v for k, v in Foo.c_dict.items() if Foo.c_dict[k]] # [unnecessary-dict-index-lookup] -[v for k, v in Foo.c_dict.items() if k] # This is fine, no indexing -[Foo.c_dict[k] for k, v in Foo.c_dict.items() if k] # [unnecessary-dict-index-lookup] -[Foo.c_dict[k] for k, v in Foo.c_dict.items() if Foo.c_dict[k]] # [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +S = {v: 1 for k, v in Foo.c_dict.items() if Foo.c_dict[k]} # [unnecessary-dict-index-lookup] +J = {v: 1 for k, v in Foo.c_dict.items() if k} # This is fine, no indexing +K = {Foo.c_dict[k]: 1 for k, v in Foo.c_dict.items() if k} # [unnecessary-dict-index-lookup] +# +1: [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +L = {Foo.c_dict[k]: 1 for k, v in Foo.c_dict.items() if Foo.c_dict[k]} + +M = [v for k, v in Foo.c_dict.items() if Foo.c_dict[k]] # [unnecessary-dict-index-lookup] +N = [v for k, v in Foo.c_dict.items() if k] # This is fine, no indexing +T = [Foo.c_dict[k] for k, v in Foo.c_dict.items() if k] # [unnecessary-dict-index-lookup] +# +1: [unnecessary-dict-index-lookup, unnecessary-dict-index-lookup] +P = [Foo.c_dict[k] for k, v in Foo.c_dict.items() if Foo.c_dict[k]] # Test assigning d.items() to a single variable d = {1: "a", 2: "b"} @@ -56,8 +61,8 @@ class Foo: print(item[0]) print(d[item[0]]) # [unnecessary-dict-index-lookup] -[item[0] for item in d.items()] -[d[item[0]] for item in d.items()] # [unnecessary-dict-index-lookup] +Q = [item[0] for item in d.items()] +R = [d[item[0]] for item in d.items()] # [unnecessary-dict-index-lookup] # Reassigning single var for item in d.items(): diff --git a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt index 09ad9b6571..6ca3008d0c 100644 --- a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt +++ b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.txt @@ -1,24 +1,24 @@ unnecessary-dict-index-lookup:7:10:7:19::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:19:36:19:45::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:21:1:21:10::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:22:1:22:10::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:22:44:22:53::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:24:33:24:42::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:26:1:26:10::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:27:1:27:10::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:27:41:27:50::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:37:10:37:23::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:39:19:39:32::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:43:40:43:53::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:45:1:45:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:46:1:46:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:46:52:46:65::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:48:37:48:50::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:50:1:50:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:51:1:51:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:51:49:51:62::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED -unnecessary-dict-index-lookup:57:10:57:20::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED -unnecessary-dict-index-lookup:60:1:60:11::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED -unnecessary-dict-index-lookup:65:10:65:20::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED -unnecessary-dict-index-lookup:82:14:82:18::Unnecessary dictionary index lookup, use '_' instead:UNDEFINED -unnecessary-dict-index-lookup:101:12:101:27::Unnecessary dictionary index lookup, use 'val' instead:UNDEFINED +unnecessary-dict-index-lookup:20:40:20:49::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:22:5:22:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:24:5:24:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:24:48:24:57::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:26:37:26:46::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:28:5:28:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:30:5:30:14::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:30:45:30:54::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:40:10:40:23::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:42:19:42:32::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:46:44:46:57::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:48:5:48:18::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:50:5:50:18::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:50:56:50:69::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:52:41:52:54::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:54:5:54:18::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:56:5:56:18::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:56:53:56:66::Unnecessary dictionary index lookup, use 'v' instead:UNDEFINED +unnecessary-dict-index-lookup:62:10:62:20::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED +unnecessary-dict-index-lookup:65:5:65:15::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED +unnecessary-dict-index-lookup:70:10:70:20::Unnecessary dictionary index lookup, use 'item[1]' instead:UNDEFINED +unnecessary-dict-index-lookup:87:14:87:18::Unnecessary dictionary index lookup, use '_' instead:UNDEFINED +unnecessary-dict-index-lookup:106:12:106:27::Unnecessary dictionary index lookup, use 'val' instead:UNDEFINED From 736c0be70590dfafaf0905a670505e4ff380d5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 14:14:54 +0100 Subject: [PATCH 034/357] Unify validation of functional test option files (#5510) --- pylint/testutils/functional/test_file.py | 13 +++++++------ pylint/testutils/lint_module_test.py | 6 ++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index 8aa0361d45..b25fe509b9 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -27,8 +27,8 @@ class TestFileOptions(TypedDict): max_pyver: Tuple[int, ...] min_pyver_end_position: Tuple[int, ...] requires: List[str] - except_implementations: str # Type is actually comma separated list of string - exclude_platforms: str # Type is actually comma separated list of string + except_implementations: List[str] + exclude_platforms: List[str] # mypy need something literal, we can't create this dynamically from TestFileOptions @@ -39,7 +39,6 @@ class TestFileOptions(TypedDict): "requires", "except_implementations", "exclude_platforms", - "exclude_platforms", } @@ -50,7 +49,9 @@ class FunctionalTestFile: "min_pyver": parse_python_version, "max_pyver": parse_python_version, "min_pyver_end_position": parse_python_version, - "requires": lambda s: s.split(","), + "requires": lambda s: [i.strip() for i in s.split(",")], + "except_implementations": lambda s: [i.strip() for i in s.split(",")], + "exclude_platforms": lambda s: [i.strip() for i in s.split(",")], } def __init__(self, directory: str, filename: str) -> None: @@ -61,8 +62,8 @@ def __init__(self, directory: str, filename: str) -> None: "max_pyver": (4, 0), "min_pyver_end_position": (3, 8), "requires": [], - "except_implementations": "", - "exclude_platforms": "", + "except_implementations": [], + "exclude_platforms": [], } self._parse_options() diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index cbdedeecca..b7e1ffc67f 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -74,14 +74,12 @@ def setUp(self) -> None: pytest.skip(f"Requires {','.join(missing)} to be present.") except_implementations = self._test_file.options["except_implementations"] if except_implementations: - implementations = [i.strip() for i in except_implementations.split(",")] - if platform.python_implementation() in implementations: + if platform.python_implementation() in except_implementations: msg = "Test cannot run with Python implementation %r" pytest.skip(msg % platform.python_implementation()) excluded_platforms = self._test_file.options["exclude_platforms"] if excluded_platforms: - platforms = [p.strip() for p in excluded_platforms.split(",")] - if sys.platform.lower() in platforms: + if sys.platform.lower() in excluded_platforms: pytest.skip(f"Test cannot run on platform {sys.platform!r}") def runTest(self) -> None: From 7d395493c58d6408593d71ffa87a0ee4086c8710 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:05:24 +0000 Subject: [PATCH 035/357] Bump actions/upload-artifact from 2.2.4 to 2.3.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.2.4 to 2.3.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2.2.4...v2.3.0) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ff306bbf18..a49c1dcb27 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -220,7 +220,7 @@ jobs: . venv/bin/activate pytest --benchmark-disable --cov --cov-report= tests/ - name: Upload coverage artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: coverage-${{ matrix.python-version }} path: .coverage @@ -313,7 +313,7 @@ jobs: run: >- echo "::set-output name=datetime::"$(date "+%Y%m%d_%H%M") - name: Upload benchmark artifact - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: benchmark-${{ runner.os }}-${{ matrix.python-version }}_${{ From 8ab1a667fe2d92cd10835f94ae2b5bb7de79b72e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:05:27 +0000 Subject: [PATCH 036/357] Bump actions/download-artifact from 2.0.10 to 2.1.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2.0.10 to 2.1.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2.0.10...v2.1.0) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a49c1dcb27..e560de31cf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -256,7 +256,7 @@ jobs: echo "Failed to restore Python venv from cache" exit 1 - name: Download all coverage artifacts - uses: actions/download-artifact@v2.0.10 + uses: actions/download-artifact@v2.1.0 - name: Combine coverage results run: | . venv/bin/activate From ae4edd2e1fdf523aab794fa2ad9b3083d2722e07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:05:36 +0000 Subject: [PATCH 037/357] Update pytest-xdist requirement from ~=2.4 to ~=2.5 Updates the requirements on [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest-xdist/releases) - [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v2.4.0...v2.5.0) --- updated-dependencies: - dependency-name: pytest-xdist dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 5041a1d3b1..1fd84ce7d4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,7 +7,7 @@ tbump~=6.6.0 pyenchant~=3.2 pytest-cov~=3.0 pytest-profiling~=1.7 -pytest-xdist~=2.4 +pytest-xdist~=2.5 # Type packages for mypy types-pkg_resources==0.1.3 types-toml==0.10.1 From e5cb38f991bf1e9ff5aa7c7f4c81880b6ea08f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 15:01:50 +0100 Subject: [PATCH 038/357] Remove redundant tests in ``TestWhileUsed`` (#5518) --- tests/extensions/test_while_used.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 tests/extensions/test_while_used.py diff --git a/tests/extensions/test_while_used.py b/tests/extensions/test_while_used.py deleted file mode 100644 index 9eab48520f..0000000000 --- a/tests/extensions/test_while_used.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Tests for the pylint checker in :mod:`pylint.extensions.while -""" - -import astroid - -from pylint.extensions.while_used import WhileChecker -from pylint.testutils import CheckerTestCase, MessageTest - - -class TestWhileUsed(CheckerTestCase): - - CHECKER_CLASS = WhileChecker - - def test_while_used(self) -> None: - node = astroid.extract_node( - """ - def f(): - i = 0 - while i < 10: - i += 1 - """ - ).body[1] - - with self.assertAddsMessages(MessageTest("while-used", node=node)): - self.checker.visit_while(node) From bf5217819adbb71bc2c873efa2efb298d12f4731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 15:59:04 +0100 Subject: [PATCH 039/357] Move tests from ``TestComparison`` to functional tests (#5520) * Remove some redundant tests --- tests/checkers/unittest_base.py | 112 -------------------- tests/functional/n/nan_comparison_check.py | 9 +- tests/functional/n/nan_comparison_check.txt | 26 ++--- tests/functional/s/singleton_comparison.py | 2 + tests/functional/s/singleton_comparison.txt | 16 +-- 5 files changed, 31 insertions(+), 134 deletions(-) diff --git a/tests/checkers/unittest_base.py b/tests/checkers/unittest_base.py index 24e17a0a5d..bbe2be2fa5 100644 --- a/tests/checkers/unittest_base.py +++ b/tests/checkers/unittest_base.py @@ -181,118 +181,6 @@ def UPPER(): #@ self.checker.leave_module(func.root) -class TestComparison(CheckerTestCase): - CHECKER_CLASS = base.ComparisonChecker - - def test_comparison(self) -> None: - node = astroid.extract_node("foo == True") - message = MessageTest( - "singleton-comparison", - node=node, - args=( - "'foo == True'", - "'foo is True' if checking for the singleton value True, or 'bool(foo)' if testing for truthiness", - ), - ) - with self.assertAddsMessages(message): - self.checker.visit_compare(node) - - node = astroid.extract_node("foo == False") - message = MessageTest( - "singleton-comparison", - node=node, - args=( - "'foo == False'", - "'foo is False' if checking for the singleton value False, or 'not foo' if testing for falsiness", - ), - ) - with self.assertAddsMessages(message): - self.checker.visit_compare(node) - - node = astroid.extract_node("foo == None") - message = MessageTest( - "singleton-comparison", node=node, args=("'foo == None'", "'foo is None'") - ) - with self.assertAddsMessages(message): - self.checker.visit_compare(node) - - node = astroid.extract_node("foo is float('nan')") - message = MessageTest( - "nan-comparison", - node=node, - args=("'foo is float('nan')'", "'math.isnan(foo)'"), - ) - with self.assertAddsMessages(message): - self.checker.visit_compare(node) - - node = astroid.extract_node( - """ - import numpy - foo != numpy.NaN - """ - ) - message = MessageTest( - "nan-comparison", - node=node, - args=("'foo != numpy.NaN'", "'not math.isnan(foo)'"), - ) - with self.assertAddsMessages(message): - self.checker.visit_compare(node) - - node = astroid.extract_node( - """ - import numpy as nmp - foo is not nmp.NaN - """ - ) - message = MessageTest( - "nan-comparison", - node=node, - args=("'foo is not nmp.NaN'", "'not math.isnan(foo)'"), - ) - with self.assertAddsMessages(message): - self.checker.visit_compare(node) - - node = astroid.extract_node("True == foo") - messages = ( - MessageTest( - "singleton-comparison", - node=node, - args=( - "'True == foo'", - "'True is foo' if checking for the singleton value True, or 'bool(foo)' if testing for truthiness", - ), - ), - ) - with self.assertAddsMessages(*messages): - self.checker.visit_compare(node) - - node = astroid.extract_node("False == foo") - messages = ( - MessageTest( - "singleton-comparison", - node=node, - args=( - "'False == foo'", - "'False is foo' if checking for the singleton value False, or 'not foo' if testing for falsiness", - ), - ), - ) - with self.assertAddsMessages(*messages): - self.checker.visit_compare(node) - - node = astroid.extract_node("None == foo") - messages = ( - MessageTest( - "singleton-comparison", - node=node, - args=("'None == foo'", "'None is foo'"), - ), - ) - with self.assertAddsMessages(*messages): - self.checker.visit_compare(node) - - class TestNamePresets(unittest.TestCase): SNAKE_CASE_NAMES = {"tést_snake_case", "test_snake_case11", "test_https_200"} CAMEL_CASE_NAMES = {"téstCamelCase", "testCamelCase11", "testHTTP200"} diff --git a/tests/functional/n/nan_comparison_check.py b/tests/functional/n/nan_comparison_check.py index b9a720b9a8..99b921cdec 100644 --- a/tests/functional/n/nan_comparison_check.py +++ b/tests/functional/n/nan_comparison_check.py @@ -2,10 +2,12 @@ # pylint: disable=literal-comparison,comparison-with-itself, import-error """Test detection of NaN value comparison.""" import numpy + x = 42 a = x is numpy.NaN # [nan-comparison] b = x == numpy.NaN # [nan-comparison] -c = x == float('nan') # [nan-comparison] +c = x == float("nan") # [nan-comparison] +d = x is float("nan") # [nan-comparison] e = numpy.NaN == numpy.NaN # [nan-comparison] f = x is 1 g = 123 is "123" @@ -13,9 +15,10 @@ i = numpy.NaN != x # [nan-comparison] j = x != numpy.NaN # [nan-comparison] -j1 = x != float('nan') # [nan-comparison] +j1 = x != float("nan") # [nan-comparison] +k = x is not numpy.NaN # [nan-comparison] assert x == numpy.NaN # [nan-comparison] -assert x is not float('nan') # [nan-comparison] +assert x is not float("nan") # [nan-comparison] if x == numpy.NaN: # [nan-comparison] pass z = bool(x is numpy.NaN) # [nan-comparison] diff --git a/tests/functional/n/nan_comparison_check.txt b/tests/functional/n/nan_comparison_check.txt index 162b2c3788..7c15fc952e 100644 --- a/tests/functional/n/nan_comparison_check.txt +++ b/tests/functional/n/nan_comparison_check.txt @@ -1,12 +1,14 @@ -nan-comparison:6:4:6:18::Comparison 'x is numpy.NaN' should be 'math.isnan(x)':UNDEFINED -nan-comparison:7:4:7:18::Comparison 'x == numpy.NaN' should be 'math.isnan(x)':UNDEFINED -nan-comparison:8:4:8:21::Comparison 'x == float('nan')' should be 'math.isnan(x)':UNDEFINED -nan-comparison:9:4:9:26::Comparison 'numpy.NaN == numpy.NaN' should be 'math.isnan(numpy.NaN)':UNDEFINED -nan-comparison:12:4:12:22::Comparison 'numpy.NaN is not x' should be 'not math.isnan(x)':UNDEFINED -nan-comparison:13:4:13:18::Comparison 'numpy.NaN != x' should be 'not math.isnan(x)':UNDEFINED -nan-comparison:15:4:15:18::Comparison 'x != numpy.NaN' should be 'not math.isnan(x)':UNDEFINED -nan-comparison:16:5:16:22::Comparison 'x != float('nan')' should be 'not math.isnan(x)':UNDEFINED -nan-comparison:17:7:17:21::Comparison 'x == numpy.NaN' should be 'math.isnan(x)':UNDEFINED -nan-comparison:18:7:18:28::Comparison 'x is not float('nan')' should be 'not math.isnan(x)':UNDEFINED -nan-comparison:19:3:19:17::Comparison 'x == numpy.NaN' should be 'math.isnan(x)':UNDEFINED -nan-comparison:21:9:21:23::Comparison 'x is numpy.NaN' should be 'math.isnan(x)':UNDEFINED +nan-comparison:7:4:7:18::Comparison 'x is numpy.NaN' should be 'math.isnan(x)':UNDEFINED +nan-comparison:8:4:8:18::Comparison 'x == numpy.NaN' should be 'math.isnan(x)':UNDEFINED +nan-comparison:9:4:9:21::Comparison 'x == float('nan')' should be 'math.isnan(x)':UNDEFINED +nan-comparison:10:4:10:21::Comparison 'x is float('nan')' should be 'math.isnan(x)':UNDEFINED +nan-comparison:11:4:11:26::Comparison 'numpy.NaN == numpy.NaN' should be 'math.isnan(numpy.NaN)':UNDEFINED +nan-comparison:14:4:14:22::Comparison 'numpy.NaN is not x' should be 'not math.isnan(x)':UNDEFINED +nan-comparison:15:4:15:18::Comparison 'numpy.NaN != x' should be 'not math.isnan(x)':UNDEFINED +nan-comparison:17:4:17:18::Comparison 'x != numpy.NaN' should be 'not math.isnan(x)':UNDEFINED +nan-comparison:18:5:18:22::Comparison 'x != float('nan')' should be 'not math.isnan(x)':UNDEFINED +nan-comparison:19:4:19:22::Comparison 'x is not numpy.NaN' should be 'not math.isnan(x)':UNDEFINED +nan-comparison:20:7:20:21::Comparison 'x == numpy.NaN' should be 'math.isnan(x)':UNDEFINED +nan-comparison:21:7:21:28::Comparison 'x is not float('nan')' should be 'not math.isnan(x)':UNDEFINED +nan-comparison:22:3:22:17::Comparison 'x == numpy.NaN' should be 'math.isnan(x)':UNDEFINED +nan-comparison:24:9:24:23::Comparison 'x is numpy.NaN' should be 'math.isnan(x)':UNDEFINED diff --git a/tests/functional/s/singleton_comparison.py b/tests/functional/s/singleton_comparison.py index 771a1d3cb2..8bfd0e8876 100644 --- a/tests/functional/s/singleton_comparison.py +++ b/tests/functional/s/singleton_comparison.py @@ -9,6 +9,8 @@ g = 123 is "123" h = None is x i = None == x # [singleton-comparison] +i1 = True == x # [singleton-comparison] +i2 = False == x # [singleton-comparison] j = x != True # [singleton-comparison] j1 = x != False # [singleton-comparison] diff --git a/tests/functional/s/singleton_comparison.txt b/tests/functional/s/singleton_comparison.txt index 12d3414307..d095331dea 100644 --- a/tests/functional/s/singleton_comparison.txt +++ b/tests/functional/s/singleton_comparison.txt @@ -3,10 +3,12 @@ singleton-comparison:5:4:5:13::Comparison 'x == True' should be 'x is True' if c singleton-comparison:6:4:6:14::Comparison 'x == False' should be 'x is False' if checking for the singleton value False, or 'not x' if testing for falsiness:UNDEFINED singleton-comparison:7:4:7:16::Comparison 'True == True' should be 'True is True' if checking for the singleton value True, or 'bool(True)' if testing for truthiness:UNDEFINED singleton-comparison:11:4:11:13::Comparison 'None == x' should be 'None is x':UNDEFINED -singleton-comparison:13:4:13:13::Comparison 'x != True' should be 'x is not True' if checking for the singleton value True, or 'not x' if testing for falsiness:UNDEFINED -singleton-comparison:14:5:14:15::Comparison 'x != False' should be 'x is not False' if checking for the singleton value False, or 'bool(x)' if testing for truthiness:UNDEFINED -singleton-comparison:15:5:15:14::Comparison 'x != None' should be 'x is not None':UNDEFINED -singleton-comparison:16:7:16:16::Comparison 'x == True' should be 'x is True' if checking for the singleton value True, or 'x' if testing for truthiness:UNDEFINED -singleton-comparison:17:7:17:17::Comparison 'x != False' should be 'x is not False' if checking for the singleton value False, or 'x' if testing for truthiness:UNDEFINED -singleton-comparison:18:3:18:12::Comparison 'x == True' should be 'x is True' if checking for the singleton value True, or 'x' if testing for truthiness:UNDEFINED -singleton-comparison:20:9:20:18::Comparison 'x == True' should be 'x is True' if checking for the singleton value True, or 'x' if testing for truthiness:UNDEFINED +singleton-comparison:12:5:12:14::Comparison 'True == x' should be 'True is x' if checking for the singleton value True, or 'bool(x)' if testing for truthiness:UNDEFINED +singleton-comparison:13:5:13:15::Comparison 'False == x' should be 'False is x' if checking for the singleton value False, or 'not x' if testing for falsiness:UNDEFINED +singleton-comparison:15:4:15:13::Comparison 'x != True' should be 'x is not True' if checking for the singleton value True, or 'not x' if testing for falsiness:UNDEFINED +singleton-comparison:16:5:16:15::Comparison 'x != False' should be 'x is not False' if checking for the singleton value False, or 'bool(x)' if testing for truthiness:UNDEFINED +singleton-comparison:17:5:17:14::Comparison 'x != None' should be 'x is not None':UNDEFINED +singleton-comparison:18:7:18:16::Comparison 'x == True' should be 'x is True' if checking for the singleton value True, or 'x' if testing for truthiness:UNDEFINED +singleton-comparison:19:7:19:17::Comparison 'x != False' should be 'x is not False' if checking for the singleton value False, or 'x' if testing for truthiness:UNDEFINED +singleton-comparison:20:3:20:12::Comparison 'x == True' should be 'x is True' if checking for the singleton value True, or 'x' if testing for truthiness:UNDEFINED +singleton-comparison:22:9:22:18::Comparison 'x == True' should be 'x is True' if checking for the singleton value True, or 'x' if testing for truthiness:UNDEFINED From e3e5aebae7a96eb02b0f32e14f07c873f8d2519f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 15:59:54 +0100 Subject: [PATCH 040/357] Move tests from ``TestConfusingConsecutiveElifChecker`` to functional tests (#5517) * Remove some redundant test Co-authored-by: Pierre Sassoulas --- tests/extensions/test_confusing_elif.py | 198 ------------------ .../ext/confusing_elif/confusing_elif.py | 114 +++++++++- .../ext/confusing_elif/confusing_elif.txt | 4 +- 3 files changed, 111 insertions(+), 205 deletions(-) delete mode 100644 tests/extensions/test_confusing_elif.py diff --git a/tests/extensions/test_confusing_elif.py b/tests/extensions/test_confusing_elif.py deleted file mode 100644 index 236b0c6e6a..0000000000 --- a/tests/extensions/test_confusing_elif.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE - -"""Tests for the pylint checker in :mod:`pylint.extensions.confusing_elif -""" - -import astroid - -from pylint.extensions.confusing_elif import ConfusingConsecutiveElifChecker -from pylint.testutils import CheckerTestCase, MessageTest - -MSG_ID_CONFUSING_CONSECUTIVE_ELIF = "confusing-consecutive-elif" - - -class TestConfusingConsecutiveElifChecker(CheckerTestCase): - """Tests for pylint.extensions.confusing_elif.ConfusingConsecutiveElifChecker""" - - CHECKER_CLASS = ConfusingConsecutiveElifChecker - - def test_triggered_if_if_block_ends_with_elif(self) -> None: - """ - Given an if-elif construct - When the body of the if ends with an elif - Then the message confusing-consecutive-elif must be triggered. - """ - example_code = """ - def foo(a, b, c): - if a > b: #@ - if a > 0: - return a - elif a < 0: - return 0 - elif a > c: #@ - return c - """ - if_node_to_test, elif_node = astroid.extract_node(example_code) - with self.assertAddsMessages( - MessageTest(msg_id=MSG_ID_CONFUSING_CONSECUTIVE_ELIF, node=elif_node) - ): - self.checker.visit_if(if_node_to_test) - - def test_triggered_if_elif_block_ends_with_elif(self) -> None: - """ - Given an if-elif-elif construct - When the body of the first elif ends with an elif - Then the message confusing-consecutive-elif must be triggered. - """ - example_code = """ - def foo(a, b, c): - if a < b: - return b - elif a > b: #@ - if a > 0: - return a - elif a < 0: - return 0 - elif a > c: #@ - return c - """ - if_node_to_test, elif_node = astroid.extract_node(example_code) - with self.assertAddsMessages( - MessageTest(msg_id=MSG_ID_CONFUSING_CONSECUTIVE_ELIF, node=elif_node) - ): - self.checker.visit_if(if_node_to_test) - - def test_triggered_if_block_ends_with_if(self) -> None: - """ - Given an if-elif construct - When the body of the if ends with an if - Then the message confusing-consecutive-elif must be triggered. - """ - example_code = """ - def foo(a, b, c): - if a > b: #@ - if a > 0: - return a - elif a > c: #@ - return c - """ - if_node_to_test, elif_node = astroid.extract_node(example_code) - with self.assertAddsMessages( - MessageTest(msg_id=MSG_ID_CONFUSING_CONSECUTIVE_ELIF, node=elif_node) - ): - self.checker.visit_if(if_node_to_test) - - def test_not_triggered_if_indented_block_ends_with_else(self) -> None: - """ - Given an if-elif construct - When the body of the if ends with an else - Then no message shall be triggered. - """ - example_code = """ - def foo(a, b, c): - if a > b: #@ - if a > 0: - return a - else: - return 0 - elif a > c: - return c - """ - if_node_to_test = astroid.extract_node(example_code) - with self.assertNoMessages(): - self.checker.visit_if(if_node_to_test) - - def test_not_triggered_if_indentend_block_ends_with_call(self) -> None: - """ - Given an if-elif construct - When the body of the if ends with a function call - Then no message shall be triggered. - - Note: There is nothing special about the body ending with a function call. This is just taken as a - representative value for the equivalence class of "every node class unrelated to if/elif/else". - """ - example_code = """ - def foo(a, b, c): - result = None - if a > b: #@ - if a > 0: - result = a - print("result is", result) - elif a > c: - result = c - return result - """ - if_node_to_test = astroid.extract_node(example_code) - with self.assertNoMessages(): - self.checker.visit_if(if_node_to_test) - - def test_not_triggered_if_indented_block_ends_with_ifexp(self) -> None: - """ - Given an if-elif construct - When the body of the if ends with an if expression - Then no message shall be triggered. - """ - example_code = """ - def foo(a, b, c): - result = None - if a > b: #@ - if a > 0: - result = a - result = b if b > c else c - elif a > c: - result = c - return result - """ - if_node_to_test = astroid.extract_node(example_code) - with self.assertNoMessages(): - self.checker.visit_if(if_node_to_test) - - def test_not_triggered_if_outer_block_does_not_have_elif(self) -> None: - """ - Given an if construct without an elif - When the body of the if ends with an if - Then no message shall be triggered. - """ - example_code = """ - def foo(a, b, c): - result = None - if a > b: #@ - if a > 0: - result = a - elif a < 0: - result = 0 - else: - result = c - return result - """ - if_node_to_test = astroid.extract_node(example_code) - with self.assertNoMessages(): - self.checker.visit_if(if_node_to_test) - - def test_not_triggered_if_outer_block_continues_with_if(self) -> None: - """ - Given an if construct which continues with a new if construct - When the body of the first if ends with an if expression - Then no message shall be triggered. - """ - example_code = """ - def foo(a, b, c): - result = None - if a > b: #@ - if a > 0: - result = a - elif a < 0: - result = 0 - if b > c: - result = c - return result - """ - if_node_to_test = astroid.extract_node(example_code) - with self.assertNoMessages(): - self.checker.visit_if(if_node_to_test) diff --git a/tests/functional/ext/confusing_elif/confusing_elif.py b/tests/functional/ext/confusing_elif/confusing_elif.py index 9f1d1ffe9e..002931de3d 100644 --- a/tests/functional/ext/confusing_elif/confusing_elif.py +++ b/tests/functional/ext/confusing_elif/confusing_elif.py @@ -1,7 +1,13 @@ # pylint: disable=missing-module-docstring, missing-function-docstring -def check_config(machine, old_conf, new_conf): - """Example code that will trigger the message""" + +def triggered_if_if_block_ends_with_elif(machine, old_conf, new_conf): + """Example code that will trigger the message + + Given an if-elif construct + When the body of the if ends with an elif + Then the message confusing-consecutive-elif must be triggered. + """ if old_conf: if not new_conf: machine.disable() @@ -12,8 +18,13 @@ def check_config(machine, old_conf, new_conf): machine.enable(new_conf.value) -def check_config_2(machine, old_conf, new_conf): - """Example code must not trigger the message, because the inner block ends with else.""" +def not_triggered_if_indented_block_ends_with_else(machine, old_conf, new_conf): + """Example code must not trigger the message, because the inner block ends with else. + + Given an if-elif construct + When the body of the if ends with an else + Then no message shall be triggered. + """ if old_conf: if not new_conf: machine.disable() @@ -25,10 +36,18 @@ def check_config_2(machine, old_conf, new_conf): elif new_conf: machine.enable(new_conf.value) -def check_config_3(machine, old_conf, new_conf): + +def not_triggered_if_indentend_block_ends_with_call(machine, old_conf, new_conf): """ Example code must not trigger the message, - because the inner if is not the final node of the body. + + Given an if-elif construct + When the body of the if ends with a function call + Then no message shall be triggered. + + Note: There is nothing special about the body ending with a function call. + This is just taken as a representative value for the equivalence class of + "every node class unrelated to if/elif/else". """ if old_conf: if not new_conf: @@ -39,3 +58,86 @@ def check_config_3(machine, old_conf, new_conf): print("Processed old configuration...") elif new_conf: machine.enable(new_conf.value) + + +def triggered_if_elif_block_ends_with_elif(machine, old_conf, new_conf, new_new_conf): + """Example code that will trigger the message + + Given an if-elif-elif construct + When the body of the first elif ends with an elif + Then the message confusing-consecutive-elif must be triggered. + """ + if old_conf: + machine.disable() + elif not new_conf: + if new_new_conf: + machine.disable() + elif old_conf.value != new_conf.value: + machine.disable() + machine.enable(new_conf.value) + elif new_conf: # [confusing-consecutive-elif] + machine.enable(new_conf.value) + + +def triggered_if_block_ends_with_if(machine, old_conf, new_conf, new_new_conf): + """Example code that will trigger the message + + Given an if-elif construct + When the body of the if ends with an if + Then the message confusing-consecutive-elif must be triggered. + """ + if old_conf: + if new_new_conf: + machine.disable() + elif new_conf: # [confusing-consecutive-elif] + machine.enable(new_conf.value) + + +def not_triggered_if_indented_block_ends_with_ifexp(machine, old_conf, new_conf): + """ + Example code must not trigger the message, + + Given an if-elif construct + When the body of the if ends with an if expression + Then no message shall be triggered. + """ + if old_conf: + if not new_conf: + machine.disable() + print("Processed old configuration...") + elif new_conf: + machine.enable(new_conf.value) + + +def not_triggered_if_outer_block_does_not_have_elif(machine, old_conf, new_conf): + """Example code must not trigger the message + + Given an if construct without an elif + When the body of the if ends with an if + Then no message shall be triggered. + """ + if old_conf: + if not new_conf: + machine.disable() + elif old_conf.value != new_conf.value: + machine.disable() + machine.enable(new_conf.value) + else: + pass + + +def not_triggered_if_outer_block_continues_with_if(machine, old_conf, new_conf, new_new_conf): + """Example code that will trigger the message + + Given an if construct which continues with a new if construct + When the body of the first if ends with an if expression + Then no message shall be triggered. + """ + if old_conf: + if new_new_conf: + machine.disable() + elif old_conf.value != new_conf.value: + machine.disable() + machine.enable(new_conf.value) + if new_conf: + machine.enable(new_conf.value) diff --git a/tests/functional/ext/confusing_elif/confusing_elif.txt b/tests/functional/ext/confusing_elif/confusing_elif.txt index 17fd18eb5e..35487e9df3 100644 --- a/tests/functional/ext/confusing_elif/confusing_elif.txt +++ b/tests/functional/ext/confusing_elif/confusing_elif.txt @@ -1 +1,3 @@ -confusing-consecutive-elif:11:4:12:38:check_config:Consecutive elif with differing indentation level, consider creating a function to separate the inner elif:UNDEFINED +confusing-consecutive-elif:17:4:18:38:triggered_if_if_block_ends_with_elif:Consecutive elif with differing indentation level, consider creating a function to separate the inner elif:UNDEFINED +confusing-consecutive-elif:78:4:79:38:triggered_if_elif_block_ends_with_elif:Consecutive elif with differing indentation level, consider creating a function to separate the inner elif:UNDEFINED +confusing-consecutive-elif:92:4:93:38:triggered_if_block_ends_with_if:Consecutive elif with differing indentation level, consider creating a function to separate the inner elif:UNDEFINED From 35254c29eb3a0f1c7847cfcb3451501a4180373d Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 13 Dec 2021 12:02:46 -0500 Subject: [PATCH 041/357] Fix false-positive 'used-before-assignment' for assignments in except blocks following try blocks that return (#5506) --- ChangeLog | 12 +++++++--- doc/whatsnew/2.13.rst | 12 +++++++--- pylint/checkers/variables.py | 22 ++++++++++++++++++- ...ment_except_handler_for_try_with_return.py | 15 +++++++++++++ ...ent_except_handler_for_try_with_return.txt | 1 + 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py create mode 100644 tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt diff --git a/ChangeLog b/ChangeLog index a90ade1cc0..ae0c1d6e21 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,15 +11,21 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* ``used-before-assignment`` now considers that assignments in a try block + may not have occurred when the except or finally blocks are executed. + + Closes #85, #2615 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. Closes #4761 -* ``used-before-assignment`` now considers that assignments in a try block - may not have occurred when the except or finally blocks are executed. +* When evaluating statements after an except block, ``used-before-assignment`` + assumes that assignments in the except blocks took place if the + corresponding try block contained a return statement. - Closes #85, #2615 + Closes #5500 * ``used-before-assignment`` now checks names in try blocks. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index b3372278d0..8555108c11 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -43,15 +43,21 @@ Other Changes Closes #5323 +* ``used-before-assignment`` now considers that assignments in a try block + may not have occurred when the except or finally blocks are executed. + + Closes #85, #2615 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. Closes #4761 -* ``used-before-assignment`` now considers that assignments in a try block - may not have occurred when the except or finally blocks are executed. +* When evaluating statements after an except block, ``used-before-assignment`` + assumes that assignments in the except blocks took place if the + corresponding try block contained a return statement. - Closes #85, #2615 + Closes #5500 * ``used-before-assignment`` now checks names in try blocks. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 0a4a21c182..86b7eb6697 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -408,7 +408,8 @@ def _has_locals_call_after_node(stmt, scope): "Emitted when a local variable is accessed before its assignment took place. " "Assignments in try blocks are assumed not to have occurred when evaluating " "associated except/finally blocks. Assignments in except blocks are assumed " - "not to have occurred when evaluating statements outside the block.", + "not to have occurred when evaluating statements outside the block, except " + "when the associated try block contains a return statement.", ), "E0602": ( "Undefined variable %r", @@ -656,6 +657,25 @@ def get_next_to_consume(self, node): and isinstance( n.statement(future=True).parent.parent, nodes.TryExcept ) + # If the try block returns we assume that assignments in the except + # handlers could have happened. + and ( + not any( + isinstance(try_statement, nodes.Return) + for try_statement in n.statement( + future=True + ).parent.parent.body + ) + # But not if this node is in the final block, which will + # execute before the return. + or ( + isinstance(node_statement.parent, nodes.TryFinally) + and node_statement in node_statement.parent.finalbody + and n.statement(future=True).parent.parent.parent.parent_of( + node_statement + ) + ) + ) ) or n.statement(future=True).parent.parent_of(node) ] diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py new file mode 100644 index 0000000000..d976a46b2e --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py @@ -0,0 +1,15 @@ +"""Tests for used-before-assignment with assignments in except handlers after +try blocks with return statements. +See: https://github.com/PyCQA/pylint/issues/5500. +""" +def function(): + """Assume except blocks execute if the try block returns.""" + try: + success_message = "success message" + return success_message + except ValueError: + failure_message = "failure message" + finally: + print(failure_message) # [used-before-assignment] + + return failure_message diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt new file mode 100644 index 0000000000..e5d8d3de8f --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt @@ -0,0 +1 @@ +used-before-assignment:13:14:13:29:function:Using variable 'failure_message' before assignment:UNDEFINED From 78eed6af303e0e9854e43528d17d2ada96f11c09 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Mon, 13 Dec 2021 22:48:33 +0100 Subject: [PATCH 042/357] New checker - Detect use of unnecessary ellipsis (#5470) Closes #5460 --- ChangeLog | 4 + doc/whatsnew/2.13.rst | 3 + pylint/checkers/ellipsis_checker.py | 50 ++++++++++ tests/functional/c/class_members.py | 1 - .../functional/s/statement_without_effect.py | 2 +- .../t/too/too_few_public_methods_excluded.py | 1 - .../u/unnecessary/unnecessary_ellipsis.py | 99 +++++++++++++++++++ .../u/unnecessary/unnecessary_ellipsis.txt | 4 + 8 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 pylint/checkers/ellipsis_checker.py create mode 100644 tests/functional/u/unnecessary/unnecessary_ellipsis.py create mode 100644 tests/functional/u/unnecessary/unnecessary_ellipsis.txt diff --git a/ChangeLog b/ChangeLog index ae0c1d6e21..8e99896db6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -53,6 +53,10 @@ Release date: TBA Closes #5323 +* Add checker ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily. + + Closes #5460 + * Fixed incorrect classification of Numpy-style docstring as Google-style docstring for docstrings with property setter documentation. Docstring classification is now based on the highest amount of matched sections instead diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 8555108c11..3f4584fde0 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -10,6 +10,9 @@ Summary -- Release highlights New checkers ============ +* ``unnecessary-ellipsis``: Emmitted when the ellipsis constant is used unnecessarily. + + Closes #5460 Removed checkers ================ diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py new file mode 100644 index 0000000000..ea7a260ec4 --- /dev/null +++ b/pylint/checkers/ellipsis_checker.py @@ -0,0 +1,50 @@ +"""Ellipsis checker for Python code +""" +from astroid import nodes + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import check_messages +from pylint.interfaces import IAstroidChecker +from pylint.lint import PyLinter + + +class EllipsisChecker(BaseChecker): + __implements__ = (IAstroidChecker,) + name = "unnecessary_ellipsis" + msgs = { + "W2301": ( + "Unnecessary ellipsis constant", + "unnecessary-ellipsis", + "Used when the ellipsis constant is encountered and can be avoided. " + "A line of code consisting of an ellipsis is unnecessary if " + "there is a docstring on the preceding line or if there is a " + "statement in the same scope.", + ) + } + + @check_messages("unnecessary-ellipsis") + def visit_const(self, node: nodes.Const) -> None: + """Check if the ellipsis constant is used unnecessarily. + Emit a warning when: + - A line consisting of an ellipsis is preceded by a docstring. + - A statement exists in the same scope as the ellipsis. + For example: A function consisting of an ellipsis followed by a + return statement on the next line. + """ + if ( + node.pytype() == "builtins.Ellipsis" + and not isinstance(node.parent, (nodes.Assign, nodes.AnnAssign, nodes.Call)) + and ( + len(node.parent.parent.child_sequence(node.parent)) > 1 + or ( + isinstance(node.parent.parent, (nodes.ClassDef, nodes.FunctionDef)) + and (node.parent.parent.doc is not None) + ) + ) + ): + self.add_message("unnecessary-ellipsis", node=node) + + +def register(linter: PyLinter) -> None: + """required method to auto register this checker""" + linter.register_checker(EllipsisChecker(linter)) diff --git a/tests/functional/c/class_members.py b/tests/functional/c/class_members.py index e43ab57baa..e741c760b3 100644 --- a/tests/functional/c/class_members.py +++ b/tests/functional/c/class_members.py @@ -3,7 +3,6 @@ class Class: attr: int - ... # `bar` definitely does not exist here, but in a complex scenario, diff --git a/tests/functional/s/statement_without_effect.py b/tests/functional/s/statement_without_effect.py index 53da873d88..31fc7250f0 100644 --- a/tests/functional/s/statement_without_effect.py +++ b/tests/functional/s/statement_without_effect.py @@ -1,5 +1,5 @@ """Test for statements without effects.""" -# pylint: disable=too-few-public-methods, useless-object-inheritance, unnecessary-comprehension, use-list-literal +# pylint: disable=too-few-public-methods, useless-object-inheritance, unnecessary-comprehension, unnecessary-ellipsis, use-list-literal # +1:[pointless-string-statement] """inline doc string should use a separated message""" diff --git a/tests/functional/t/too/too_few_public_methods_excluded.py b/tests/functional/t/too/too_few_public_methods_excluded.py index 35ba873ee5..fecf497ad1 100644 --- a/tests/functional/t/too/too_few_public_methods_excluded.py +++ b/tests/functional/t/too/too_few_public_methods_excluded.py @@ -11,4 +11,3 @@ class MyJsonEncoder(JSONEncoder): class InheritedInModule(Control): """This class inherits from a class that doesn't have enough mehods, and its parent is excluded via config, so it doesn't raise.""" - ... diff --git a/tests/functional/u/unnecessary/unnecessary_ellipsis.py b/tests/functional/u/unnecessary/unnecessary_ellipsis.py new file mode 100644 index 0000000000..b5a61e3493 --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_ellipsis.py @@ -0,0 +1,99 @@ +"""Emit a warning when the ellipsis constant is used and can be avoided""" + +# pylint: disable=missing-docstring, too-few-public-methods + +from typing import List, overload, Union + +# Ellipsis and preceding statement +try: + A = 2 +except ValueError: + A = 24 + ... # [unnecessary-ellipsis] + +def ellipsis_and_subsequent_statement(): + ... # [unnecessary-ellipsis] + return 0 + +# The parent of ellipsis is an assignment +B = ... +C = [..., 1, 2, 3] + +# The parent of ellipsis is a call +if "X" is type(...): + ... + +def docstring_only(): + '''In Python, stubbed functions often have a body that contains just a + single `...` constant, indicating that the function doesn't do + anything. However, a stubbed function can also have just a + docstring, and function with a docstring and no body also does + nothing. + ''' + + +# This function has no docstring, so it needs a `...` constant. +def ellipsis_only(): + ... + + +def docstring_and_ellipsis(): + '''This function doesn't do anything, but it has a docstring, so its + `...` constant is useless clutter. + + NEW CHECK: unnecessary-ellipsis + + This would check for stubs with both docstrings and `...` + constants, suggesting the removal of the useless `...` + constants + ''' + ... # [unnecessary-ellipsis] + + +class DocstringOnly: + '''The same goes for class stubs: docstring, or `...`, but not both. + ''' + + +# No problem +class EllipsisOnly: + ... + + +class DocstringAndEllipsis: + '''Whoops! Mark this one as bad too. + ''' + ... # [unnecessary-ellipsis] + + +# Function overloading +@overload +def summarize(data: int) -> float: ... + + +@overload +def summarize(data: str) -> str: ... + + +def summarize(data): + if isinstance(data, str): + ... + return float(data) + + + +# Method overloading +class MyIntegerList(List[int]): + @overload + def __getitem__(self, index: int) -> int: ... + + @overload + def __getitem__(self, index: slice) -> List[int]: ... + + def __getitem__(self, index: Union[int, slice]) -> Union[int, List[int]]: + if isinstance(index, int): + ... + elif isinstance(index, slice): + ... + else: + raise TypeError(...) diff --git a/tests/functional/u/unnecessary/unnecessary_ellipsis.txt b/tests/functional/u/unnecessary/unnecessary_ellipsis.txt new file mode 100644 index 0000000000..7b7b905890 --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_ellipsis.txt @@ -0,0 +1,4 @@ +unnecessary-ellipsis:12:4:12:7::Unnecessary ellipsis constant:UNDEFINED +unnecessary-ellipsis:15:4:15:7:ellipsis_and_subsequent_statement:Unnecessary ellipsis constant:UNDEFINED +unnecessary-ellipsis:50:4:50:7:docstring_and_ellipsis:Unnecessary ellipsis constant:UNDEFINED +unnecessary-ellipsis:66:4:66:7:DocstringAndEllipsis:Unnecessary ellipsis constant:UNDEFINED From 71bbbdb29407a2b4525f0bf2da8b0025da5fc4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 13 Dec 2021 23:17:48 +0100 Subject: [PATCH 043/357] Check `node` location attributes in unittests and update tests (#5383) * Allow checking of `end_col_offset` and `end_lineno` in unittests * Allow checking of `col_offset` in unittests * Allow checking of `lineno` in unittests * Update tests for ``TestVariablesChecker`` * Fix ``TestMultiNamingStyle`` * Update tests for ``TestImportsChecker`` * Add location attributes to messages of ``TestTypeChecker`` * Add location params to ``TestMessage``'s of ``TestDeprecatedChecker`` * Add location params to ``TestMessage``'s of ``TestTypeCheckerOnDecorators`` * Add changelog and ``DeprecationWarning`` --- ChangeLog | 3 + doc/whatsnew/2.13.rst | 3 + pylint/testutils/checker_test_case.py | 35 ++++++++- pylint/testutils/output_line.py | 2 + pylint/testutils/unittest_linter.py | 30 +++++--- tests/checkers/unittest_base.py | 20 ++++++ tests/checkers/unittest_deprecated.py | 100 ++++++++++++++++++++++++++ tests/checkers/unittest_imports.py | 13 +++- tests/checkers/unittest_typecheck.py | 20 ++++++ tests/checkers/unittest_variables.py | 28 +++++++- 10 files changed, 239 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8e99896db6..44e8f5459e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -67,6 +67,9 @@ Release date: TBA Closes #5371 +* The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests + without these will trigger a ``DeprecationWarning``. + .. Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 3f4584fde0..a0f9a4a70d 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -70,3 +70,6 @@ Other Changes * The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` as its reporter if none is provided. + +* The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests + without these will trigger a ``DeprecationWarning``. diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index 5c8815dabc..af72136a9d 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -2,9 +2,12 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import contextlib -from typing import Dict, Optional, Type +import warnings +from typing import Dict, Generator, Optional, Type +from pylint.constants import PY38_PLUS from pylint.testutils.global_test_linter import linter +from pylint.testutils.output_line import MessageTest from pylint.testutils.unittest_linter import UnittestLinter from pylint.utils import ASTWalker @@ -29,7 +32,7 @@ def assertNoMessages(self): yield @contextlib.contextmanager - def assertAddsMessages(self, *messages): + def assertAddsMessages(self, *messages: MessageTest) -> Generator[None, None, None]: """Assert that exactly the given method adds the given messages. The list of messages must exactly match *all* the messages added by the @@ -45,7 +48,33 @@ def assertAddsMessages(self, *messages): "Expected messages did not match actual.\n" f"\nExpected:\n{expected}\n\nGot:\n{got_str}\n" ) - assert got == list(messages), msg + + assert len(messages) == len(got), msg + + for expected_msg, gotten_msg in zip(messages, got): + assert expected_msg.msg_id == gotten_msg.msg_id, msg + assert expected_msg.line == gotten_msg.line, msg + assert expected_msg.node == gotten_msg.node, msg + assert expected_msg.args == gotten_msg.args, msg + assert expected_msg.confidence == gotten_msg.confidence, msg + assert expected_msg.col_offset == gotten_msg.col_offset, msg + if PY38_PLUS: + # pylint: disable=fixme + # TODO: Require end_line and end_col_offset and remove the warning + if not expected_msg.end_line == gotten_msg.end_line: + warnings.warn( + f"The end_line attribute of {gotten_msg} does not match " + f"the expected value in {expected_msg}. In pylint 3.0 correct end_line " + "attributes will be required for MessageTest.", + DeprecationWarning, + ) + if not expected_msg.end_col_offset == gotten_msg.end_col_offset: + warnings.warn( + f"The end_col_offset attribute of {gotten_msg} does not match " + f"the expected value in {expected_msg}. In pylint 3.0 correct end_col_offset " + "attributes will be required for MessageTest.", + DeprecationWarning, + ) def walk(self, node): """recursive walk on the given node""" diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index 420b58c882..c3836a763e 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -21,6 +21,8 @@ class MessageTest(NamedTuple): args: Optional[Any] = None confidence: Optional[Confidence] = UNDEFINED col_offset: Optional[int] = None + end_line: Optional[int] = None + end_col_offset: Optional[int] = None """Used to test messages produced by pylint. Class name cannot start with Test as pytest doesn't allow constructors in test classes.""" diff --git a/pylint/testutils/unittest_linter.py b/pylint/testutils/unittest_linter.py index d37f17099e..0c45264f7b 100644 --- a/pylint/testutils/unittest_linter.py +++ b/pylint/testutils/unittest_linter.py @@ -30,6 +30,8 @@ def add_message( self, msg_id: str, line: Optional[int] = None, + # pylint: disable=fixme + # TODO: Make node non optional node: Optional[nodes.NodeNG] = None, args: Any = None, confidence: Optional[Confidence] = None, @@ -41,16 +43,28 @@ def add_message( # If confidence is None we set it to UNDEFINED as well in PyLinter if confidence is None: confidence = UNDEFINED + if not line and node: + line = node.fromlineno # pylint: disable=fixme - # TODO: Test col_offset - # pylint: disable=fixme - # TODO: Initialize col_offset on every node (can be None) -> astroid - # if col_offset is None and hasattr(node, "col_offset"): - # col_offset = node.col_offset - # pylint: disable=fixme - # TODO: Test end_lineno and end_col_offset :) + # TODO: Initialize col_offset, end_lineno and end_col_offset on every node -> astroid + # See https://github.com/PyCQA/astroid/issues/1273 + if col_offset is None and node and hasattr(node, "col_offset"): + col_offset = node.col_offset + if not end_lineno and node and hasattr(node, "end_lineno"): + end_lineno = node.end_lineno + if not end_col_offset and node and hasattr(node, "end_col_offset"): + end_col_offset = node.end_col_offset self._messages.append( - MessageTest(msg_id, line, node, args, confidence, col_offset) + MessageTest( + msg_id, + line, + node, + args, + confidence, + col_offset, + end_lineno, + end_col_offset, + ) ) @staticmethod diff --git a/tests/checkers/unittest_base.py b/tests/checkers/unittest_base.py index bbe2be2fa5..3dddd859b9 100644 --- a/tests/checkers/unittest_base.py +++ b/tests/checkers/unittest_base.py @@ -64,6 +64,10 @@ class CLASSC(object): #@ "the `UP` group in the '(?:(?P[A-Z]+)|(?P[a-z]+))$' pattern", ), confidence=HIGH, + line=2, + col_offset=0, + end_line=3, + end_col_offset=8, ) with self.assertAddsMessages(message): cls = None @@ -94,6 +98,10 @@ class CLASSC(object): #@ "'(?:(?P[A-Z]+)|(?P[a-z]+))$' pattern", ), confidence=HIGH, + line=2, + col_offset=0, + end_line=3, + end_col_offset=8, ), MessageTest( "invalid-name", @@ -104,6 +112,10 @@ class CLASSC(object): #@ "the `down` group in the '(?:(?P[A-Z]+)|(?P[a-z]+))$' pattern", ), confidence=HIGH, + line=6, + col_offset=0, + end_line=7, + end_col_offset=8, ), ] with self.assertAddsMessages(*messages): @@ -139,6 +151,10 @@ def FUNC(): #@ "the `down` group in the '(?:(?P[A-Z]+)|(?P[a-z]+))$' pattern", ), confidence=HIGH, + line=6, + col_offset=0, + end_line=7, + end_col_offset=8, ) with self.assertAddsMessages(message): func = None @@ -172,6 +188,10 @@ def UPPER(): #@ "the `down` group in the '(?:(?PFOO)|(?P[A-Z]+)|(?P[a-z]+))$' pattern", ), confidence=HIGH, + line=8, + col_offset=0, + end_line=9, + end_col_offset=8, ) with self.assertAddsMessages(message): func = None diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index fbf6c1ce8c..75608acf4d 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -71,6 +71,10 @@ def deprecated_func(): args=("deprecated_func",), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=17, ) ): self.checker.visit_call(node) @@ -93,6 +97,10 @@ def deprecated_method(): args=("deprecated_method",), node=node, confidence=UNDEFINED, + line=7, + col_offset=0, + end_line=7, + end_col_offset=21, ) ): self.checker.visit_call(node) @@ -118,6 +126,10 @@ def _deprecated_method(self): args=("deprecated_method",), node=node, confidence=UNDEFINED, + line=9, + col_offset=0, + end_line=9, + end_col_offset=21, ) ): self.checker.visit_call(node) @@ -157,6 +169,10 @@ def myfunction1(arg1, deprecated_arg1='spam'): args=("deprecated_arg1", "myfunction1"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=31, ) ): self.checker.visit_call(node) @@ -177,6 +193,10 @@ def myfunction1(arg1, deprecated_arg1='spam'): args=("deprecated_arg1", "myfunction1"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=47, ) ): self.checker.visit_call(node) @@ -210,6 +230,10 @@ def myfunction3(arg1, *, deprecated_arg1='spam'): args=("deprecated_arg1", "myfunction3"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=47, ) ): self.checker.visit_call(node) @@ -231,6 +255,10 @@ def mymethod1(self, arg1, deprecated_arg1): args=("deprecated_arg1", "mymethod1"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=39, ) ): self.checker.visit_call(node) @@ -252,6 +280,10 @@ def mymethod1(self, arg1, deprecated_arg1): args=("deprecated_arg1", "mymethod1"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=55, ) ): self.checker.visit_call(node) @@ -287,6 +319,10 @@ def mymethod3(self, arg1, *, deprecated_arg1): args=("deprecated_arg1", "mymethod3"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=55, ) ): self.checker.visit_call(node) @@ -308,12 +344,20 @@ def myfunction2(arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'): args=("deprecated_arg1", "myfunction2"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=61, ), MessageTest( msg_id="deprecated-argument", args=("deprecated_arg2", "myfunction2"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=61, ), ): self.checker.visit_call(node) @@ -334,12 +378,20 @@ def myfunction2(arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'): args=("deprecated_arg1", "myfunction2"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=77, ), MessageTest( msg_id="deprecated-argument", args=("deprecated_arg2", "myfunction2"), node=node, confidence=UNDEFINED, + line=5, + col_offset=0, + end_line=5, + end_col_offset=77, ), ): self.checker.visit_call(node) @@ -362,12 +414,20 @@ def mymethod2(self, arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'): args=("deprecated_arg1", "mymethod2"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=69, ), MessageTest( msg_id="deprecated-argument", args=("deprecated_arg2", "mymethod2"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=69, ), ): self.checker.visit_call(node) @@ -389,12 +449,20 @@ def mymethod2(self, arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'): args=("deprecated_arg1", "mymethod2"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=85, ), MessageTest( msg_id="deprecated-argument", args=("deprecated_arg2", "mymethod2"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=85, ), ): self.checker.visit_call(node) @@ -416,6 +484,10 @@ def __init__(self, deprecated_arg=None): args=("deprecated_arg", "MyClass"), node=node, confidence=UNDEFINED, + line=6, + col_offset=0, + end_line=6, + end_col_offset=10, ) ): self.checker.visit_call(node) @@ -433,6 +505,10 @@ def test_deprecated_module(self) -> None: args="deprecated_module", node=node, confidence=UNDEFINED, + line=2, + col_offset=0, + end_line=2, + end_col_offset=24, ) ): self.checker.visit_import(node) @@ -450,6 +526,10 @@ def test_deprecated_module_from(self) -> None: args="deprecated_module", node=node, confidence=UNDEFINED, + line=2, + col_offset=0, + end_line=2, + end_col_offset=40, ) ): self.checker.visit_importfrom(node) @@ -467,6 +547,10 @@ def test_deprecated_class_import_from(self) -> None: args=("DeprecatedClass", "deprecated"), node=node, confidence=UNDEFINED, + line=2, + col_offset=0, + end_line=2, + end_col_offset=38, ) ): self.checker.visit_importfrom(node) @@ -484,6 +568,10 @@ def test_deprecated_class_import(self) -> None: args=("DeprecatedClass", "deprecated"), node=node, confidence=UNDEFINED, + line=2, + col_offset=0, + end_line=2, + end_col_offset=33, ) ): self.checker.visit_import(node) @@ -502,6 +590,10 @@ def test_deprecated_class_call(self) -> None: args=("DeprecatedClass", "deprecated"), node=node, confidence=UNDEFINED, + line=3, + col_offset=0, + end_line=3, + end_col_offset=28, ) ): self.checker.visit_call(node) @@ -526,6 +618,10 @@ def function(): args=".deprecated_decorator", node=node, confidence=UNDEFINED, + line=7, + col_offset=0, + end_line=7, + end_col_offset=21, ) ): self.checker.visit_decorators(node) @@ -552,6 +648,10 @@ def function(): args=".deprecated_decorator", node=node, confidence=UNDEFINED, + line=9, + col_offset=0, + end_line=9, + end_col_offset=27, ) ): self.checker.visit_decorators(node) diff --git a/tests/checkers/unittest_imports.py b/tests/checkers/unittest_imports.py index 9dbcf2fe1e..507b0d0550 100644 --- a/tests/checkers/unittest_imports.py +++ b/tests/checkers/unittest_imports.py @@ -39,7 +39,14 @@ def test_relative_beyond_top_level(self) -> None: module = astroid.MANAGER.ast_from_module_name("beyond_top", REGR_DATA) import_from = module.body[0] - msg = MessageTest(msg_id="relative-beyond-top-level", node=import_from) + msg = MessageTest( + msg_id="relative-beyond-top-level", + node=import_from, + line=1, + col_offset=0, + end_line=1, + end_col_offset=25, + ) with self.assertAddsMessages(msg): self.checker.visit_importfrom(import_from) with self.assertNoMessages(): @@ -95,6 +102,10 @@ def test_wildcard_import_non_init(self) -> None: node=import_from, args="empty", confidence=UNDEFINED, + line=1, + col_offset=0, + end_line=1, + end_col_offset=19, ) with self.assertAddsMessages(msg): self.checker.visit_importfrom(import_from) diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index fbb4d53de6..8411203ae4 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -63,6 +63,10 @@ def test_nomember_on_c_extension_error_msg(self) -> None: node=node, args=("Module", "coverage.tracer", "CTracer", ""), confidence=INFERENCE, + line=3, + col_offset=0, + end_line=3, + end_col_offset=14, ) with self.assertAddsMessages(message): self.checker.visit_attribute(node) @@ -81,6 +85,10 @@ def test_nomember_on_c_extension_info_msg(self) -> None: node=node, args=("Module", "coverage.tracer", "CTracer", ""), confidence=INFERENCE, + line=3, + col_offset=0, + end_line=3, + end_col_offset=14, ) with self.assertAddsMessages(message): self.checker.visit_attribute(node) @@ -128,6 +136,10 @@ def getitem_on_modules(self) -> None: node=subscript.value, args="collections", confidence=UNDEFINED, + line=3, + col_offset=7, + end_line=3, + end_col_offset=18, ) ): self.checker.visit_subscript(subscript) @@ -179,6 +191,10 @@ def decorated(): node=subscript.value, args="decorated", confidence=UNDEFINED, + line=18, + col_offset=7, + end_line=18, + end_col_offset=16, ) ): self.checker.visit_subscript(subscript) @@ -219,6 +235,10 @@ def decorated(): node=subscript.value, args="decorated", confidence=UNDEFINED, + line=17, + col_offset=7, + end_line=17, + end_col_offset=16, ) ): self.checker.visit_subscript(subscript) diff --git a/tests/checkers/unittest_variables.py b/tests/checkers/unittest_variables.py index 43ecedb904..a59278bb99 100644 --- a/tests/checkers/unittest_variables.py +++ b/tests/checkers/unittest_variables.py @@ -90,7 +90,14 @@ def normal_func(abc): ) with self.assertAddsMessages( MessageTest( - "unused-argument", node=node["abc"], args="abc", confidence=HIGH + "unused-argument", + node=node["abc"], + args="abc", + confidence=HIGH, + line=2, + col_offset=16, + end_line=2, + end_col_offset=19, ) ): self.checker.visit_functiondef(node) @@ -104,7 +111,14 @@ def cb_func(abc): ) with self.assertAddsMessages( MessageTest( - "unused-argument", node=node["abc"], args="abc", confidence=HIGH + "unused-argument", + node=node["abc"], + args="abc", + confidence=HIGH, + line=2, + col_offset=12, + end_line=2, + end_col_offset=15, ) ): self.checker.visit_functiondef(node) @@ -118,7 +132,15 @@ def test_redefined_builtin_modname_not_ignored(self) -> None: """ ) with self.assertAddsMessages( - MessageTest("redefined-builtin", node=node.body[0], args="open") + MessageTest( + "redefined-builtin", + node=node.body[0], + args="open", + line=2, + col_offset=0, + end_line=2, + end_col_offset=32, + ) ): self.checker.visit_module(node) From 6dceba5a6000d948a406079621b93d4917dfef8b Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 28 Nov 2021 23:34:56 +0100 Subject: [PATCH 044/357] Add Confidences in pylint.interfaces API --- pylint/interfaces.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pylint/interfaces.py b/pylint/interfaces.py index a7b53e6036..b2f76a3209 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -26,6 +26,19 @@ if TYPE_CHECKING: from pylint.reporters.ureports.nodes import Section +__all__ = ( + "IRawChecker", + "IAstroidChecker", + "ITokenChecker", + "IReporter", + "IChecker", + "HIGH", + "INFERENCE", + "INFERENCE_FAILURE", + "UNDEFINED", + "CONFIDENCE_LEVELS", +) + Confidence = namedtuple("Confidence", ["name", "description"]) # Warning Certainties HIGH = Confidence("HIGH", "No false positive possible.") @@ -102,6 +115,3 @@ def handle_message(self, msg) -> None: def display_reports(self, layout: "Section") -> None: """display results encapsulated in the layout tree""" - - -__all__ = ("IRawChecker", "IAstroidChecker", "ITokenChecker", "IReporter", "IChecker") From 8eb65109cfb17e28452bd5b077d741fe077fc3d1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 13 Dec 2021 16:58:42 +0100 Subject: [PATCH 045/357] Fix typo in pylint/interfaces.py --- pylint/interfaces.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pylint/interfaces.py b/pylint/interfaces.py index b2f76a3209..e8cba5e9aa 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -75,10 +75,10 @@ class IChecker(Interface): """ def open(self): - """called before visiting project (i.e set of modules)""" + """called before visiting project (i.e. set of modules)""" def close(self): - """called after visiting project (i.e set of modules)""" + """called after visiting project (i.e. set of modules)""" class IRawChecker(IChecker): @@ -87,7 +87,7 @@ class IRawChecker(IChecker): def process_module(self, node: nodes.Module) -> None: """process a module - the module's content is accessible via astroid.stream + the module's content is accessible via ``astroid.stream`` """ From 86c3709572a90911b5cb7a7347f3a08c94710ba8 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 13 Dec 2021 16:59:12 +0100 Subject: [PATCH 046/357] Fix the typing of pylint.interfaces.implements --- pylint/interfaces.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pylint/interfaces.py b/pylint/interfaces.py index e8cba5e9aa..0ef8cbb621 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -19,11 +19,12 @@ """Interfaces for Pylint objects""" from collections import namedtuple -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING, Tuple, Type, Union from astroid import nodes if TYPE_CHECKING: + from pylint.checkers import BaseChecker from pylint.reporters.ureports.nodes import Section __all__ = ( @@ -59,7 +60,10 @@ def is_implemented_by(cls, instance): return implements(instance, cls) -def implements(obj: "Interface", interface: Tuple[type, type]) -> bool: +def implements( + obj: "BaseChecker", + interface: Union[Type["Interface"], Tuple[Type["Interface"], ...]], +) -> bool: """Return whether the given object (maybe an instance or class) implements the interface. """ From 01fa4df457309f8a7249d791a84084b4ae3f0fd4 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 14 Dec 2021 08:31:23 +0100 Subject: [PATCH 047/357] Modify the description of HIGH confidence for accuracy --- pylint/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/interfaces.py b/pylint/interfaces.py index 0ef8cbb621..eef47449ca 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -42,7 +42,7 @@ Confidence = namedtuple("Confidence", ["name", "description"]) # Warning Certainties -HIGH = Confidence("HIGH", "No false positive possible.") +HIGH = Confidence("HIGH", "Warning that is not based on inference result.") INFERENCE = Confidence("INFERENCE", "Warning based on inference result.") INFERENCE_FAILURE = Confidence( "INFERENCE_FAILURE", "Warning based on inference with failures." From 1f1c7b94d41e93b42ebed5e633b548d8dcbdf387 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 14 Dec 2021 08:32:53 -0500 Subject: [PATCH 048/357] Fix #3675: `safe_infer()` finds ambiguity among function definitions when number of arguments differ (#5409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 5 +++++ pylint/checkers/utils.py | 7 +++++++ .../c/consider/consider_using_with.py | 2 +- .../c/consider/consider_using_with.txt | 1 - .../t/too/too_many_function_args.py | 19 +++++++++++++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/functional/t/too/too_many_function_args.py diff --git a/ChangeLog b/ChangeLog index 44e8f5459e..df21f26c86 100644 --- a/ChangeLog +++ b/ChangeLog @@ -32,6 +32,11 @@ Release date: TBA * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. +* ``safe_infer`` no longer makes an inference when given two function + definitions with differing numbers of arguments. + + Closes #3675 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 5a03ca4f42..d716697297 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1260,6 +1260,13 @@ def safe_infer(node: nodes.NodeNG, context=None) -> Optional[nodes.NodeNG]: inferred_type = _get_python_type_of_node(inferred) if inferred_type not in inferred_types: return None # If there is ambiguity on the inferred node. + if ( + isinstance(inferred, nodes.FunctionDef) + and inferred.args.args is not None + and value.args.args is not None + and len(inferred.args.args) != len(value.args.args) + ): + return None # Different number of arguments indicates ambiguity except astroid.InferenceError: return None # There is some kind of ambiguity except StopIteration: diff --git a/tests/functional/c/consider/consider_using_with.py b/tests/functional/c/consider/consider_using_with.py index d05866aaab..3466e15c0a 100644 --- a/tests/functional/c/consider/consider_using_with.py +++ b/tests/functional/c/consider/consider_using_with.py @@ -21,7 +21,7 @@ def test_urlopen(): def test_temporary_file(): - _ = tempfile.TemporaryFile("r") # [consider-using-with] + _ = tempfile.TemporaryFile("r") # ambiguous with NamedTemporaryFile def test_named_temporary_file(): diff --git a/tests/functional/c/consider/consider_using_with.txt b/tests/functional/c/consider/consider_using_with.txt index dad0eb45f2..973d974035 100644 --- a/tests/functional/c/consider/consider_using_with.txt +++ b/tests/functional/c/consider/consider_using_with.txt @@ -1,6 +1,5 @@ consider-using-with:15:9:15:40:test_codecs_open:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:20:8:20:55:test_urlopen:Consider using 'with' for resource-allocating operations:UNDEFINED -consider-using-with:24:8:24:35:test_temporary_file:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:28:8:28:40:test_named_temporary_file:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:32:8:32:42:test_spooled_temporary_file:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:36:8:36:37:test_temporary_directory:Consider using 'with' for resource-allocating operations:UNDEFINED diff --git a/tests/functional/t/too/too_many_function_args.py b/tests/functional/t/too/too_many_function_args.py new file mode 100644 index 0000000000..c5ca4f78eb --- /dev/null +++ b/tests/functional/t/too/too_many_function_args.py @@ -0,0 +1,19 @@ +"""https://github.com/PyCQA/pylint/issues/3675""" + + +def noop(x): # pylint: disable=invalid-name + """Return value unchanged""" + return x + + +def add(x, y): # pylint: disable=invalid-name + """Add two values""" + return x + y + + +def main(param): + """Should not emit too-many-function-args""" + tmp = noop # matched first + if param == 0: + tmp = add + return tmp(1, 1.01) From 80e43a95da6fd3dc5882f7c77d3fb13b2666df4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Wed, 15 Dec 2021 00:51:50 +0900 Subject: [PATCH 049/357] typecheck: simplify variadic positional detection (#5417) The isinstance checks were not necessary here and caused several false positives where a function with variadic positional argument was called, like for example when the call is used as a function argument, in a if, while or with statement. Co-authored-by: Pierre Sassoulas --- CONTRIBUTORS.txt | 2 ++ ChangeLog | 5 ++++ pylint/checkers/typecheck.py | 12 +------- .../regression_no_value_for_parameter.py | 28 +++++++++++++++++-- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 54e31c6179..f50f8e470e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -581,6 +581,8 @@ contributors: * Harshil (harshil21): contributor +* Jérome Perrin (perrinjerome): contributor + * Felix von Drigalski (felixvd): contributor * Philipp Albrecht (pylbrecht): contributor diff --git a/ChangeLog b/ChangeLog index df21f26c86..265ac67a4d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -32,6 +32,11 @@ Release date: TBA * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. +* Fixed false positives for ``no-value-for-parameter`` with variadic + positional arguments. + + Closes #5416 + * ``safe_infer`` no longer makes an inference when given two function definitions with differing numbers of arguments. diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f29f4f3e15..c5203adf7c 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -630,17 +630,7 @@ def _no_context_variadic_keywords(node, scope): def _no_context_variadic_positional(node, scope): - variadics = () - if isinstance(scope, nodes.Lambda) and not isinstance(scope, nodes.FunctionDef): - variadics = node.starargs + node.kwargs - else: - statement = node.statement() - if isinstance( - statement, (nodes.Expr, nodes.Return, nodes.Assign) - ) and isinstance(statement.value, nodes.Call): - call = statement.value - variadics = call.starargs + call.kwargs - + variadics = node.starargs + node.kwargs return _no_context_variadic(node, scope.args.vararg, nodes.Starred, variadics) diff --git a/tests/functional/r/regression/regression_no_value_for_parameter.py b/tests/functional/r/regression/regression_no_value_for_parameter.py index 6dd2ea4913..d9675e6141 100644 --- a/tests/functional/r/regression/regression_no_value_for_parameter.py +++ b/tests/functional/r/regression/regression_no_value_for_parameter.py @@ -28,9 +28,20 @@ def varargs_good(*parts): def varargs_no_expr(*parts): - """False positive below this line""" + """False positives below this line""" ret = os.path.join(*parts) - return ret + if ret: + return ret + print(os.path.join(*parts)) + if os.path.join(*parts): + print() + elif os.path.join(*parts): + print() + while os.path.join(*parts): + print() + with os.path.join(*parts): # pylint:disable=not-context-manager + print() + return os.path.join(*parts) + os.path.join(*parts) - os.path.join(*parts) def kwargs_good(**kwargs): @@ -39,4 +50,15 @@ def kwargs_good(**kwargs): def kwargs_no_expr(**kwargs): ret = func(**kwargs) - return ret + if ret: + return ret + print(func(**kwargs)) + if func(**kwargs): + print() + elif func(**kwargs): + print() + while func(**kwargs): + print() + with func(**kwargs): # pylint:disable=not-context-manager + print() + return func(**kwargs) + func(**kwargs) - func(**kwargs) From af8cc2e75018d34fbbed08d4bfa3380e80f89b4d Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 14 Dec 2021 14:38:35 -0500 Subject: [PATCH 050/357] Produce a score of 0 for fatal errors and add fatal to score evaluation (#5521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 7 +++++++ doc/faq.rst | 2 +- doc/whatsnew/2.13.rst | 9 ++++++++- examples/pylintrc | 4 ++-- pylint/lint/pylinter.py | 3 ++- pylintrc | 10 +++++----- tests/test_self.py | 8 ++++++++ 7 files changed, 33 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index 265ac67a4d..36b75c95d8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -54,6 +54,13 @@ Release date: TBA * The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` as its reporter if none is provided. +* Fatal errors now emit a score of 0.0 regardless of whether the linted module + contained any statements + + Closes #5451 + +* ``fatal`` was added to the variables permitted in score evaluation expressions. + * Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. Closes #5312 diff --git a/doc/faq.rst b/doc/faq.rst index 6a401bf25d..a2c0f2252d 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -252,7 +252,7 @@ default value by changing the mixin-class-rgx option. Even though the final rating Pylint renders is nominally out of ten, there's no lower bound on it. By default, the formula to calculate score is :: - 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) However, this option can be changed in the Pylint rc file. If having negative values really bugs you, you can set the formula to be the maximum of 0 and the diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a0f9a4a70d..9c7d93d2c8 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -68,7 +68,14 @@ Other Changes Closes #5065 -* The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` +* Fatal errors now emit a score of 0.0 regardless of whether the linted module + contained any statements + + Closes #5451 + +* ``fatal`` was added to the variables permitted in score evaluation expressions. + +* The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests diff --git a/examples/pylintrc b/examples/pylintrc index 07e870b581..2662898dca 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -90,11 +90,11 @@ enable=c-extension-no-member [REPORTS] # Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention' and 'info' # which contain the number of messages in each category, as well as 'statement' # which is the total number of statements analyzed. This score is used by the # global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +evaluation=0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details. diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 3617d91415..4513949256 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -764,7 +764,7 @@ def enable_fail_on_messages(self): self.enable(msg.msgid) self.fail_on_symbols.append(msg.symbol) elif msg.msgid[0] in fail_on_cats: - # message starts with a cateogry value, flag (but do not enable) it + # message starts with a category value, flag (but do not enable) it self.fail_on_symbols.append(msg.symbol) def any_fail_on_issues(self): @@ -1315,6 +1315,7 @@ def _report_evaluation(self): evaluation = self.config.evaluation try: stats_dict = { + "fatal": self.stats.fatal, "error": self.stats.error, "warning": self.stats.warning, "refactor": self.stats.refactor, diff --git a/pylintrc b/pylintrc index 7e983510e2..2682bfc1a3 100644 --- a/pylintrc +++ b/pylintrc @@ -98,11 +98,11 @@ files-output=no reports=no # Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +# note). You have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention' +# and 'info', which contain the number of messages in each category, as +# well as 'statement', which is the total number of statements analyzed. This +# score is used by the global evaluation report (RP0004). +evaluation=0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details diff --git a/tests/test_self.py b/tests/test_self.py index ebdb914437..27bf17cf65 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1154,6 +1154,14 @@ def test_fail_on_exit_code(self, args, expected): # and errors that are generated they don't affect the exit code. self._runtest([path, "--fail-under=-10"] + args, code=expected) + def test_one_module_fatal_error(self): + """ + Fatal errors in one of several modules linted still exits non-zero. + """ + valid_path = join(HERE, "conftest.py") + invalid_path = join(HERE, "garbagePath.py") + self._runtest([valid_path, invalid_path], code=1) + @pytest.mark.parametrize( "args, expected", [ From 26c9042825a6549c9f889b2cd17b5e08ba784ab2 Mon Sep 17 00:00:00 2001 From: kasium <15907922+kasium@users.noreply.github.com> Date: Wed, 15 Dec 2021 00:19:44 +0100 Subject: [PATCH 051/357] Enable missing-raises-doc to understand class hierarchies (#5278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, missing-raises-doc could not understand class hierarchies but just compared names. So, when a method documented a raise of a parent class, but a child exception was raised, the check failed. With this change, if the name compare doesn't help, the exception class hierarchy is checked. For that, possible_exc_types was changed to return classes instead of names Resolved #4955 Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 14 ++++++ doc/whatsnew/2.13.rst | 14 ++++++ pylint/extensions/_check_docs_utils.py | 29 ++++++------ pylint/extensions/docparams.py | 15 ++++++- tests/extensions/test_check_docs_utils.py | 6 ++- ...ing_raises_doc_required_exc_inheritance.py | 45 +++++++++++++++++++ ...ing_raises_doc_required_exc_inheritance.rc | 5 +++ ...ng_raises_doc_required_exc_inheritance.txt | 1 + 8 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.py create mode 100644 tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.rc create mode 100644 tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt diff --git a/ChangeLog b/ChangeLog index 36b75c95d8..c2f77182ed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -87,6 +87,20 @@ Release date: TBA * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions + + .. code-block:: python + + def my_function() + """My function. + + Raises: + Exception: if something fails + """ + raise ValueError + + Closes #4955 + .. Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 9c7d93d2c8..da595d2256 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -80,3 +80,17 @@ Other Changes * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. + +* ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions + + .. code-block:: python + + def my_function() + """My function. + + Raises: + Exception: if something fails + """ + raise ValueError + + Closes #4955 diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index bfab0e1e4b..2a26fe0e29 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -120,7 +120,7 @@ def _split_multiple_exc_types(target: str) -> List[str]: return re.split(delimiters, target) -def possible_exc_types(node): +def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: """ Gets all of the possible raised exception types for the given raise node. @@ -130,28 +130,30 @@ def possible_exc_types(node): :param node: The raise node to find exception types for. - :type node: nodes.NodeNG :returns: A list of exception types possibly raised by :param:`node`. - :rtype: set(str) """ excs = [] if isinstance(node.exc, nodes.Name): inferred = utils.safe_infer(node.exc) if inferred: - excs = [inferred.name] + excs = [inferred] elif node.exc is None: handler = node.parent while handler and not isinstance(handler, nodes.ExceptHandler): handler = handler.parent if handler and handler.type: - inferred_excs = astroid.unpack_infer(handler.type) - excs = (exc.name for exc in inferred_excs if exc is not astroid.Uninferable) + try: + for exc in astroid.unpack_infer(handler.type): + if exc is not astroid.Uninferable: + excs.append(exc) + except astroid.InferenceError: + pass else: target = _get_raise_target(node) if isinstance(target, nodes.ClassDef): - excs = [target.name] + excs = [target] elif isinstance(target, nodes.FunctionDef): for ret in target.nodes_of_class(nodes.Return): if ret.frame() != target: @@ -159,15 +161,14 @@ def possible_exc_types(node): continue val = utils.safe_infer(ret.value) - if ( - val - and isinstance(val, (astroid.Instance, nodes.ClassDef)) - and utils.inherit_from_std_ex(val) - ): - excs.append(val.name) + if val and utils.inherit_from_std_ex(val): + if isinstance(val, nodes.ClassDef): + excs.append(val) + elif isinstance(val, astroid.Instance): + excs.append(val.getattr("__class__")[0]) try: - return {exc for exc in excs if not utils.node_ignores_exception(node, exc)} + return {exc for exc in excs if not utils.node_ignores_exception(node, exc.name)} except astroid.InferenceError: return set() diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index e7b293988c..a0c66f16c2 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -309,14 +309,25 @@ def visit_raise(self, node: nodes.Raise) -> None: doc = utils.docstringify(func_node.doc, self.config.default_docstring_type) if not doc.matching_sections(): if doc.doc: - self._handle_no_raise_doc(expected_excs, func_node) + missing = {exc.name for exc in expected_excs} + self._handle_no_raise_doc(missing, func_node) return found_excs_full_names = doc.exceptions() # Extract just the class name, e.g. "error" from "re.error" found_excs_class_names = {exc.split(".")[-1] for exc in found_excs_full_names} - missing_excs = expected_excs - found_excs_class_names + + missing_excs = set() + for expected in expected_excs: + for found_exc in found_excs_class_names: + if found_exc == expected.name: + break + if any(found_exc == ancestor.name for ancestor in expected.ancestors()): + break + else: + missing_excs.add(expected.name) + self._add_raise_message(missing_excs, func_node) def visit_return(self, node: nodes.Return) -> None: diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py index c1552a0c30..0414d05a4c 100644 --- a/tests/extensions/test_check_docs_utils.py +++ b/tests/extensions/test_check_docs_utils.py @@ -147,5 +147,7 @@ def my_func(): ], ) def test_exception(raise_node, expected): - found = utils.possible_exc_types(raise_node) - assert found == expected + found_nodes = utils.possible_exc_types(raise_node) + for node in found_nodes: + assert isinstance(node, astroid.nodes.ClassDef) + assert {node.name for node in found_nodes} == expected diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.py b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.py new file mode 100644 index 0000000000..16334818d5 --- /dev/null +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.py @@ -0,0 +1,45 @@ +"""Tests for missing-raises-doc for exception class inheritance.""" +# pylint: disable=missing-class-docstring + +class CustomError(NameError): + pass + + +class CustomChildError(CustomError): + pass + + +def test_find_missing_raise_for_parent(): # [missing-raises-doc] + """This is a docstring. + + Raises: + CustomError: Never + """ + raise NameError("hi") + + +def test_no_missing_raise_for_child_builtin(): + """This is a docstring. + + Raises: + Exception: Never + """ + raise ValueError("hi") + + +def test_no_missing_raise_for_child_custom(): + """This is a docstring. + + Raises: + NameError: Never + """ + raise CustomError("hi") + + +def test_no_missing_raise_for_child_custom_nested(): + """This is a docstring. + + Raises: + NameError: Never + """ + raise CustomChildError("hi") diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.rc b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.rc new file mode 100644 index 0000000000..0989210701 --- /dev/null +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.rc @@ -0,0 +1,5 @@ +[MASTER] +load-plugins = pylint.extensions.docparams + +[BASIC] +accept-no-raise-doc=no diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt new file mode 100644 index 0000000000..411cb77d5c --- /dev/null +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt @@ -0,0 +1 @@ +missing-raises-doc:12:0:18:25:test_find_missing_raise_for_parent:"""NameError""" not documented as being raised:UNDEFINED From 623b54d94764b0c51f328c084dfaf72fb2d16457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 15 Dec 2021 14:36:31 +0100 Subject: [PATCH 052/357] Update typing of reporter attributes in ``PyLinter`` (#5525) --- pylint/lint/pylinter.py | 29 +++++++++++++++-------------- pylint/reporters/base_reporter.py | 3 +++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 4513949256..d6edf1ef9f 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -11,7 +11,7 @@ import traceback import warnings from io import TextIOWrapper -from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Union +from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Type, Union import astroid from astroid import AstroidError, nodes @@ -533,8 +533,8 @@ def __init__( self.set_reporter(reporter) else: self.set_reporter(TextReporter()) - self._reporter_names = None - self._reporters = {} + self._reporters: Dict[str, Type[reporters.BaseReporter]] = {} + """Dictionary of possible but non-initialized reporters""" self.msgs_store = MessageDefinitionStore() self._checkers = collections.defaultdict(list) @@ -619,19 +619,21 @@ def load_plugin_configuration(self): except ModuleNotFoundError as e: self.add_message("bad-plugin-value", args=(modname, e), line=0) - def _load_reporters(self) -> None: + def _load_reporters(self, reporter_names: str) -> None: + """Load the reporters if they are available on _reporters""" + if not self._reporters: + return sub_reporters = [] output_files = [] with contextlib.ExitStack() as stack: - for reporter_name in self._reporter_names.split(","): + for reporter_name in reporter_names.split(","): reporter_name, *reporter_output = reporter_name.split(":", 1) reporter = self._load_reporter_by_name(reporter_name) sub_reporters.append(reporter) if reporter_output: - (reporter_output,) = reporter_output output_file = stack.enter_context( - open(reporter_output, "w", encoding="utf-8") + open(reporter_output[0], "w", encoding="utf-8") ) reporter.out = output_file output_files.append(output_file) @@ -690,18 +692,17 @@ def set_option(self, optname, value, action=None, optdict=None): meth(value) return # no need to call set_option, disable/enable methods do it elif optname == "output-format": - self._reporter_names = value - # If the reporters are already available, load - # the reporter class. - if self._reporters: - self._load_reporters() - + assert isinstance( + value, str + ), "'output-format' should be a comma separated string of reporters" + self._load_reporters(value) try: checkers.BaseTokenChecker.set_option(self, optname, value, action, optdict) except config.UnsupportedAction: print(f"option {optname} can't be read from config file", file=sys.stderr) - def register_reporter(self, reporter_class): + def register_reporter(self, reporter_class: Type[reporters.BaseReporter]) -> None: + """Registers a reporter class on the _reporters attribute.""" self._reporters[reporter_class.name] = reporter_class def report_order(self): diff --git a/pylint/reporters/base_reporter.py b/pylint/reporters/base_reporter.py index 21549c943d..7ee55beffa 100644 --- a/pylint/reporters/base_reporter.py +++ b/pylint/reporters/base_reporter.py @@ -23,6 +23,9 @@ class BaseReporter: extension = "" + name = "base" + """Name of the reporter""" + def __init__(self, output: Optional[TextIO] = None) -> None: self.linter: "PyLinter" self.section = 0 From fe547fbc47d9f8389260d38d4237b1b003722035 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 15 Dec 2021 14:37:08 +0100 Subject: [PATCH 053/357] Add additional test cases used-before-assignment with try-except (#5523) --- ...ment_except_handler_for_try_with_return.py | 36 +++++++++++++++++++ ...ent_except_handler_for_try_with_return.txt | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py index d976a46b2e..f915a85cc1 100644 --- a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py @@ -2,6 +2,9 @@ try blocks with return statements. See: https://github.com/PyCQA/pylint/issues/5500. """ +# pylint: disable=inconsistent-return-statements + + def function(): """Assume except blocks execute if the try block returns.""" try: @@ -13,3 +16,36 @@ def function(): print(failure_message) # [used-before-assignment] return failure_message + + +def func_ok(var): + """'msg' is defined in all ExceptHandlers.""" + try: + return 1 / var.some_other_func() + except AttributeError: + msg = "Attribute not defined" + except ZeroDivisionError: + msg = "Devision by 0" + print(msg) + + +def func_ok2(var): + """'msg' is defined in all ExceptHandlers that don't raise an Exception.""" + try: + return 1 / var.some_other_func() + except AttributeError as ex: + raise Exception from ex + except ZeroDivisionError: + msg = "Devision by 0" + print(msg) + + +def func_ok3(var): + """'msg' is defined in all ExceptHandlers that don't return.""" + try: + return 1 / var.some_other_func() + except AttributeError: + return + except ZeroDivisionError: + msg = "Devision by 0" + print(msg) diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt index e5d8d3de8f..7ca99f115c 100644 --- a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt @@ -1 +1 @@ -used-before-assignment:13:14:13:29:function:Using variable 'failure_message' before assignment:UNDEFINED +used-before-assignment:16:14:16:29:function:Using variable 'failure_message' before assignment:UNDEFINED From 7b79386b64db66c38c2095c17fa7d6f7e6083892 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 15 Dec 2021 14:11:06 +0000 Subject: [PATCH 054/357] Fix assigning-non-slot false positive with setattr (#5457) * Fix assigning-non-slot false positive with setattr Previously, if a class was slotted and overrode `__setattr__`, `assigning-non-slot` would be issued when assigning to attributes. With `__setattr__` defined, we cannot infer if it is an error to assign to an attribute, so we suppress the error. Fix #3793 Co-authored-by: Pierre Sassoulas --- CONTRIBUTORS.txt | 2 ++ ChangeLog | 5 ++++ doc/whatsnew/2.13.rst | 5 ++++ pylint/checkers/classes.py | 8 ++++++ .../functional/a/assign/assigning_non_slot.py | 27 +++++++++++++++++++ 5 files changed, 47 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index f50f8e470e..2752254244 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -585,6 +585,8 @@ contributors: * Felix von Drigalski (felixvd): contributor +* Jake Lishman (jakelishman): contributor + * Philipp Albrecht (pylbrecht): contributor * Allan Chandler (allanc65): contributor diff --git a/ChangeLog b/ChangeLog index c2f77182ed..069868d986 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,11 @@ Release date: TBA Closes #85, #2615 +* Fixed a false positive for ``assigning-non-slot`` when the slotted class + defined ``__setattr__``. + + Closes #3793 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index da595d2256..31b6d4e367 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -51,6 +51,11 @@ Other Changes Closes #85, #2615 +* Fix a false positive for ``assigning-non-slot`` when the slotted class + defined ``__setattr__``. + + Closes #3793 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 4bbc45f20f..478197b0f9 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -1487,6 +1487,14 @@ def _check_in_slots(self, node): return if "__slots__" not in klass.locals or not klass.newstyle: return + # If `__setattr__` is defined on the class, then we can't reason about + # what will happen when assigning to an attribute. + if any( + base.locals.get("__setattr__") + for base in klass.mro() + if base.qname() != "builtins.object" + ): + return # If 'typing.Generic' is a base of bases of klass, the cached version # of 'slots()' might have been evaluated incorrectly, thus deleted cache entry. diff --git a/tests/functional/a/assign/assigning_non_slot.py b/tests/functional/a/assign/assigning_non_slot.py index cf673692ff..2cd1483e0b 100644 --- a/tests/functional/a/assign/assigning_non_slot.py +++ b/tests/functional/a/assign/assigning_non_slot.py @@ -173,3 +173,30 @@ class Cls(Generic[TYPE]): def __init__(self, value): self.value = value + + +class ClassDefiningSetattr(object): + __slots__ = ["foobar"] + + def __init__(self): + self.foobar = {} + + def __setattr__(self, name, value): + if name == "foobar": + super().__setattr__(name, value) + else: + self.foobar[name] = value + + +class ClassWithParentDefiningSetattr(ClassDefiningSetattr): + __slots__ = [] + + +def dont_emit_for_defined_setattr(): + inst = ClassDefiningSetattr() + # This should not emit because we can't reason about what happens with + # classes defining __setattr__ + inst.non_existent = "non-existent" + + child = ClassWithParentDefiningSetattr() + child.non_existent = "non-existent" From 9909daec8e3089e350decf8c1a837a2f6fe95cb9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 16 Dec 2021 13:36:33 +0100 Subject: [PATCH 055/357] Upgrade mypy to 0.920 (#5535) --- .pre-commit-config.yaml | 2 +- pylint/extensions/__init__.py | 2 +- pylint/reporters/__init__.py | 2 +- requirements_test_pre_commit.txt | 2 +- tests/config/unittest_config.py | 6 +++++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d5596eedb..6c3bd79b48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: types: [text] # necessary to include ChangeLog file files: ^(ChangeLog|doc/(.*/)*.*\.rst) - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 + rev: v0.920 hooks: - id: mypy name: mypy diff --git a/pylint/extensions/__init__.py b/pylint/extensions/__init__.py index 42477a4b39..8b5fa91324 100644 --- a/pylint/extensions/__init__.py +++ b/pylint/extensions/__init__.py @@ -11,7 +11,7 @@ def initialize(linter: "PyLinter") -> None: """Initialize linter with checkers in the extensions directory""" - register_plugins(linter, __path__[0]) # type: ignore[name-defined] # Fixed in https://github.com/python/mypy/pull/9454 + register_plugins(linter, __path__[0]) __all__ = ["initialize"] diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 271975bb58..420154d54c 100644 --- a/pylint/reporters/__init__.py +++ b/pylint/reporters/__init__.py @@ -37,7 +37,7 @@ def initialize(linter: "PyLinter") -> None: """initialize linter with reporters in this package""" - utils.register_plugins(linter, __path__[0]) # type: ignore[name-defined] # Fixed in https://github.com/python/mypy/pull/9454 + utils.register_plugins(linter, __path__[0]) __all__ = [ diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 217002d4d6..0823f177c6 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,4 @@ black==21.12b0 flake8==4.0.1 flake8-typing-imports==1.11.0 isort==5.10.1 -mypy==0.910 +mypy==0.920 diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py index b10c686e4d..d98b2a7a1b 100644 --- a/tests/config/unittest_config.py +++ b/tests/config/unittest_config.py @@ -16,6 +16,7 @@ import re import sre_constants +import sys from typing import Dict, Tuple, Type import pytest @@ -25,7 +26,10 @@ from pylint.testutils import CheckerTestCase, set_config from pylint.utils.utils import get_global_option -RE_PATTERN_TYPE = getattr(re, "Pattern", getattr(re, "_pattern_type", None)) +if sys.version_info >= (3, 7): + RE_PATTERN_TYPE = re.Pattern +else: + RE_PATTERN_TYPE = re._pattern_type # pylint: disable=no-member def test__regexp_validator_valid() -> None: From c285fae493a7a272639a969d6ea914db8c9e1d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 16 Dec 2021 16:34:14 +0100 Subject: [PATCH 056/357] Add ``future=True`` to all ``NodeNG.statement()`` calls (#5310) Co-authored-by: Pierre Sassoulas --- pylint/checkers/base.py | 2 +- pylint/checkers/classes.py | 8 +-- pylint/checkers/format.py | 4 +- pylint/checkers/typecheck.py | 11 ++-- pylint/checkers/utils.py | 2 +- pylint/checkers/variables.py | 51 +++++++++++-------- .../regression_node_statement.py | 18 +++++++ 7 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 tests/functional/r/regression_02/regression_node_statement.py diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 0206b12025..9d61cf10f6 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -606,7 +606,7 @@ def visit_starred(self, node: nodes.Starred) -> None: # PEP 448 unpacking. return - stmt = node.statement() + stmt = node.statement(future=True) if not isinstance(stmt, nodes.Assign): return diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 478197b0f9..da9bf074fc 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -1052,7 +1052,9 @@ def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: nodes_lst = [ n for n in nodes_lst - if not isinstance(n.statement(), (nodes.Delete, nodes.AugAssign)) + if not isinstance( + n.statement(future=True), (nodes.Delete, nodes.AugAssign) + ) and n.root() is current_module ] if not nodes_lst: @@ -1651,7 +1653,7 @@ def _check_protected_attribute_access(self, node: nodes.Attribute): # class A: # b = property(lambda: self._b) - stmt = node.parent.statement() + stmt = node.parent.statement(future=True) if ( isinstance(stmt, nodes.Assign) and len(stmt.targets) == 1 @@ -1796,7 +1798,7 @@ def _check_accessed_members(self, node, accessed): _node.frame() is frame and _node.fromlineno < lno and not astroid.are_exclusive( - _node.statement(), defstmt, excs + _node.statement(future=True), defstmt, excs ) ): self.add_message( diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index cdbacd4cfd..0c0cb8752c 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -602,8 +602,10 @@ def visit_default(self, node: nodes.NodeNG) -> None: isinstance(node.parent, nodes.TryFinally) and node in node.parent.finalbody ): prev_line = node.parent.body[0].tolineno + 1 + elif isinstance(node.parent, nodes.Module): + prev_line = 0 else: - prev_line = node.parent.statement().fromlineno + prev_line = node.parent.statement(future=True).fromlineno line = node.fromlineno assert line, node if prev_line == line and self._visited_lines.get(line) != 2: diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index c5203adf7c..df01cc2fe9 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -72,6 +72,7 @@ from typing import Any, Callable, Iterator, List, Optional, Pattern, Tuple import astroid +import astroid.exceptions from astroid import bases, nodes from pylint.checkers import BaseChecker, utils @@ -615,7 +616,7 @@ def _has_parent_of_type(node, node_type, statement): def _no_context_variadic_keywords(node, scope): - statement = node.statement() + statement = node.statement(future=True) variadics = () if isinstance(scope, nodes.Lambda) and not isinstance(scope, nodes.FunctionDef): @@ -649,7 +650,7 @@ def _no_context_variadic(node, variadic_name, variadic_type, variadics): is_in_lambda_scope = not isinstance(scope, nodes.FunctionDef) and isinstance( scope, nodes.Lambda ) - statement = node.statement() + statement = node.statement(future=True) for name in statement.nodes_of_class(nodes.Name): if name.name != variadic_name: continue @@ -667,7 +668,7 @@ def _no_context_variadic(node, variadic_name, variadic_type, variadics): # so we need to go the lambda instead inferred_statement = inferred.parent.parent else: - inferred_statement = inferred.statement() + inferred_statement = inferred.statement(future=True) if not length and isinstance(inferred_statement, nodes.Lambda): is_in_starred_context = _has_parent_of_type(node, variadic_type, statement) @@ -1029,10 +1030,12 @@ def visit_attribute(self, node: nodes.Attribute) -> None: if not [ n for n in owner.getattr(node.attrname) - if not isinstance(n.statement(), nodes.AugAssign) + if not isinstance(n.statement(future=True), nodes.AugAssign) ]: missingattr.add((owner, name)) continue + except astroid.exceptions.StatementMissing: + continue except AttributeError: continue except astroid.DuplicateBasesError: diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index d716697297..1f85399a7d 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -401,7 +401,7 @@ def is_defined_before(var_node: nodes.Name) -> bool: if is_defined_in_scope(var_node, varname, parent): return True # possibly multiple statements on the same line using semi colon separator - stmt = var_node.statement() + stmt = var_node.statement(future=True) _node = stmt.previous_sibling() lineno = stmt.fromlineno while _node and _node.fromlineno == lineno: diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 86b7eb6697..a1cee36a0b 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1110,7 +1110,7 @@ def visit_name(self, node: nodes.Name) -> None: It's important that all 'Name' nodes are visited, otherwise the 'NamesConsumers' won't be correct. """ - stmt = node.statement() + stmt = node.statement(future=True) if stmt.fromlineno is None: # name node from an astroid built from live code, skip assert not stmt.root().file.endswith(".py") @@ -1261,7 +1261,7 @@ def _check_consumer( return (VariableVisitConsumerAction.CONSUME, found_nodes) defnode = utils.assign_parent(found_nodes[0]) - defstmt = defnode.statement() + defstmt = defnode.statement(future=True) defframe = defstmt.frame() # The class reuses itself in the class scope. @@ -1515,7 +1515,10 @@ def _allow_global_unused_variables(self): @staticmethod def _defined_in_function_definition(node, frame): in_annotation_or_default_or_decorator = False - if isinstance(frame, nodes.FunctionDef) and node.statement() is frame: + if ( + isinstance(frame, nodes.FunctionDef) + and node.statement(future=True) is frame + ): in_annotation_or_default_or_decorator = ( ( node in frame.args.annotations @@ -1563,8 +1566,8 @@ def _in_lambda_or_comprehension_body( def _is_variable_violation( node: nodes.Name, defnode, - stmt, - defstmt, + stmt: nodes.Statement, + defstmt: nodes.Statement, frame, # scope of statement of node defframe, base_scope_type, @@ -1753,12 +1756,8 @@ def _is_variable_violation( return maybe_before_assign, annotation_return, use_outer_definition - # pylint: disable-next=fixme - # TODO: The typing of `NodeNG.statement()` in astroid is non-specific - # After this has been updated the typing of `defstmt` should reflect this - # See: https://github.com/PyCQA/astroid/pull/1217 @staticmethod - def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.NodeNG) -> bool: + def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool: """Check if variable only gets assigned a type and never a value""" if not isinstance(defstmt, nodes.AnnAssign) or defstmt.value: return False @@ -1866,7 +1865,7 @@ def _ignore_class_scope(self, node): # ... name = node.name - frame = node.statement().scope() + frame = node.statement(future=True).scope() in_annotation_or_default_or_decorator = self._defined_in_function_definition( node, frame ) @@ -1890,29 +1889,36 @@ def _loopvar_name(self, node: astroid.Name) -> None: # the variable is not defined. scope = node.scope() if isinstance(scope, nodes.FunctionDef) and any( - asmt.statement().parent_of(scope) for asmt in astmts + asmt.scope().parent_of(scope) for asmt in astmts ): return - - # filter variables according their respective scope test is_statement - # and parent to avoid #74747. This is not a total fix, which would + # Filter variables according to their respective scope. Test parent + # and statement to avoid #74747. This is not a total fix, which would # introduce a mechanism similar to special attribute lookup in # modules. Also, in order to get correct inference in this case, the # scope lookup rules would need to be changed to return the initial # assignment (which does not exist in code per se) as well as any later # modifications. + # pylint: disable-next=too-many-boolean-expressions if ( not astmts - or (astmts[0].is_statement or astmts[0].parent) - and astmts[0].statement().parent_of(node) + or ( + astmts[0].parent == astmts[0].root() + and astmts[0].parent.parent_of(node) + ) + or ( + astmts[0].is_statement + or not isinstance(astmts[0].parent, nodes.Module) + and astmts[0].statement(future=True).parent_of(node) + ) ): _astmts = [] else: _astmts = astmts[:1] for i, stmt in enumerate(astmts[1:]): - if astmts[i].statement().parent_of(stmt) and not in_for_else_branch( - astmts[i].statement(), stmt - ): + if astmts[i].statement(future=True).parent_of( + stmt + ) and not in_for_else_branch(astmts[i].statement(future=True), stmt): continue _astmts.append(stmt) astmts = _astmts @@ -1922,7 +1928,7 @@ def _loopvar_name(self, node: astroid.Name) -> None: assign = astmts[0].assign_type() if not ( isinstance(assign, (nodes.For, nodes.Comprehension, nodes.GeneratorExp)) - and assign.statement() is not node.statement() + and assign.statement(future=True) is not node.statement(future=True) ): return @@ -2136,7 +2142,8 @@ def _check_late_binding_closure(self, node: nodes.Name) -> None: maybe_for and maybe_for.parent_of(node_scope) and not utils.is_being_called(node_scope) - and not isinstance(node_scope.statement(), nodes.Return) + and node_scope.parent + and not isinstance(node_scope.statement(future=True), nodes.Return) ): self.add_message("cell-var-from-loop", node=node, args=node.name) diff --git a/tests/functional/r/regression_02/regression_node_statement.py b/tests/functional/r/regression_02/regression_node_statement.py new file mode 100644 index 0000000000..bd982480be --- /dev/null +++ b/tests/functional/r/regression_02/regression_node_statement.py @@ -0,0 +1,18 @@ +"""Test to see we don't crash on this code in pandas. +See: https://github.com/pandas-dev/pandas/blob/master/pandas/core/arrays/sparse/array.py +Code written by Guido van Rossum here: https://github.com/python/typing/issues/684""" +# pylint: disable=no-member, redefined-builtin, invalid-name, missing-class-docstring + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from enum import Enum + + class ellipsis(Enum): + Ellipsis = "..." + + Ellipsis = ellipsis.Ellipsis + + +else: + ellipsis = type(Ellipsis) From 8743d895cf39b8e0eb53b4203de1e569ed1f2518 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 17 Dec 2021 04:12:59 -0500 Subject: [PATCH 057/357] Factor out `_uncertain_nodes_in_except_blocks()` (#5541) --- pylint/checkers/variables.py | 85 +++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index a1cee36a0b..27ad863f8c 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -649,40 +649,12 @@ def get_next_to_consume(self, node): # Filter out assignments in an Except clause that the node is not # contained in, assuming they may fail if found_nodes: - filtered_nodes = [ - n - for n in found_nodes - if not ( - isinstance(n.statement(future=True).parent, nodes.ExceptHandler) - and isinstance( - n.statement(future=True).parent.parent, nodes.TryExcept - ) - # If the try block returns we assume that assignments in the except - # handlers could have happened. - and ( - not any( - isinstance(try_statement, nodes.Return) - for try_statement in n.statement( - future=True - ).parent.parent.body - ) - # But not if this node is in the final block, which will - # execute before the return. - or ( - isinstance(node_statement.parent, nodes.TryFinally) - and node_statement in node_statement.parent.finalbody - and n.statement(future=True).parent.parent.parent.parent_of( - node_statement - ) - ) - ) - ) - or n.statement(future=True).parent.parent_of(node) - ] - filtered_nodes_set = set(filtered_nodes) - difference = [n for n in found_nodes if n not in filtered_nodes_set] - self.consumed_uncertain[node.name] += difference - found_nodes = filtered_nodes + uncertain_nodes = self._uncertain_nodes_in_except_blocks( + found_nodes, node, node_statement + ) + self.consumed_uncertain[node.name] += uncertain_nodes + uncertain_nodes_set = set(uncertain_nodes) + found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set] # If this node is in a Finally block of a Try/Finally, # filter out assignments in the try portion, assuming they may fail @@ -724,6 +696,51 @@ def get_next_to_consume(self, node): return found_nodes + @staticmethod + def _uncertain_nodes_in_except_blocks(found_nodes, node, node_statement): + """ + Return any nodes in ``found_nodes`` that should be treated as uncertain + because they are in an except block. + """ + uncertain_nodes = [] + for other_node in found_nodes: + other_node_statement = other_node.statement(future=True) + # Only testing for statements in the except block of TryExcept + if not ( + isinstance(other_node_statement.parent, nodes.ExceptHandler) + and isinstance(other_node_statement.parent.parent, nodes.TryExcept) + ): + continue + # If the other node is in the same scope as this node, assume it executes + if other_node_statement.parent.parent_of(node): + continue + try_block_returns = any( + isinstance(try_statement, nodes.Return) + for try_statement in other_node_statement.parent.parent.body + ) + # If the try block returns, assume the except blocks execute. + if try_block_returns: + # Exception: if this node is in the final block of the other_node_statement, + # it will execute before returning. Assume the except statements are uncertain. + if ( + isinstance(node_statement.parent, nodes.TryFinally) + and node_statement in node_statement.parent.finalbody + # We have already tested that other_node_statement has two parents + # and it was TryExcept, so getting one more parent is safe. + and other_node_statement.parent.parent.parent.parent_of( + node_statement + ) + ): + uncertain_nodes.append(other_node) + else: + # Assume the except blocks execute. Possiblility for a false negative + # if one of the except blocks does not define the name in question, + # raise, or return. See: https://github.com/PyCQA/pylint/issues/5524. + continue + # Passed all tests for uncertain execution + uncertain_nodes.append(other_node) + return uncertain_nodes + # pylint: disable=too-many-public-methods class VariablesChecker(BaseChecker): From 65e3efa70d760ce1c8d41f077b78b6afac5f4049 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 7 Dec 2021 14:40:02 +0100 Subject: [PATCH 058/357] [refactor] Create a package in order to be able to burst classes.py --- pylint/checkers/{classes.py => classes/__init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pylint/checkers/{classes.py => classes/__init__.py} (100%) diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes/__init__.py similarity index 100% rename from pylint/checkers/classes.py rename to pylint/checkers/classes/__init__.py From 8aaae9963535cfaa799935214ba36f598455a1bb Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 16 Dec 2021 23:59:31 +0100 Subject: [PATCH 059/357] Style following review: better docstring --- pylint/checkers/classes/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index da9bf074fc..52601bc421 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -49,8 +49,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""classes checker for Python code -""" +"""Classes checker for Python code""" import collections from itertools import chain, zip_longest from typing import List, Pattern From 617f79831349dcb8362434e0ed4a11629fd92879 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 17 Dec 2021 00:06:11 +0100 Subject: [PATCH 060/357] [refactor] Create a file for the SpecialMethodsChecker in pylint.classes --- pylint/checkers/classes/__init__.py | 370 +---------------- .../classes/special_methods_checker.py | 386 ++++++++++++++++++ 2 files changed, 387 insertions(+), 369 deletions(-) create mode 100644 pylint/checkers/classes/special_methods_checker.py diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index 52601bc421..15c9cdb6c4 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -58,9 +58,9 @@ from astroid import nodes from pylint.checkers import BaseChecker, utils +from pylint.checkers.classes.special_methods_checker import SpecialMethodsChecker from pylint.checkers.utils import ( PYMETHODS, - SPECIAL_METHODS_PARAMS, check_messages, class_is_abstract, decorated_with, @@ -71,7 +71,6 @@ is_attr_protected, is_builtin_object, is_comprehension, - is_function_body_ellipsis, is_iterable, is_overload_stub, is_property_setter, @@ -86,7 +85,6 @@ from pylint.interfaces import IAstroidChecker from pylint.utils import get_global_option -NEXT_METHOD = "__next__" INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"} ASTROID_TYPE_COMPARATORS = { @@ -428,29 +426,6 @@ def _has_bare_super_call(fundef_node): return False -def _safe_infer_call_result(node, caller, context=None): - """ - Safely infer the return value of a function. - - Returns None if inference failed or if there is some ambiguity (more than - one node has been inferred). Otherwise returns inferred value. - """ - try: - inferit = node.infer_call_result(caller, context=context) - value = next(inferit) - except astroid.InferenceError: - return None # inference failed - except StopIteration: - return None # no values inferred - try: - next(inferit) - return None # there is ambiguity on the inferred node - except astroid.InferenceError: - return None # there is some kind of ambiguity - except StopIteration: - return value - - def _has_same_layout_slots(slots, assigned_value): inferred = next(assigned_value.infer()) if isinstance(inferred, nodes.ClassDef): @@ -2065,349 +2040,6 @@ def _is_mandatory_method_param(self, node): ) -class SpecialMethodsChecker(BaseChecker): - """Checker which verifies that special methods - are implemented correctly. - """ - - __implements__ = (IAstroidChecker,) - name = "classes" - msgs = { - "E0301": ( - "__iter__ returns non-iterator", - "non-iterator-returned", - "Used when an __iter__ method returns something which is not an " - f"iterable (i.e. has no `{NEXT_METHOD}` method)", - { - "old_names": [ - ("W0234", "old-non-iterator-returned-1"), - ("E0234", "old-non-iterator-returned-2"), - ] - }, - ), - "E0302": ( - "The special method %r expects %s param(s), %d %s given", - "unexpected-special-method-signature", - "Emitted when a special method was defined with an " - "invalid number of parameters. If it has too few or " - "too many, it might not work at all.", - {"old_names": [("E0235", "bad-context-manager")]}, - ), - "E0303": ( - "__len__ does not return non-negative integer", - "invalid-length-returned", - "Used when a __len__ method returns something which is not a " - "non-negative integer", - ), - "E0304": ( - "__bool__ does not return bool", - "invalid-bool-returned", - "Used when a __bool__ method returns something which is not a bool", - ), - "E0305": ( - "__index__ does not return int", - "invalid-index-returned", - "Used when an __index__ method returns something which is not " - "an integer", - ), - "E0306": ( - "__repr__ does not return str", - "invalid-repr-returned", - "Used when a __repr__ method returns something which is not a string", - ), - "E0307": ( - "__str__ does not return str", - "invalid-str-returned", - "Used when a __str__ method returns something which is not a string", - ), - "E0308": ( - "__bytes__ does not return bytes", - "invalid-bytes-returned", - "Used when a __bytes__ method returns something which is not bytes", - ), - "E0309": ( - "__hash__ does not return int", - "invalid-hash-returned", - "Used when a __hash__ method returns something which is not an integer", - ), - "E0310": ( - "__length_hint__ does not return non-negative integer", - "invalid-length-hint-returned", - "Used when a __length_hint__ method returns something which is not a " - "non-negative integer", - ), - "E0311": ( - "__format__ does not return str", - "invalid-format-returned", - "Used when a __format__ method returns something which is not a string", - ), - "E0312": ( - "__getnewargs__ does not return a tuple", - "invalid-getnewargs-returned", - "Used when a __getnewargs__ method returns something which is not " - "a tuple", - ), - "E0313": ( - "__getnewargs_ex__ does not return a tuple containing (tuple, dict)", - "invalid-getnewargs-ex-returned", - "Used when a __getnewargs_ex__ method returns something which is not " - "of the form tuple(tuple, dict)", - ), - } - priority = -2 - - def __init__(self, linter=None): - super().__init__(linter) - self._protocol_map = { - "__iter__": self._check_iter, - "__len__": self._check_len, - "__bool__": self._check_bool, - "__index__": self._check_index, - "__repr__": self._check_repr, - "__str__": self._check_str, - "__bytes__": self._check_bytes, - "__hash__": self._check_hash, - "__length_hint__": self._check_length_hint, - "__format__": self._check_format, - "__getnewargs__": self._check_getnewargs, - "__getnewargs_ex__": self._check_getnewargs_ex, - } - - @check_messages( - "unexpected-special-method-signature", - "non-iterator-returned", - "invalid-length-returned", - "invalid-bool-returned", - "invalid-index-returned", - "invalid-repr-returned", - "invalid-str-returned", - "invalid-bytes-returned", - "invalid-hash-returned", - "invalid-length-hint-returned", - "invalid-format-returned", - "invalid-getnewargs-returned", - "invalid-getnewargs-ex-returned", - ) - def visit_functiondef(self, node: nodes.FunctionDef) -> None: - if not node.is_method(): - return - - inferred = _safe_infer_call_result(node, node) - # Only want to check types that we are able to infer - if ( - inferred - and node.name in self._protocol_map - and not is_function_body_ellipsis(node) - ): - self._protocol_map[node.name](node, inferred) - - if node.name in PYMETHODS: - self._check_unexpected_method_signature(node) - - visit_asyncfunctiondef = visit_functiondef - - def _check_unexpected_method_signature(self, node): - expected_params = SPECIAL_METHODS_PARAMS[node.name] - - if expected_params is None: - # This can support a variable number of parameters. - return - if not node.args.args and not node.args.vararg: - # Method has no parameter, will be caught - # by no-method-argument. - return - - if decorated_with(node, ["builtins.staticmethod"]): - # We expect to not take in consideration self. - all_args = node.args.args - else: - all_args = node.args.args[1:] - mandatory = len(all_args) - len(node.args.defaults) - optional = len(node.args.defaults) - current_params = mandatory + optional - - if isinstance(expected_params, tuple): - # The expected number of parameters can be any value from this - # tuple, although the user should implement the method - # to take all of them in consideration. - emit = mandatory not in expected_params - # pylint: disable-next=consider-using-f-string - expected_params = "between %d or %d" % expected_params - else: - # If the number of mandatory parameters doesn't - # suffice, the expected parameters for this - # function will be deduced from the optional - # parameters. - rest = expected_params - mandatory - if rest == 0: - emit = False - elif rest < 0: - emit = True - elif rest > 0: - emit = not ((optional - rest) >= 0 or node.args.vararg) - - if emit: - verb = "was" if current_params <= 1 else "were" - self.add_message( - "unexpected-special-method-signature", - args=(node.name, expected_params, current_params, verb), - node=node, - ) - - @staticmethod - def _is_wrapped_type(node, type_): - return ( - isinstance(node, astroid.Instance) - and node.name == type_ - and not isinstance(node, nodes.Const) - ) - - @staticmethod - def _is_int(node): - if SpecialMethodsChecker._is_wrapped_type(node, "int"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, int) - - @staticmethod - def _is_str(node): - if SpecialMethodsChecker._is_wrapped_type(node, "str"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, str) - - @staticmethod - def _is_bool(node): - if SpecialMethodsChecker._is_wrapped_type(node, "bool"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, bool) - - @staticmethod - def _is_bytes(node): - if SpecialMethodsChecker._is_wrapped_type(node, "bytes"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, bytes) - - @staticmethod - def _is_tuple(node): - if SpecialMethodsChecker._is_wrapped_type(node, "tuple"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, tuple) - - @staticmethod - def _is_dict(node): - if SpecialMethodsChecker._is_wrapped_type(node, "dict"): - return True - - return isinstance(node, nodes.Const) and isinstance(node.value, dict) - - @staticmethod - def _is_iterator(node): - if node is astroid.Uninferable: - # Just ignore Uninferable objects. - return True - if isinstance(node, astroid.bases.Generator): - # Generators can be iterated. - return True - - if isinstance(node, astroid.Instance): - try: - node.local_attr(NEXT_METHOD) - return True - except astroid.NotFoundError: - pass - elif isinstance(node, nodes.ClassDef): - metaclass = node.metaclass() - if metaclass and isinstance(metaclass, nodes.ClassDef): - try: - metaclass.local_attr(NEXT_METHOD) - return True - except astroid.NotFoundError: - pass - return False - - def _check_iter(self, node, inferred): - if not self._is_iterator(inferred): - self.add_message("non-iterator-returned", node=node) - - def _check_len(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-length-returned", node=node) - elif isinstance(inferred, nodes.Const) and inferred.value < 0: - self.add_message("invalid-length-returned", node=node) - - def _check_bool(self, node, inferred): - if not self._is_bool(inferred): - self.add_message("invalid-bool-returned", node=node) - - def _check_index(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-index-returned", node=node) - - def _check_repr(self, node, inferred): - if not self._is_str(inferred): - self.add_message("invalid-repr-returned", node=node) - - def _check_str(self, node, inferred): - if not self._is_str(inferred): - self.add_message("invalid-str-returned", node=node) - - def _check_bytes(self, node, inferred): - if not self._is_bytes(inferred): - self.add_message("invalid-bytes-returned", node=node) - - def _check_hash(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-hash-returned", node=node) - - def _check_length_hint(self, node, inferred): - if not self._is_int(inferred): - self.add_message("invalid-length-hint-returned", node=node) - elif isinstance(inferred, nodes.Const) and inferred.value < 0: - self.add_message("invalid-length-hint-returned", node=node) - - def _check_format(self, node, inferred): - if not self._is_str(inferred): - self.add_message("invalid-format-returned", node=node) - - def _check_getnewargs(self, node, inferred): - if not self._is_tuple(inferred): - self.add_message("invalid-getnewargs-returned", node=node) - - def _check_getnewargs_ex(self, node, inferred): - if not self._is_tuple(inferred): - self.add_message("invalid-getnewargs-ex-returned", node=node) - return - - if not isinstance(inferred, nodes.Tuple): - # If it's not an astroid.Tuple we can't analyze it further - return - - found_error = False - - if len(inferred.elts) != 2: - found_error = True - else: - for arg, check in ( - (inferred.elts[0], self._is_tuple), - (inferred.elts[1], self._is_dict), - ): - - if isinstance(arg, nodes.Call): - arg = safe_infer(arg) - - if arg and arg is not astroid.Uninferable: - if not check(arg): - found_error = True - break - - if found_error: - self.add_message("invalid-getnewargs-ex-returned", node=node) - - def _ancestors_to_call(klass_node, method="__init__"): """return a dictionary where keys are the list of base classes providing the queried method, and so that should/may be called from the method node diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py new file mode 100644 index 0000000000..adfb7b3f17 --- /dev/null +++ b/pylint/checkers/classes/special_methods_checker.py @@ -0,0 +1,386 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +"""Special methods checker and helper function's module""" + +import astroid +from astroid import nodes + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + PYMETHODS, + SPECIAL_METHODS_PARAMS, + check_messages, + decorated_with, + is_function_body_ellipsis, + safe_infer, +) +from pylint.interfaces import IAstroidChecker + +NEXT_METHOD = "__next__" + + +def _safe_infer_call_result(node, caller, context=None): + """ + Safely infer the return value of a function. + + Returns None if inference failed or if there is some ambiguity (more than + one node has been inferred). Otherwise returns inferred value. + """ + try: + inferit = node.infer_call_result(caller, context=context) + value = next(inferit) + except astroid.InferenceError: + return None # inference failed + except StopIteration: + return None # no values inferred + try: + next(inferit) + return None # there is ambiguity on the inferred node + except astroid.InferenceError: + return None # there is some kind of ambiguity + except StopIteration: + return value + + +class SpecialMethodsChecker(BaseChecker): + """Checker which verifies that special methods + are implemented correctly. + """ + + __implements__ = (IAstroidChecker,) + name = "classes" + msgs = { + "E0301": ( + "__iter__ returns non-iterator", + "non-iterator-returned", + "Used when an __iter__ method returns something which is not an " + f"iterable (i.e. has no `{NEXT_METHOD}` method)", + { + "old_names": [ + ("W0234", "old-non-iterator-returned-1"), + ("E0234", "old-non-iterator-returned-2"), + ] + }, + ), + "E0302": ( + "The special method %r expects %s param(s), %d %s given", + "unexpected-special-method-signature", + "Emitted when a special method was defined with an " + "invalid number of parameters. If it has too few or " + "too many, it might not work at all.", + {"old_names": [("E0235", "bad-context-manager")]}, + ), + "E0303": ( + "__len__ does not return non-negative integer", + "invalid-length-returned", + "Used when a __len__ method returns something which is not a " + "non-negative integer", + ), + "E0304": ( + "__bool__ does not return bool", + "invalid-bool-returned", + "Used when a __bool__ method returns something which is not a bool", + ), + "E0305": ( + "__index__ does not return int", + "invalid-index-returned", + "Used when an __index__ method returns something which is not " + "an integer", + ), + "E0306": ( + "__repr__ does not return str", + "invalid-repr-returned", + "Used when a __repr__ method returns something which is not a string", + ), + "E0307": ( + "__str__ does not return str", + "invalid-str-returned", + "Used when a __str__ method returns something which is not a string", + ), + "E0308": ( + "__bytes__ does not return bytes", + "invalid-bytes-returned", + "Used when a __bytes__ method returns something which is not bytes", + ), + "E0309": ( + "__hash__ does not return int", + "invalid-hash-returned", + "Used when a __hash__ method returns something which is not an integer", + ), + "E0310": ( + "__length_hint__ does not return non-negative integer", + "invalid-length-hint-returned", + "Used when a __length_hint__ method returns something which is not a " + "non-negative integer", + ), + "E0311": ( + "__format__ does not return str", + "invalid-format-returned", + "Used when a __format__ method returns something which is not a string", + ), + "E0312": ( + "__getnewargs__ does not return a tuple", + "invalid-getnewargs-returned", + "Used when a __getnewargs__ method returns something which is not " + "a tuple", + ), + "E0313": ( + "__getnewargs_ex__ does not return a tuple containing (tuple, dict)", + "invalid-getnewargs-ex-returned", + "Used when a __getnewargs_ex__ method returns something which is not " + "of the form tuple(tuple, dict)", + ), + } + priority = -2 + + def __init__(self, linter=None): + super().__init__(linter) + self._protocol_map = { + "__iter__": self._check_iter, + "__len__": self._check_len, + "__bool__": self._check_bool, + "__index__": self._check_index, + "__repr__": self._check_repr, + "__str__": self._check_str, + "__bytes__": self._check_bytes, + "__hash__": self._check_hash, + "__length_hint__": self._check_length_hint, + "__format__": self._check_format, + "__getnewargs__": self._check_getnewargs, + "__getnewargs_ex__": self._check_getnewargs_ex, + } + + @check_messages( + "unexpected-special-method-signature", + "non-iterator-returned", + "invalid-length-returned", + "invalid-bool-returned", + "invalid-index-returned", + "invalid-repr-returned", + "invalid-str-returned", + "invalid-bytes-returned", + "invalid-hash-returned", + "invalid-length-hint-returned", + "invalid-format-returned", + "invalid-getnewargs-returned", + "invalid-getnewargs-ex-returned", + ) + def visit_functiondef(self, node: nodes.FunctionDef) -> None: + if not node.is_method(): + return + + inferred = _safe_infer_call_result(node, node) + # Only want to check types that we are able to infer + if ( + inferred + and node.name in self._protocol_map + and not is_function_body_ellipsis(node) + ): + self._protocol_map[node.name](node, inferred) + + if node.name in PYMETHODS: + self._check_unexpected_method_signature(node) + + visit_asyncfunctiondef = visit_functiondef + + def _check_unexpected_method_signature(self, node): + expected_params = SPECIAL_METHODS_PARAMS[node.name] + + if expected_params is None: + # This can support a variable number of parameters. + return + if not node.args.args and not node.args.vararg: + # Method has no parameter, will be caught + # by no-method-argument. + return + + if decorated_with(node, ["builtins.staticmethod"]): + # We expect to not take in consideration self. + all_args = node.args.args + else: + all_args = node.args.args[1:] + mandatory = len(all_args) - len(node.args.defaults) + optional = len(node.args.defaults) + current_params = mandatory + optional + + if isinstance(expected_params, tuple): + # The expected number of parameters can be any value from this + # tuple, although the user should implement the method + # to take all of them in consideration. + emit = mandatory not in expected_params + # pylint: disable-next=consider-using-f-string + expected_params = "between %d or %d" % expected_params + else: + # If the number of mandatory parameters doesn't + # suffice, the expected parameters for this + # function will be deduced from the optional + # parameters. + rest = expected_params - mandatory + if rest == 0: + emit = False + elif rest < 0: + emit = True + elif rest > 0: + emit = not ((optional - rest) >= 0 or node.args.vararg) + + if emit: + verb = "was" if current_params <= 1 else "were" + self.add_message( + "unexpected-special-method-signature", + args=(node.name, expected_params, current_params, verb), + node=node, + ) + + @staticmethod + def _is_wrapped_type(node, type_): + return ( + isinstance(node, astroid.Instance) + and node.name == type_ + and not isinstance(node, nodes.Const) + ) + + @staticmethod + def _is_int(node): + if SpecialMethodsChecker._is_wrapped_type(node, "int"): + return True + + return isinstance(node, nodes.Const) and isinstance(node.value, int) + + @staticmethod + def _is_str(node): + if SpecialMethodsChecker._is_wrapped_type(node, "str"): + return True + + return isinstance(node, nodes.Const) and isinstance(node.value, str) + + @staticmethod + def _is_bool(node): + if SpecialMethodsChecker._is_wrapped_type(node, "bool"): + return True + + return isinstance(node, nodes.Const) and isinstance(node.value, bool) + + @staticmethod + def _is_bytes(node): + if SpecialMethodsChecker._is_wrapped_type(node, "bytes"): + return True + + return isinstance(node, nodes.Const) and isinstance(node.value, bytes) + + @staticmethod + def _is_tuple(node): + if SpecialMethodsChecker._is_wrapped_type(node, "tuple"): + return True + + return isinstance(node, nodes.Const) and isinstance(node.value, tuple) + + @staticmethod + def _is_dict(node): + if SpecialMethodsChecker._is_wrapped_type(node, "dict"): + return True + + return isinstance(node, nodes.Const) and isinstance(node.value, dict) + + @staticmethod + def _is_iterator(node): + if node is astroid.Uninferable: + # Just ignore Uninferable objects. + return True + if isinstance(node, astroid.bases.Generator): + # Generators can be iterated. + return True + + if isinstance(node, astroid.Instance): + try: + node.local_attr(NEXT_METHOD) + return True + except astroid.NotFoundError: + pass + elif isinstance(node, nodes.ClassDef): + metaclass = node.metaclass() + if metaclass and isinstance(metaclass, nodes.ClassDef): + try: + metaclass.local_attr(NEXT_METHOD) + return True + except astroid.NotFoundError: + pass + return False + + def _check_iter(self, node, inferred): + if not self._is_iterator(inferred): + self.add_message("non-iterator-returned", node=node) + + def _check_len(self, node, inferred): + if not self._is_int(inferred): + self.add_message("invalid-length-returned", node=node) + elif isinstance(inferred, nodes.Const) and inferred.value < 0: + self.add_message("invalid-length-returned", node=node) + + def _check_bool(self, node, inferred): + if not self._is_bool(inferred): + self.add_message("invalid-bool-returned", node=node) + + def _check_index(self, node, inferred): + if not self._is_int(inferred): + self.add_message("invalid-index-returned", node=node) + + def _check_repr(self, node, inferred): + if not self._is_str(inferred): + self.add_message("invalid-repr-returned", node=node) + + def _check_str(self, node, inferred): + if not self._is_str(inferred): + self.add_message("invalid-str-returned", node=node) + + def _check_bytes(self, node, inferred): + if not self._is_bytes(inferred): + self.add_message("invalid-bytes-returned", node=node) + + def _check_hash(self, node, inferred): + if not self._is_int(inferred): + self.add_message("invalid-hash-returned", node=node) + + def _check_length_hint(self, node, inferred): + if not self._is_int(inferred): + self.add_message("invalid-length-hint-returned", node=node) + elif isinstance(inferred, nodes.Const) and inferred.value < 0: + self.add_message("invalid-length-hint-returned", node=node) + + def _check_format(self, node, inferred): + if not self._is_str(inferred): + self.add_message("invalid-format-returned", node=node) + + def _check_getnewargs(self, node, inferred): + if not self._is_tuple(inferred): + self.add_message("invalid-getnewargs-returned", node=node) + + def _check_getnewargs_ex(self, node, inferred): + if not self._is_tuple(inferred): + self.add_message("invalid-getnewargs-ex-returned", node=node) + return + + if not isinstance(inferred, nodes.Tuple): + # If it's not an astroid.Tuple we can't analyze it further + return + + found_error = False + + if len(inferred.elts) != 2: + found_error = True + else: + for arg, check in ( + (inferred.elts[0], self._is_tuple), + (inferred.elts[1], self._is_dict), + ): + + if isinstance(arg, nodes.Call): + arg = safe_infer(arg) + + if arg and arg is not astroid.Uninferable: + if not check(arg): + found_error = True + break + + if found_error: + self.add_message("invalid-getnewargs-ex-returned", node=node) From d59c6f77f1ab3d8c62fbdb080d492c4356ccc324 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 17 Dec 2021 00:07:28 +0100 Subject: [PATCH 061/357] [refactor] Create a file for the ClassChecker in pylint.classes --- pylint/checkers/classes/__init__.py | 2049 +-------------------- pylint/checkers/classes/class_checker.py | 2052 ++++++++++++++++++++++ 2 files changed, 2053 insertions(+), 2048 deletions(-) create mode 100644 pylint/checkers/classes/class_checker.py diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index 15c9cdb6c4..e46eb583d3 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -1,2056 +1,9 @@ -# Copyright (c) 2006-2016 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2010 Maarten ter Huurne -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2014 David Pursehouse -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2016 Anthony Foglia -# Copyright (c) 2016 Florian Bruhin -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2016 Jakub Wilk -# Copyright (c) 2017, 2019-2020 hippo91 -# Copyright (c) 2018, 2021 Ville Skyttä -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018-2019 Nick Drozd -# Copyright (c) 2018-2019 Ashley Whetter -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Ben Green -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 mattlbeck <17108752+mattlbeck@users.noreply.github.com> -# Copyright (c) 2019-2020 craig-sh -# Copyright (c) 2019 Janne Rönkkö -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Grygorii Iermolenko -# Copyright (c) 2019 Andrzej Klajnert -# Copyright (c) 2019 Pascal Corpet -# Copyright (c) 2020 GergelyKalmar -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Samuel Freilich -# Copyright (c) 2021 Nick Pesce -# Copyright (c) 2021 bot -# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> -# Copyright (c) 2021 SupImDos <62866982+SupImDos@users.noreply.github.com> -# Copyright (c) 2021 Kayran Schmidt <59456929+yumasheta@users.noreply.github.com> -# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> -# Copyright (c) 2021 James Sinclair -# Copyright (c) 2021 tiagohonorato <61059243+tiagohonorato@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Classes checker for Python code""" -import collections -from itertools import chain, zip_longest -from typing import List, Pattern - -import astroid -from astroid import nodes -from pylint.checkers import BaseChecker, utils +from pylint.checkers.classes.class_checker import ClassChecker from pylint.checkers.classes.special_methods_checker import SpecialMethodsChecker -from pylint.checkers.utils import ( - PYMETHODS, - check_messages, - class_is_abstract, - decorated_with, - decorated_with_property, - get_outer_class, - has_known_bases, - is_attr_private, - is_attr_protected, - is_builtin_object, - is_comprehension, - is_iterable, - is_overload_stub, - is_property_setter, - is_property_setter_or_deleter, - is_protocol_class, - node_frame_class, - overrides_a_method, - safe_infer, - unimplemented_abstract_methods, - uninferable_final_decorators, -) -from pylint.interfaces import IAstroidChecker -from pylint.utils import get_global_option - -INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} -BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"} -ASTROID_TYPE_COMPARATORS = { - nodes.Const: lambda a, b: a.value == b.value, - nodes.ClassDef: lambda a, b: a.qname == b.qname, - nodes.Tuple: lambda a, b: a.elts == b.elts, - nodes.List: lambda a, b: a.elts == b.elts, - nodes.Dict: lambda a, b: a.items == b.items, - nodes.Name: lambda a, b: set(a.infer()) == set(b.infer()), -} - -# Dealing with useless override detection, with regard -# to parameters vs arguments - -_CallSignature = collections.namedtuple( - "_CallSignature", "args kws starred_args starred_kws" -) -_ParameterSignature = collections.namedtuple( - "_ParameterSignature", "args kwonlyargs varargs kwargs" -) - - -def _signature_from_call(call): - kws = {} - args = [] - starred_kws = [] - starred_args = [] - for keyword in call.keywords or []: - arg, value = keyword.arg, keyword.value - if arg is None and isinstance(value, nodes.Name): - # Starred node and we are interested only in names, - # otherwise some transformation might occur for the parameter. - starred_kws.append(value.name) - elif isinstance(value, nodes.Name): - kws[arg] = value.name - else: - kws[arg] = None - - for arg in call.args: - if isinstance(arg, nodes.Starred) and isinstance(arg.value, nodes.Name): - # Positional variadic and a name, otherwise some transformation - # might have occurred. - starred_args.append(arg.value.name) - elif isinstance(arg, nodes.Name): - args.append(arg.name) - else: - args.append(None) - - return _CallSignature(args, kws, starred_args, starred_kws) - - -def _signature_from_arguments(arguments): - kwarg = arguments.kwarg - vararg = arguments.vararg - args = [ - arg.name - for arg in chain(arguments.posonlyargs, arguments.args) - if arg.name != "self" - ] - kwonlyargs = [arg.name for arg in arguments.kwonlyargs] - return _ParameterSignature(args, kwonlyargs, vararg, kwarg) - - -def _definition_equivalent_to_call(definition, call): - """Check if a definition signature is equivalent to a call.""" - if definition.kwargs: - if definition.kwargs not in call.starred_kws: - return False - elif call.starred_kws: - return False - if definition.varargs: - if definition.varargs not in call.starred_args: - return False - elif call.starred_args: - return False - if any(kw not in call.kws for kw in definition.kwonlyargs): - return False - if definition.args != call.args: - return False - - # No extra kwargs in call. - return all(kw in call.args or kw in definition.kwonlyargs for kw in call.kws) - - -# Deal with parameters overriding in two methods. - - -def _positional_parameters(method): - positional = method.args.args - if method.is_bound() and method.type in {"classmethod", "method"}: - positional = positional[1:] - return positional - - -class _DefaultMissing: - """Sentinel value for missing arg default, use _DEFAULT_MISSING.""" - - -_DEFAULT_MISSING = _DefaultMissing() - - -def _has_different_parameters_default_value(original, overridden): - """ - Check if original and overridden methods arguments have different default values - - Return True if one of the overridden arguments has a default - value different from the default value of the original argument - If one of the method doesn't have argument (.args is None) - return False - """ - if original.args is None or overridden.args is None: - return False - - for param in chain(original.args, original.kwonlyargs): - try: - original_default = original.default_value(param.name) - except astroid.exceptions.NoDefault: - original_default = _DEFAULT_MISSING - try: - overridden_default = overridden.default_value(param.name) - if original_default is _DEFAULT_MISSING: - # Only the original has a default. - return True - except astroid.exceptions.NoDefault: - if original_default is _DEFAULT_MISSING: - # Both have a default, no difference - continue - # Only the override has a default. - return True - - original_type = type(original_default) - if not isinstance(overridden_default, original_type): - # Two args with same name but different types - return True - is_same_fn = ASTROID_TYPE_COMPARATORS.get(original_type) - if is_same_fn is None: - # If the default value comparison is unhandled, assume the value is different - return True - if not is_same_fn(original_default, overridden_default): - # Two args with same type but different values - return True - return False - - -def _has_different_parameters( - original: List[nodes.AssignName], - overridden: List[nodes.AssignName], - dummy_parameter_regex: Pattern, -) -> List[str]: - result = [] - zipped = zip_longest(original, overridden) - for original_param, overridden_param in zipped: - params = (original_param, overridden_param) - if not all(params): - return ["Number of parameters "] - - # 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 " - f"to '{overridden_param.name}' in" - ) - - return result - - -def _different_parameters( - original: nodes.FunctionDef, - overridden: nodes.FunctionDef, - dummy_parameter_regex: Pattern, -) -> List[str]: - """Determine if the two methods have different parameters - - They are considered to have different parameters if: - - * they have different positional parameters, including different names - - * one of the methods is having variadics, while the other is not - - * they have different keyword only parameters. - - """ - output_messages = [] - original_parameters = _positional_parameters(original) - overridden_parameters = _positional_parameters(overridden) - - # Copy kwonlyargs list so that we don't affect later function linting - original_kwonlyargs = original.args.kwonlyargs - - # Allow positional/keyword variadic in overridden to match against any - # positional/keyword argument in original. - # Keep any arguments that are found separately in overridden to satisfy - # later tests - if overridden.args.vararg: - overridden_names = [v.name for v in overridden_parameters] - original_parameters = [ - v for v in original_parameters if v.name in overridden_names - ] - - if overridden.args.kwarg: - overridden_names = [v.name for v in overridden.args.kwonlyargs] - original_kwonlyargs = [ - v for v in original.args.kwonlyargs if v.name in overridden_names - ] - - different_positional = _has_different_parameters( - original_parameters, overridden_parameters, dummy_parameter_regex - ) - different_kwonly = _has_different_parameters( - 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]: - output_messages.append("Number of parameters ") - output_messages += different_positional[1:] - output_messages += different_kwonly[1:] - else: - output_messages += different_positional - output_messages += different_kwonly - else: - if different_positional: - output_messages += different_positional - if different_kwonly: - output_messages += different_kwonly - - if original.name in PYMETHODS: - # Ignore the difference for special methods. If the parameter - # numbers are different, then that is going to be caught by - # unexpected-special-method-signature. - # If the names are different, it doesn't matter, since they can't - # be used as keyword arguments anyway. - output_messages.clear() - - # Arguments will only violate LSP if there are variadics in the original - # that are then removed from the overridden - kwarg_lost = original.args.kwarg and not overridden.args.kwarg - vararg_lost = original.args.vararg and not overridden.args.vararg - - if kwarg_lost or vararg_lost: - output_messages += ["Variadics removed in"] - - return output_messages - - -def _is_invalid_base_class(cls): - return cls.name in INVALID_BASE_CLASSES and is_builtin_object(cls) - - -def _has_data_descriptor(cls, attr): - attributes = cls.getattr(attr) - for attribute in attributes: - try: - for inferred in attribute.infer(): - if isinstance(inferred, astroid.Instance): - try: - inferred.getattr("__get__") - inferred.getattr("__set__") - except astroid.NotFoundError: - continue - else: - return True - except astroid.InferenceError: - # Can't infer, avoid emitting a false positive in this case. - return True - return False - - -def _called_in_methods(func, klass, methods): - """Check if the func was called in any of the given methods, - belonging to the *klass*. Returns True if so, False otherwise. - """ - if not isinstance(func, nodes.FunctionDef): - return False - for method in methods: - try: - inferred = klass.getattr(method) - except astroid.NotFoundError: - continue - for infer_method in inferred: - for call in infer_method.nodes_of_class(nodes.Call): - try: - bound = next(call.func.infer()) - except (astroid.InferenceError, StopIteration): - continue - if not isinstance(bound, astroid.BoundMethod): - continue - func_obj = bound._proxied - if isinstance(func_obj, astroid.UnboundMethod): - func_obj = func_obj._proxied - if func_obj.name == func.name: - return True - return False - - -def _is_attribute_property(name, klass): - """Check if the given attribute *name* is a property in the given *klass*. - - It will look for `property` calls or for functions - with the given name, decorated by `property` or `property` - subclasses. - Returns ``True`` if the name is a property in the given klass, - ``False`` otherwise. - """ - - try: - attributes = klass.getattr(name) - except astroid.NotFoundError: - return False - property_name = "builtins.property" - for attr in attributes: - if attr is astroid.Uninferable: - continue - try: - inferred = next(attr.infer()) - except astroid.InferenceError: - continue - if isinstance(inferred, nodes.FunctionDef) and decorated_with_property( - inferred - ): - return True - if inferred.pytype() != property_name: - continue - - cls = node_frame_class(inferred) - if cls == klass.declared_metaclass(): - continue - return True - return False - - -def _has_bare_super_call(fundef_node): - for call in fundef_node.nodes_of_class(nodes.Call): - func = call.func - if isinstance(func, nodes.Name) and func.name == "super" and not call.args: - return True - return False - - -def _has_same_layout_slots(slots, assigned_value): - inferred = next(assigned_value.infer()) - if isinstance(inferred, nodes.ClassDef): - other_slots = inferred.slots() - if all( - first_slot and second_slot and first_slot.value == second_slot.value - for (first_slot, second_slot) in zip_longest(slots, other_slots) - ): - return True - return False - - -MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass - "F0202": ( - "Unable to check methods signature (%s / %s)", - "method-check-failed", - "Used when Pylint has been unable to check methods signature " - "compatibility for an unexpected reason. Please report this kind " - "if you don't make sense of it.", - ), - "E0202": ( - "An attribute defined in %s line %s hides this method", - "method-hidden", - "Used when a class defines a method which is hidden by an " - "instance attribute from an ancestor class or set by some " - "client code.", - ), - "E0203": ( - "Access to member %r before its definition line %s", - "access-member-before-definition", - "Used when an instance member is accessed before it's actually assigned.", - ), - "W0201": ( - "Attribute %r defined outside __init__", - "attribute-defined-outside-init", - "Used when an instance attribute is defined outside the __init__ method.", - ), - "W0212": ( - "Access to a protected member %s of a client class", # E0214 - "protected-access", - "Used when a protected member (i.e. class member with a name " - "beginning with an underscore) is access outside the class or a " - "descendant of the class where it's defined.", - ), - "E0211": ( - "Method has no argument", - "no-method-argument", - "Used when a method which should have the bound instance as " - "first argument has no argument defined.", - ), - "E0213": ( - 'Method should have "self" as first argument', - "no-self-argument", - 'Used when a method has an attribute different the "self" as ' - "first argument. This is considered as an error since this is " - "a so common convention that you shouldn't break it!", - ), - "C0202": ( - "Class method %s should have %s as first argument", - "bad-classmethod-argument", - "Used when a class method has a first argument named differently " - "than the value specified in valid-classmethod-first-arg option " - '(default to "cls"), recommended to easily differentiate them ' - "from regular instance methods.", - ), - "C0203": ( - "Metaclass method %s should have %s as first argument", - "bad-mcs-method-argument", - "Used when a metaclass method has a first argument named " - "differently than the value specified in valid-classmethod-first" - '-arg option (default to "cls"), recommended to easily ' - "differentiate them from regular instance methods.", - ), - "C0204": ( - "Metaclass class method %s should have %s as first argument", - "bad-mcs-classmethod-argument", - "Used when a metaclass class method has a first argument named " - "differently than the value specified in valid-metaclass-" - 'classmethod-first-arg option (default to "mcs"), recommended to ' - "easily differentiate them from regular instance methods.", - ), - "W0211": ( - "Static method with %r as first argument", - "bad-staticmethod-argument", - 'Used when a static method has "self" or a value specified in ' - "valid-classmethod-first-arg option or " - "valid-metaclass-classmethod-first-arg option as first argument.", - ), - "R0201": ( - "Method could be a function", - "no-self-use", - "Used when a method doesn't use its bound instance, and so could " - "be written as a function.", - ), - "W0221": ( - "%s %s %r method", - "arguments-differ", - "Used when a method has a different number of arguments than in " - "the implemented interface or in an overridden method.", - ), - "W0222": ( - "Signature differs from %s %r method", - "signature-differs", - "Used when a method signature is different than in the " - "implemented interface or in an overridden method.", - ), - "W0223": ( - "Method %r is abstract in class %r but is not overridden", - "abstract-method", - "Used when an abstract method (i.e. raise NotImplementedError) is " - "not overridden in concrete class.", - ), - "W0231": ( - "__init__ method from base class %r is not called", - "super-init-not-called", - "Used when an ancestor class method has an __init__ method " - "which is not called by a derived class.", - ), - "W0232": ( - "Class has no __init__ method", - "no-init", - "Used when a class has no __init__ method, neither its parent classes.", - ), - "W0233": ( - "__init__ method from a non direct base class %r is called", - "non-parent-init-called", - "Used when an __init__ method is called on a class which is not " - "in the direct ancestors for the analysed class.", - ), - "W0235": ( - "Useless super delegation in method %r", - "useless-super-delegation", - "Used whenever we can detect that an overridden method is useless, " - "relying on super() delegation to do the same thing as another method " - "from the MRO.", - ), - "W0236": ( - "Method %r was expected to be %r, found it instead as %r", - "invalid-overridden-method", - "Used when we detect that a method was overridden in a way " - "that does not match its base class " - "which could result in potential bugs at runtime.", - ), - "W0237": ( - "%s %s %r method", - "arguments-renamed", - "Used when a method parameter has a different name than in " - "the implemented interface or in an overridden method.", - ), - "W0238": ( - "Unused private member `%s.%s`", - "unused-private-member", - "Emitted when a private member of a class is defined but not used.", - ), - "W0239": ( - "Method %r overrides a method decorated with typing.final which is defined in class %r", - "overridden-final-method", - "Used when a method decorated with typing.final has been overridden.", - ), - "W0240": ( - "Class %r is a subclass of a class decorated with typing.final: %r", - "subclassed-final-class", - "Used when a class decorated with typing.final has been subclassed.", - ), - "E0236": ( - "Invalid object %r in __slots__, must contain only non empty strings", - "invalid-slots-object", - "Used when an invalid (non-string) object occurs in __slots__.", - ), - "E0237": ( - "Assigning to attribute %r not defined in class slots", - "assigning-non-slot", - "Used when assigning to an attribute not defined in the class slots.", - ), - "E0238": ( - "Invalid __slots__ object", - "invalid-slots", - "Used when an invalid __slots__ is found in class. " - "Only a string, an iterable or a sequence is permitted.", - ), - "E0239": ( - "Inheriting %r, which is not a class.", - "inherit-non-class", - "Used when a class inherits from something which is not a class.", - ), - "E0240": ( - "Inconsistent method resolution order for class %r", - "inconsistent-mro", - "Used when a class has an inconsistent method resolution order.", - ), - "E0241": ( - "Duplicate bases for class %r", - "duplicate-bases", - "Used when a class has duplicate bases.", - ), - "E0242": ( - "Value %r in slots conflicts with class variable", - "class-variable-slots-conflict", - "Used when a value in __slots__ conflicts with a class variable, property or method.", - ), - "E0243": ( - "Invalid __class__ object", - "invalid-class-object", - "Used when an invalid object is assigned to a __class__ property. " - "Only a class is permitted.", - ), - "R0202": ( - "Consider using a decorator instead of calling classmethod", - "no-classmethod-decorator", - "Used when a class method is defined without using the decorator syntax.", - ), - "R0203": ( - "Consider using a decorator instead of calling staticmethod", - "no-staticmethod-decorator", - "Used when a static method is defined without using the decorator syntax.", - ), - "C0205": ( - "Class __slots__ should be a non-string iterable", - "single-string-used-for-slots", - "Used when a class __slots__ is a simple string, rather than an iterable.", - ), - "R0205": ( - "Class %r inherits from object, can be safely removed from bases in python3", - "useless-object-inheritance", - "Used when a class inherit from object, which under python3 is implicit, " - "hence can be safely removed from bases.", - ), - "R0206": ( - "Cannot have defined parameters for properties", - "property-with-parameters", - "Used when we detect that a property also has parameters, which are useless, " - "given that properties cannot be called with additional arguments.", - ), -} - - -def _scope_default(): - return collections.defaultdict(list) - - -class ScopeAccessMap: - """Store the accessed variables per scope.""" - - def __init__(self): - self._scopes = collections.defaultdict(_scope_default) - - def set_accessed(self, node): - """Set the given node as accessed.""" - - frame = node_frame_class(node) - if frame is None: - # The node does not live in a class. - return - self._scopes[frame][node.attrname].append(node) - - def accessed(self, scope): - """Get the accessed variables for the given scope.""" - return self._scopes.get(scope, {}) - - -class ClassChecker(BaseChecker): - """checks for : - * methods without self as first argument - * overridden methods signature - * access only to existent members via self - * attributes not defined in the __init__ method - * unreachable code - """ - - __implements__ = (IAstroidChecker,) - - # configuration section name - name = "classes" - # messages - msgs = MSGS - priority = -2 - # configuration options - options = ( - ( - "defining-attr-methods", - { - "default": ("__init__", "__new__", "setUp", "__post_init__"), - "type": "csv", - "metavar": "", - "help": "List of method names used to declare (i.e. assign) \ -instance attributes.", - }, - ), - ( - "valid-classmethod-first-arg", - { - "default": ("cls",), - "type": "csv", - "metavar": "", - "help": "List of valid names for the first argument in \ -a class method.", - }, - ), - ( - "valid-metaclass-classmethod-first-arg", - { - "default": ("cls",), - "type": "csv", - "metavar": "", - "help": "List of valid names for the first argument in \ -a metaclass class method.", - }, - ), - ( - "exclude-protected", - { - "default": ( - # namedtuple public API. - "_asdict", - "_fields", - "_replace", - "_source", - "_make", - ), - "type": "csv", - "metavar": "", - "help": ( - "List of member names, which should be excluded " - "from the protected access warning." - ), - }, - ), - ( - "check-protected-access-in-special-methods", - { - "default": False, - "type": "yn", - "metavar": "", - "help": "Warn about protected attribute access inside special methods", - }, - ), - ) - - def __init__(self, linter=None): - super().__init__(linter) - self._accessed = ScopeAccessMap() - self._first_attrs = [] - self._meth_could_be_func = None - - def open(self) -> None: - self._mixin_class_rgx = get_global_option(self, "mixin-class-rgx") - py_version = get_global_option(self, "py-version") - self._py38_plus = py_version >= (3, 8) - - @astroid.decorators.cachedproperty - def _dummy_rgx(self): - return get_global_option(self, "dummy-variables-rgx", default=None) - - @astroid.decorators.cachedproperty - def _ignore_mixin(self): - return get_global_option(self, "ignore-mixin-members", default=True) - - @check_messages( - "abstract-method", - "no-init", - "invalid-slots", - "single-string-used-for-slots", - "invalid-slots-object", - "class-variable-slots-conflict", - "inherit-non-class", - "useless-object-inheritance", - "inconsistent-mro", - "duplicate-bases", - ) - def visit_classdef(self, node: nodes.ClassDef) -> None: - """init visit variable _accessed""" - self._check_bases_classes(node) - # if not an exception or a metaclass - if node.type == "class" and has_known_bases(node): - try: - node.local_attr("__init__") - except astroid.NotFoundError: - self.add_message("no-init", args=node, node=node) - self._check_slots(node) - self._check_proper_bases(node) - self._check_typing_final(node) - self._check_consistent_mro(node) - - def _check_consistent_mro(self, node): - """Detect that a class has a consistent mro or duplicate bases.""" - try: - node.mro() - except astroid.InconsistentMroError: - self.add_message("inconsistent-mro", args=node.name, node=node) - except astroid.DuplicateBasesError: - self.add_message("duplicate-bases", args=node.name, node=node) - except NotImplementedError: - # Old style class, there's no mro so don't do anything. - pass - - def _check_proper_bases(self, node): - """ - Detect that a class inherits something which is not - a class or a type. - """ - for base in node.bases: - ancestor = safe_infer(base) - if not ancestor: - continue - if isinstance(ancestor, astroid.Instance) and ancestor.is_subtype_of( - "builtins.type" - ): - continue - - if not isinstance(ancestor, nodes.ClassDef) or _is_invalid_base_class( - ancestor - ): - self.add_message("inherit-non-class", args=base.as_string(), node=node) - - if ancestor.name == object.__name__: - self.add_message( - "useless-object-inheritance", args=node.name, node=node - ) - - def _check_typing_final(self, node: nodes.ClassDef) -> None: - """Detect that a class does not subclass a class decorated with `typing.final`""" - if not self._py38_plus: - return - for base in node.bases: - ancestor = safe_infer(base) - if not ancestor: - continue - - if isinstance(ancestor, nodes.ClassDef) and ( - decorated_with(ancestor, ["typing.final"]) - or uninferable_final_decorators(ancestor.decorators) - ): - self.add_message( - "subclassed-final-class", - args=(node.name, ancestor.name), - node=node, - ) - - @check_messages("unused-private-member", "attribute-defined-outside-init") - def leave_classdef(self, node: nodes.ClassDef) -> None: - """close a class node: - check that instance attributes are defined in __init__ and check - access to existent members - """ - self._check_unused_private_functions(node) - self._check_unused_private_variables(node) - self._check_unused_private_attributes(node) - self._check_attribute_defined_outside_init(node) - - def _check_unused_private_functions(self, node: nodes.ClassDef) -> None: - for function_def in node.nodes_of_class(nodes.FunctionDef): - if not is_attr_private(function_def.name): - continue - parent_scope = function_def.parent.scope() - if isinstance(parent_scope, nodes.FunctionDef): - # Handle nested functions - if function_def.name in ( - n.name for n in parent_scope.nodes_of_class(nodes.Name) - ): - continue - for attribute in node.nodes_of_class(nodes.Attribute): - if ( - attribute.attrname != function_def.name - or attribute.scope() == function_def # We ignore recursive calls - ): - continue - if isinstance(attribute.expr, nodes.Name) and attribute.expr.name in ( - "self", - "cls", - node.name, - ): - # self.__attrname - # cls.__attrname - # node_name.__attrname - break - if isinstance(attribute.expr, nodes.Call): - # type(self).__attrname - inferred = safe_infer(attribute.expr) - if ( - isinstance(inferred, nodes.ClassDef) - and inferred.name == node.name - ): - break - else: - name_stack = [] - curr = parent_scope - # Generate proper names for nested functions - while curr != node: - name_stack.append(curr.name) - curr = curr.parent.scope() - - outer_level_names = f"{'.'.join(reversed(name_stack))}" - function_repr = f"{outer_level_names}.{function_def.name}({function_def.args.as_string()})" - self.add_message( - "unused-private-member", - node=function_def, - args=(node.name, function_repr.lstrip(".")), - ) - - def _check_unused_private_variables(self, node: nodes.ClassDef) -> None: - """Check if private variables are never used within a class""" - for assign_name in node.nodes_of_class(nodes.AssignName): - if isinstance(assign_name.parent, nodes.Arguments): - continue # Ignore function arguments - if not is_attr_private(assign_name.name): - continue - for child in node.nodes_of_class((nodes.Name, nodes.Attribute)): - if isinstance(child, nodes.Name) and child.name == assign_name.name: - break - if isinstance(child, nodes.Attribute): - if not isinstance(child.expr, nodes.Name): - break - if child.attrname == assign_name.name and child.expr.name in ( - "self", - "cls", - node.name, - ): - break - else: - args = (node.name, assign_name.name) - self.add_message("unused-private-member", node=assign_name, args=args) - - def _check_unused_private_attributes(self, node: nodes.ClassDef) -> None: - for assign_attr in node.nodes_of_class(nodes.AssignAttr): - if not is_attr_private(assign_attr.attrname) or not isinstance( - assign_attr.expr, nodes.Name - ): - continue - - # Logic for checking false positive when using __new__, - # Get the returned object names of the __new__ magic function - # Then check if the attribute was consumed in other instance methods - acceptable_obj_names: List[str] = ["self"] - scope = assign_attr.scope() - if isinstance(scope, nodes.FunctionDef) and scope.name == "__new__": - acceptable_obj_names.extend( - [ - return_node.value.name - for return_node in scope.nodes_of_class(nodes.Return) - if isinstance(return_node.value, nodes.Name) - ] - ) - - for attribute in node.nodes_of_class(nodes.Attribute): - if attribute.attrname != assign_attr.attrname: - continue - - if ( - assign_attr.expr.name - in { - "cls", - node.name, - } - and attribute.expr.name in {"cls", "self", node.name} - ): - # If assigned to cls or class name, can be accessed by cls/self/class name - break - - if ( - assign_attr.expr.name in acceptable_obj_names - and attribute.expr.name == "self" - ): - # If assigned to self.attrib, can only be accessed by self - # Or if __new__ was used, the returned object names are acceptable - break - - if assign_attr.expr.name == attribute.expr.name == node.name: - # Recognise attributes which are accessed via the class name - break - - else: - args = (node.name, assign_attr.attrname) - self.add_message("unused-private-member", node=assign_attr, args=args) - - def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: - # check access to existent members on non metaclass classes - if self._ignore_mixin and self._mixin_class_rgx.match(cnode.name): - # We are in a mixin class. No need to try to figure out if - # something is missing, since it is most likely that it will - # miss. - return - - accessed = self._accessed.accessed(cnode) - if cnode.type != "metaclass": - self._check_accessed_members(cnode, accessed) - # checks attributes are defined in an allowed method such as __init__ - if not self.linter.is_message_enabled("attribute-defined-outside-init"): - return - defining_methods = self.config.defining_attr_methods - current_module = cnode.root() - for attr, nodes_lst in cnode.instance_attrs.items(): - # Exclude `__dict__` as it is already defined. - if attr == "__dict__": - continue - - # Skip nodes which are not in the current module and it may screw up - # the output, while it's not worth it - nodes_lst = [ - n - for n in nodes_lst - if not isinstance( - n.statement(future=True), (nodes.Delete, nodes.AugAssign) - ) - and n.root() is current_module - ] - if not nodes_lst: - continue # error detected by typechecking - - # Check if any method attr is defined in is a defining method - # or if we have the attribute defined in a setter. - frames = (node.frame() for node in nodes_lst) - if any( - frame.name in defining_methods or is_property_setter(frame) - for frame in frames - ): - continue - - # check attribute is defined in a parent's __init__ - for parent in cnode.instance_attr_ancestors(attr): - attr_defined = False - # check if any parent method attr is defined in is a defining method - for node in parent.instance_attrs[attr]: - if node.frame().name in defining_methods: - attr_defined = True - if attr_defined: - # we're done :) - break - else: - # check attribute is defined as a class attribute - try: - cnode.local_attr(attr) - except astroid.NotFoundError: - for node in nodes_lst: - if node.frame().name not in defining_methods: - # If the attribute was set by a call in any - # of the defining methods, then don't emit - # the warning. - if _called_in_methods( - node.frame(), cnode, defining_methods - ): - continue - self.add_message( - "attribute-defined-outside-init", args=attr, node=node - ) - - def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """check method arguments, overriding""" - # ignore actual functions - if not node.is_method(): - return - - self._check_useless_super_delegation(node) - self._check_property_with_parameters(node) - - klass = node.parent.frame() - self._meth_could_be_func = True - # check first argument is self if this is actually a method - self._check_first_arg_for_type(node, klass.type == "metaclass") - if node.name == "__init__": - self._check_init(node) - return - # check signature if the method overloads inherited method - for overridden in klass.local_attr_ancestors(node.name): - # get astroid for the searched method - try: - parent_function = overridden[node.name] - except KeyError: - # we have found the method but it's not in the local - # dictionary. - # This may happen with astroid build from living objects - continue - if not isinstance(parent_function, nodes.FunctionDef): - continue - self._check_signature(node, parent_function, "overridden", klass) - self._check_invalid_overridden_method(node, parent_function) - break - - if node.decorators: - for decorator in node.decorators.nodes: - if isinstance(decorator, nodes.Attribute) and decorator.attrname in { - "getter", - "setter", - "deleter", - }: - # attribute affectation will call this method, not hiding it - return - if isinstance(decorator, nodes.Name): - if decorator.name == "property": - # attribute affectation will either call a setter or raise - # an attribute error, anyway not hiding the function - return - - # Infer the decorator and see if it returns something useful - inferred = safe_infer(decorator) - if not inferred: - return - if isinstance(inferred, nodes.FunctionDef): - # Okay, it's a decorator, let's see what it can infer. - try: - inferred = next(inferred.infer_call_result(inferred)) - except astroid.InferenceError: - return - try: - if ( - isinstance(inferred, (astroid.Instance, nodes.ClassDef)) - and inferred.getattr("__get__") - and inferred.getattr("__set__") - ): - return - except astroid.AttributeInferenceError: - pass - - # check if the method is hidden by an attribute - try: - overridden = klass.instance_attr(node.name)[0] - overridden_frame = overridden.frame() - if ( - isinstance(overridden_frame, nodes.FunctionDef) - and overridden_frame.type == "method" - ): - overridden_frame = overridden_frame.parent.frame() - if not ( - isinstance(overridden_frame, nodes.ClassDef) - and klass.is_subtype_of(overridden_frame.qname()) - ): - return - - # If a subclass defined the method then it's not our fault. - for ancestor in klass.ancestors(): - if node.name in ancestor.instance_attrs and is_attr_private(node.name): - return - for obj in ancestor.lookup(node.name)[1]: - if isinstance(obj, nodes.FunctionDef): - return - args = (overridden.root().name, overridden.fromlineno) - self.add_message("method-hidden", args=args, node=node) - except astroid.NotFoundError: - pass - - visit_asyncfunctiondef = visit_functiondef - - def _check_useless_super_delegation(self, function): - """Check if the given function node is an useless method override - - We consider it *useless* if it uses the super() builtin, but having - nothing additional whatsoever than not implementing the method at all. - If the method uses super() to delegate an operation to the rest of the MRO, - and if the method called is the same as the current one, the arguments - passed to super() are the same as the parameters that were passed to - this method, then the method could be removed altogether, by letting - other implementation to take precedence. - """ - - if ( - not function.is_method() - # With decorators is a change of use - or function.decorators - ): - return - - body = function.body - if len(body) != 1: - # Multiple statements, which means this overridden method - # could do multiple things we are not aware of. - return - - statement = body[0] - if not isinstance(statement, (nodes.Expr, nodes.Return)): - # Doing something else than what we are interested into. - return - - call = statement.value - if ( - not isinstance(call, nodes.Call) - # Not a super() attribute access. - or not isinstance(call.func, nodes.Attribute) - ): - return - - # Should be a super call. - try: - super_call = next(call.func.expr.infer()) - except astroid.InferenceError: - return - else: - if not isinstance(super_call, astroid.objects.Super): - return - - # The name should be the same. - if call.func.attrname != function.name: - return - - # Should be a super call with the MRO pointer being the - # current class and the type being the current instance. - current_scope = function.parent.scope() - if ( - super_call.mro_pointer != current_scope - or not isinstance(super_call.type, astroid.Instance) - or super_call.type.name != current_scope.name - ): - return - - # Check values of default args - klass = function.parent.frame() - meth_node = None - for overridden in klass.local_attr_ancestors(function.name): - # get astroid for the searched method - try: - meth_node = overridden[function.name] - except KeyError: - # we have found the method but it's not in the local - # dictionary. - # This may happen with astroid build from living objects - continue - if ( - not isinstance(meth_node, nodes.FunctionDef) - # If the method have an ancestor which is not a - # function then it is legitimate to redefine it - or _has_different_parameters_default_value( - meth_node.args, function.args - ) - ): - return - break - - # Detect if the parameters are the same as the call's arguments. - params = _signature_from_arguments(function.args) - args = _signature_from_call(call) - - if meth_node is not None: - - def form_annotations(arguments): - annotations = chain( - (arguments.posonlyargs_annotations or []), arguments.annotations - ) - return [ann.as_string() for ann in annotations if ann is not None] - - called_annotations = form_annotations(function.args) - overridden_annotations = form_annotations(meth_node.args) - if called_annotations and overridden_annotations: - if called_annotations != overridden_annotations: - return - - if _definition_equivalent_to_call(params, args): - self.add_message( - "useless-super-delegation", node=function, args=(function.name,) - ) - - def _check_property_with_parameters(self, node): - if ( - node.args.args - and len(node.args.args) > 1 - and decorated_with_property(node) - and not is_property_setter(node) - ): - self.add_message("property-with-parameters", node=node) - - def _check_invalid_overridden_method(self, function_node, parent_function_node): - parent_is_property = decorated_with_property( - parent_function_node - ) or is_property_setter_or_deleter(parent_function_node) - current_is_property = decorated_with_property( - function_node - ) or is_property_setter_or_deleter(function_node) - if parent_is_property and not current_is_property: - self.add_message( - "invalid-overridden-method", - args=(function_node.name, "property", function_node.type), - node=function_node, - ) - elif not parent_is_property and current_is_property: - self.add_message( - "invalid-overridden-method", - args=(function_node.name, "method", "property"), - node=function_node, - ) - - parent_is_async = isinstance(parent_function_node, nodes.AsyncFunctionDef) - current_is_async = isinstance(function_node, nodes.AsyncFunctionDef) - - if parent_is_async and not current_is_async: - self.add_message( - "invalid-overridden-method", - args=(function_node.name, "async", "non-async"), - node=function_node, - ) - - elif not parent_is_async and current_is_async: - self.add_message( - "invalid-overridden-method", - args=(function_node.name, "non-async", "async"), - node=function_node, - ) - if ( - decorated_with(parent_function_node, ["typing.final"]) - or uninferable_final_decorators(parent_function_node.decorators) - ) and self._py38_plus: - self.add_message( - "overridden-final-method", - args=(function_node.name, parent_function_node.parent.name), - node=function_node, - ) - - def _check_slots(self, node): - if "__slots__" not in node.locals: - return - for slots in node.igetattr("__slots__"): - # check if __slots__ is a valid type - if slots is astroid.Uninferable: - continue - if not is_iterable(slots) and not is_comprehension(slots): - self.add_message("invalid-slots", node=node) - continue - - if isinstance(slots, nodes.Const): - # a string, ignore the following checks - self.add_message("single-string-used-for-slots", node=node) - continue - if not hasattr(slots, "itered"): - # we can't obtain the values, maybe a .deque? - continue - - if isinstance(slots, nodes.Dict): - values = [item[0] for item in slots.items] - else: - values = slots.itered() - if values is astroid.Uninferable: - return - for elt in values: - try: - self._check_slots_elt(elt, node) - except astroid.InferenceError: - continue - - def _check_slots_elt(self, elt, node): - for inferred in elt.infer(): - if inferred is astroid.Uninferable: - continue - if not isinstance(inferred, nodes.Const) or not isinstance( - inferred.value, str - ): - self.add_message( - "invalid-slots-object", args=inferred.as_string(), node=elt - ) - continue - if not inferred.value: - self.add_message( - "invalid-slots-object", args=inferred.as_string(), node=elt - ) - - # Check if we have a conflict with a class variable. - class_variable = node.locals.get(inferred.value) - if class_variable: - # Skip annotated assignments which don't conflict at all with slots. - if len(class_variable) == 1: - parent = class_variable[0].parent - if isinstance(parent, nodes.AnnAssign) and parent.value is None: - return - self.add_message( - "class-variable-slots-conflict", args=(inferred.value,), node=elt - ) - - def leave_functiondef(self, node: nodes.FunctionDef) -> None: - """on method node, check if this method couldn't be a function - - ignore class, static and abstract methods, initializer, - methods overridden from a parent class. - """ - if node.is_method(): - if node.args.args is not None: - self._first_attrs.pop() - if not self.linter.is_message_enabled("no-self-use"): - return - class_node = node.parent.frame() - if ( - self._meth_could_be_func - and node.type == "method" - and node.name not in PYMETHODS - and not ( - node.is_abstract() - or overrides_a_method(class_node, node.name) - or decorated_with_property(node) - or _has_bare_super_call(node) - or is_protocol_class(class_node) - or is_overload_stub(node) - ) - ): - self.add_message("no-self-use", node=node) - - leave_asyncfunctiondef = leave_functiondef - - def visit_attribute(self, node: nodes.Attribute) -> None: - """check if the getattr is an access to a class member - if so, register it. Also check for access to protected - class member from outside its class (but ignore __special__ - methods) - """ - # Check self - if self._uses_mandatory_method_param(node): - self._accessed.set_accessed(node) - return - if not self.linter.is_message_enabled("protected-access"): - return - - self._check_protected_attribute_access(node) - - @check_messages("assigning-non-slot", "invalid-class-object") - def visit_assignattr(self, node: nodes.AssignAttr) -> None: - if isinstance( - node.assign_type(), nodes.AugAssign - ) and self._uses_mandatory_method_param(node): - self._accessed.set_accessed(node) - self._check_in_slots(node) - self._check_invalid_class_object(node) - - def _check_invalid_class_object(self, node: nodes.AssignAttr) -> None: - if not node.attrname == "__class__": - return - inferred = safe_infer(node.parent.value) - if isinstance(inferred, nodes.ClassDef) or inferred is astroid.Uninferable: - # If is uninferrable, we allow it to prevent false positives - return - self.add_message("invalid-class-object", node=node) - - def _check_in_slots(self, node): - """Check that the given AssignAttr node - is defined in the class slots. - """ - inferred = safe_infer(node.expr) - if not isinstance(inferred, astroid.Instance): - return - - klass = inferred._proxied - if not has_known_bases(klass): - return - if "__slots__" not in klass.locals or not klass.newstyle: - return - # If `__setattr__` is defined on the class, then we can't reason about - # what will happen when assigning to an attribute. - if any( - base.locals.get("__setattr__") - for base in klass.mro() - if base.qname() != "builtins.object" - ): - return - - # If 'typing.Generic' is a base of bases of klass, the cached version - # of 'slots()' might have been evaluated incorrectly, thus deleted cache entry. - if any(base.qname() == "typing.Generic" for base in klass.mro()): - cache = getattr(klass, "__cache", None) - if cache and cache.get(klass.slots) is not None: - del cache[klass.slots] - - slots = klass.slots() - if slots is None: - return - # If any ancestor doesn't use slots, the slots - # defined for this class are superfluous. - if any( - "__slots__" not in ancestor.locals and ancestor.name != "object" - for ancestor in klass.ancestors() - ): - return - - if not any(slot.value == node.attrname for slot in slots): - # If we have a '__dict__' in slots, then - # assigning any name is valid. - if not any(slot.value == "__dict__" for slot in slots): - if _is_attribute_property(node.attrname, klass): - # Properties circumvent the slots mechanism, - # so we should not emit a warning for them. - return - if node.attrname in klass.locals and _has_data_descriptor( - klass, node.attrname - ): - # Descriptors circumvent the slots mechanism as well. - return - if node.attrname == "__class__" and _has_same_layout_slots( - slots, node.parent.value - ): - return - self.add_message("assigning-non-slot", args=(node.attrname,), node=node) - - @check_messages( - "protected-access", "no-classmethod-decorator", "no-staticmethod-decorator" - ) - def visit_assign(self, assign_node: nodes.Assign) -> None: - self._check_classmethod_declaration(assign_node) - node = assign_node.targets[0] - if not isinstance(node, nodes.AssignAttr): - return - - if self._uses_mandatory_method_param(node): - return - self._check_protected_attribute_access(node) - - def _check_classmethod_declaration(self, node): - """Checks for uses of classmethod() or staticmethod() - - When a @classmethod or @staticmethod decorator should be used instead. - A message will be emitted only if the assignment is at a class scope - and only if the classmethod's argument belongs to the class where it - is defined. - `node` is an assign node. - """ - if not isinstance(node.value, nodes.Call): - return - - # check the function called is "classmethod" or "staticmethod" - func = node.value.func - if not isinstance(func, nodes.Name) or func.name not in ( - "classmethod", - "staticmethod", - ): - return - - msg = ( - "no-classmethod-decorator" - if func.name == "classmethod" - else "no-staticmethod-decorator" - ) - # assignment must be at a class scope - parent_class = node.scope() - if not isinstance(parent_class, nodes.ClassDef): - return - - # Check if the arg passed to classmethod is a class member - classmeth_arg = node.value.args[0] - if not isinstance(classmeth_arg, nodes.Name): - return - - method_name = classmeth_arg.name - if any(method_name == member.name for member in parent_class.mymethods()): - self.add_message(msg, node=node.targets[0]) - - def _check_protected_attribute_access(self, node: nodes.Attribute): - """Given an attribute access node (set or get), check if attribute - access is legitimate. Call _check_first_attr with node before calling - this method. Valid cases are: - * self._attr in a method or cls._attr in a classmethod. Checked by - _check_first_attr. - * Klass._attr inside "Klass" class. - * Klass2._attr inside "Klass" class when Klass2 is a base class of - Klass. - """ - attrname = node.attrname - - if ( - is_attr_protected(attrname) - and attrname not in self.config.exclude_protected - ): - - klass = node_frame_class(node) - - # In classes, check we are not getting a parent method - # through the class object or through super - callee = node.expr.as_string() - - # Typing annotations in function definitions can include protected members - if utils.is_node_in_type_annotation_context(node): - return - - # We are not in a class, no remaining valid case - if klass is None: - self.add_message("protected-access", node=node, args=attrname) - return - - # If the expression begins with a call to super, that's ok. - if ( - isinstance(node.expr, nodes.Call) - and isinstance(node.expr.func, nodes.Name) - and node.expr.func.name == "super" - ): - return - - # If the expression begins with a call to type(self), that's ok. - if self._is_type_self_call(node.expr): - return - - # Check if we are inside the scope of a class or nested inner class - inside_klass = True - outer_klass = klass - parents_callee = callee.split(".") - parents_callee.reverse() - for callee in parents_callee: - if not outer_klass or callee != outer_klass.name: - inside_klass = False - break - - # Move up one level within the nested classes - outer_klass = get_outer_class(outer_klass) - - # We are in a class, one remaining valid cases, Klass._attr inside - # Klass - if not (inside_klass or callee in klass.basenames): - # Detect property assignments in the body of the class. - # This is acceptable: - # - # class A: - # b = property(lambda: self._b) - - stmt = node.parent.statement(future=True) - if ( - isinstance(stmt, nodes.Assign) - and len(stmt.targets) == 1 - and isinstance(stmt.targets[0], nodes.AssignName) - ): - name = stmt.targets[0].name - if _is_attribute_property(name, klass): - return - - if ( - self._is_classmethod(node.frame()) - and self._is_inferred_instance(node.expr, klass) - and self._is_class_attribute(attrname, klass) - ): - return - - licit_protected_member = not attrname.startswith("__") - if ( - not self.config.check_protected_access_in_special_methods - and licit_protected_member - and self._is_called_inside_special_method(node) - ): - return - - self.add_message("protected-access", node=node, args=attrname) - - @staticmethod - def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: - """ - Returns true if the node is located inside a special (aka dunder) method - """ - try: - frame_name = node.frame().name - except AttributeError: - return False - return frame_name and frame_name in PYMETHODS - - def _is_type_self_call(self, expr): - return ( - isinstance(expr, nodes.Call) - and isinstance(expr.func, nodes.Name) - and expr.func.name == "type" - and len(expr.args) == 1 - and self._is_mandatory_method_param(expr.args[0]) - ) - - @staticmethod - def _is_classmethod(func): - """Check if the given *func* node is a class method.""" - - return isinstance(func, nodes.FunctionDef) and ( - func.type == "classmethod" or func.name == "__class_getitem__" - ) - - @staticmethod - def _is_inferred_instance(expr, klass): - """Check if the inferred value of the given *expr* is an instance of *klass*.""" - - inferred = safe_infer(expr) - if not isinstance(inferred, astroid.Instance): - return False - - return inferred._proxied is klass - - @staticmethod - def _is_class_attribute(name, klass): - """Check if the given attribute *name* is a class or instance member of the given *klass*. - - Returns ``True`` if the name is a property in the given klass, - ``False`` otherwise. - """ - - try: - klass.getattr(name) - return True - except astroid.NotFoundError: - pass - - try: - klass.instance_attr(name) - return True - except astroid.NotFoundError: - return False - - def visit_name(self, node: nodes.Name) -> None: - """check if the name handle an access to a class member - if so, register it - """ - if self._first_attrs and ( - node.name == self._first_attrs[-1] or not self._first_attrs[-1] - ): - self._meth_could_be_func = False - - def _check_accessed_members(self, node, accessed): - """check that accessed members are defined""" - excs = ("AttributeError", "Exception", "BaseException") - for attr, nodes_lst in accessed.items(): - try: - # is it a class attribute ? - node.local_attr(attr) - # yes, stop here - continue - except astroid.NotFoundError: - pass - # is it an instance attribute of a parent class ? - try: - next(node.instance_attr_ancestors(attr)) - # yes, stop here - continue - except StopIteration: - pass - # is it an instance attribute ? - try: - defstmts = node.instance_attr(attr) - except astroid.NotFoundError: - pass - else: - # filter out augment assignment nodes - defstmts = [stmt for stmt in defstmts if stmt not in nodes_lst] - if not defstmts: - # only augment assignment for this node, no-member should be - # triggered by the typecheck checker - continue - # filter defstmts to only pick the first one when there are - # several assignments in the same scope - scope = defstmts[0].scope() - defstmts = [ - stmt - for i, stmt in enumerate(defstmts) - if i == 0 or stmt.scope() is not scope - ] - # if there are still more than one, don't attempt to be smarter - # than we can be - if len(defstmts) == 1: - defstmt = defstmts[0] - # check that if the node is accessed in the same method as - # it's defined, it's accessed after the initial assignment - frame = defstmt.frame() - lno = defstmt.fromlineno - for _node in nodes_lst: - if ( - _node.frame() is frame - and _node.fromlineno < lno - and not astroid.are_exclusive( - _node.statement(future=True), defstmt, excs - ) - ): - self.add_message( - "access-member-before-definition", - node=_node, - args=(attr, lno), - ) - - def _check_first_arg_for_type(self, node, metaclass=0): - """check the name of first argument, expect: - - * 'self' for a regular method - * 'cls' for a class method or a metaclass regular method (actually - valid-classmethod-first-arg value) - * 'mcs' for a metaclass class method (actually - valid-metaclass-classmethod-first-arg) - * not one of the above for a static method - """ - # don't care about functions with unknown argument (builtins) - if node.args.args is None: - return - if node.args.posonlyargs: - first_arg = node.args.posonlyargs[0].name - elif node.args.args: - first_arg = node.argnames()[0] - else: - first_arg = None - self._first_attrs.append(first_arg) - first = self._first_attrs[-1] - # static method - if node.type == "staticmethod": - if ( - first_arg == "self" - or first_arg in self.config.valid_classmethod_first_arg - or first_arg in self.config.valid_metaclass_classmethod_first_arg - ): - self.add_message("bad-staticmethod-argument", args=first, node=node) - return - self._first_attrs[-1] = None - # class / regular method with no args - elif not node.args.args and not node.args.posonlyargs: - self.add_message("no-method-argument", node=node) - # metaclass - elif metaclass: - # metaclass __new__ or classmethod - if node.type == "classmethod": - self._check_first_arg_config( - first, - self.config.valid_metaclass_classmethod_first_arg, - node, - "bad-mcs-classmethod-argument", - node.name, - ) - # metaclass regular method - else: - self._check_first_arg_config( - first, - self.config.valid_classmethod_first_arg, - node, - "bad-mcs-method-argument", - node.name, - ) - # regular class with class method - elif node.type == "classmethod" or node.name == "__class_getitem__": - self._check_first_arg_config( - first, - self.config.valid_classmethod_first_arg, - node, - "bad-classmethod-argument", - node.name, - ) - # regular class with regular method without self as argument - elif first != "self": - self.add_message("no-self-argument", node=node) - - def _check_first_arg_config(self, first, config, node, message, method_name): - if first not in config: - if len(config) == 1: - valid = repr(config[0]) - else: - valid = ", ".join(repr(v) for v in config[:-1]) - valid = f"{valid} or {config[-1]!r}" - self.add_message(message, args=(method_name, valid), node=node) - - def _check_bases_classes(self, node): - """check that the given class node implements abstract methods from - base classes - """ - - def is_abstract(method): - return method.is_abstract(pass_is_abstract=False) - - # check if this class abstract - if class_is_abstract(node): - return - - methods = sorted( - unimplemented_abstract_methods(node, is_abstract).items(), - key=lambda item: item[0], - ) - for name, method in methods: - owner = method.parent.frame() - if owner is node: - continue - # owner is not this class, it must be a parent class - # check that the ancestor's method is not abstract - if name in node.locals: - # it is redefined as an attribute or with a descriptor - continue - self.add_message("abstract-method", node=node, args=(name, owner.name)) - - def _check_init(self, node): - """check that the __init__ method call super or ancestors'__init__ - method (unless it is used for type hinting with `typing.overload`) - """ - if not self.linter.is_message_enabled( - "super-init-not-called" - ) and not self.linter.is_message_enabled("non-parent-init-called"): - return - klass_node = node.parent.frame() - to_call = _ancestors_to_call(klass_node) - not_called_yet = dict(to_call) - for stmt in node.nodes_of_class(nodes.Call): - expr = stmt.func - if not isinstance(expr, nodes.Attribute) or expr.attrname != "__init__": - continue - # skip the test if using super - if ( - isinstance(expr.expr, nodes.Call) - and isinstance(expr.expr.func, nodes.Name) - and expr.expr.func.name == "super" - ): - return - try: - for klass in expr.expr.infer(): - if klass is astroid.Uninferable: - continue - # The inferred klass can be super(), which was - # assigned to a variable and the `__init__` - # was called later. - # - # base = super() - # base.__init__(...) - - if ( - isinstance(klass, astroid.Instance) - and isinstance(klass._proxied, nodes.ClassDef) - and is_builtin_object(klass._proxied) - and klass._proxied.name == "super" - ): - return - if isinstance(klass, astroid.objects.Super): - return - try: - del not_called_yet[klass] - except KeyError: - if klass not in to_call: - self.add_message( - "non-parent-init-called", node=expr, args=klass.name - ) - except astroid.InferenceError: - continue - for klass, method in not_called_yet.items(): - if decorated_with(node, ["typing.overload"]): - continue - cls = node_frame_class(method) - if klass.name == "object" or (cls and cls.name == "object"): - continue - self.add_message("super-init-not-called", args=klass.name, node=node) - - def _check_signature(self, method1, refmethod, class_type, cls): - """check that the signature of the two given methods match""" - if not ( - isinstance(method1, nodes.FunctionDef) - and isinstance(refmethod, nodes.FunctionDef) - ): - self.add_message( - "method-check-failed", args=(method1, refmethod), node=method1 - ) - return - - instance = cls.instantiate_class() - method1 = astroid.scoped_nodes.function_to_method(method1, instance) - refmethod = astroid.scoped_nodes.function_to_method(refmethod, instance) - - # Don't care about functions with unknown argument (builtins). - if method1.args.args is None or refmethod.args.args is None: - return - - # Ignore private to class methods. - if is_attr_private(method1.name): - return - # Ignore setters, they have an implicit extra argument, - # which shouldn't be taken in consideration. - if is_property_setter(method1): - return - - arg_differ_output = _different_parameters( - refmethod, method1, dummy_parameter_regex=self._dummy_rgx - ) - if len(arg_differ_output) > 0: - for msg in arg_differ_output: - if "Number" in msg: - total_args_method1 = len(method1.args.args) - if method1.args.vararg: - total_args_method1 += 1 - if method1.args.kwarg: - total_args_method1 += 1 - if method1.args.kwonlyargs: - total_args_method1 += len(method1.args.kwonlyargs) - total_args_refmethod = len(refmethod.args.args) - if refmethod.args.vararg: - total_args_refmethod += 1 - if refmethod.args.kwarg: - total_args_refmethod += 1 - if refmethod.args.kwonlyargs: - total_args_refmethod += len(refmethod.args.kwonlyargs) - error_type = "arguments-differ" - msg_args = ( - msg - + f"was {total_args_refmethod} in '{refmethod.parent.name}.{refmethod.name}' and " - f"is now {total_args_method1} in", - class_type, - f"{method1.parent.name}.{method1.name}", - ) - elif "renamed" in msg: - error_type = "arguments-renamed" - msg_args = ( - msg, - class_type, - f"{method1.parent.name}.{method1.name}", - ) - else: - error_type = "arguments-differ" - msg_args = ( - msg, - class_type, - f"{method1.parent.name}.{method1.name}", - ) - self.add_message(error_type, args=msg_args, node=method1) - elif ( - len(method1.args.defaults) < len(refmethod.args.defaults) - and not method1.args.vararg - ): - self.add_message( - "signature-differs", args=(class_type, method1.name), node=method1 - ) - - def _uses_mandatory_method_param(self, node): - """Check that attribute lookup name use first attribute variable name - - Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. - """ - return self._is_mandatory_method_param(node.expr) - - def _is_mandatory_method_param(self, node): - """Check if nodes.Name corresponds to first attribute variable name - - Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. - """ - return ( - self._first_attrs - and isinstance(node, nodes.Name) - and node.name == self._first_attrs[-1] - ) - - -def _ancestors_to_call(klass_node, method="__init__"): - """return a dictionary where keys are the list of base classes providing - the queried method, and so that should/may be called from the method node - """ - to_call = {} - for base_node in klass_node.ancestors(recurs=False): - try: - to_call[base_node] = next(base_node.igetattr(method)) - except astroid.InferenceError: - continue - return to_call def register(linter): diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py new file mode 100644 index 0000000000..b76e10e43a --- /dev/null +++ b/pylint/checkers/classes/class_checker.py @@ -0,0 +1,2052 @@ +# Copyright (c) 2006-2016 LOGILAB S.A. (Paris, FRANCE) +# Copyright (c) 2010 Maarten ter Huurne +# Copyright (c) 2012-2014 Google, Inc. +# Copyright (c) 2012 FELD Boris +# Copyright (c) 2013-2020 Claudiu Popa +# Copyright (c) 2014 Michal Nowikowski +# Copyright (c) 2014 Brett Cannon +# Copyright (c) 2014 Arun Persaud +# Copyright (c) 2014 David Pursehouse +# Copyright (c) 2015 Dmitry Pribysh +# Copyright (c) 2015 Ionel Cristian Maries +# Copyright (c) 2016-2017 Łukasz Rogalski +# Copyright (c) 2016 Alexander Todorov +# Copyright (c) 2016 Anthony Foglia +# Copyright (c) 2016 Florian Bruhin +# Copyright (c) 2016 Moises Lopez +# Copyright (c) 2016 Jakub Wilk +# Copyright (c) 2017, 2019-2020 hippo91 +# Copyright (c) 2018, 2021 Ville Skyttä +# Copyright (c) 2018, 2020 Anthony Sottile +# Copyright (c) 2018-2019 Nick Drozd +# Copyright (c) 2018-2019 Ashley Whetter +# Copyright (c) 2018 Lucas Cimon +# Copyright (c) 2018 Bryce Guinta +# Copyright (c) 2018 ssolanki +# Copyright (c) 2018 Ben Green +# Copyright (c) 2019-2021 Pierre Sassoulas +# Copyright (c) 2019 mattlbeck <17108752+mattlbeck@users.noreply.github.com> +# Copyright (c) 2019-2020 craig-sh +# Copyright (c) 2019 Janne Rönkkö +# Copyright (c) 2019 Hugo van Kemenade +# Copyright (c) 2019 Grygorii Iermolenko +# Copyright (c) 2019 Andrzej Klajnert +# Copyright (c) 2019 Pascal Corpet +# Copyright (c) 2020 GergelyKalmar +# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> +# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> +# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> +# Copyright (c) 2021 Samuel Freilich +# Copyright (c) 2021 Nick Pesce +# Copyright (c) 2021 bot +# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> +# Copyright (c) 2021 SupImDos <62866982+SupImDos@users.noreply.github.com> +# Copyright (c) 2021 Kayran Schmidt <59456929+yumasheta@users.noreply.github.com> +# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> +# Copyright (c) 2021 James Sinclair +# Copyright (c) 2021 tiagohonorato <61059243+tiagohonorato@users.noreply.github.com> + +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +"""Classes checker for Python code""" +import collections +from itertools import chain, zip_longest +from typing import List, Pattern + +import astroid +from astroid import nodes + +from pylint.checkers import BaseChecker, utils +from pylint.checkers.utils import ( + PYMETHODS, + check_messages, + class_is_abstract, + decorated_with, + decorated_with_property, + get_outer_class, + has_known_bases, + is_attr_private, + is_attr_protected, + is_builtin_object, + is_comprehension, + is_iterable, + is_overload_stub, + is_property_setter, + is_property_setter_or_deleter, + is_protocol_class, + node_frame_class, + overrides_a_method, + safe_infer, + unimplemented_abstract_methods, + uninferable_final_decorators, +) +from pylint.interfaces import IAstroidChecker +from pylint.utils import get_global_option + +INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} +BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"} +ASTROID_TYPE_COMPARATORS = { + nodes.Const: lambda a, b: a.value == b.value, + nodes.ClassDef: lambda a, b: a.qname == b.qname, + nodes.Tuple: lambda a, b: a.elts == b.elts, + nodes.List: lambda a, b: a.elts == b.elts, + nodes.Dict: lambda a, b: a.items == b.items, + nodes.Name: lambda a, b: set(a.infer()) == set(b.infer()), +} + +# Dealing with useless override detection, with regard +# to parameters vs arguments + +_CallSignature = collections.namedtuple( + "_CallSignature", "args kws starred_args starred_kws" +) +_ParameterSignature = collections.namedtuple( + "_ParameterSignature", "args kwonlyargs varargs kwargs" +) + + +def _signature_from_call(call): + kws = {} + args = [] + starred_kws = [] + starred_args = [] + for keyword in call.keywords or []: + arg, value = keyword.arg, keyword.value + if arg is None and isinstance(value, nodes.Name): + # Starred node and we are interested only in names, + # otherwise some transformation might occur for the parameter. + starred_kws.append(value.name) + elif isinstance(value, nodes.Name): + kws[arg] = value.name + else: + kws[arg] = None + + for arg in call.args: + if isinstance(arg, nodes.Starred) and isinstance(arg.value, nodes.Name): + # Positional variadic and a name, otherwise some transformation + # might have occurred. + starred_args.append(arg.value.name) + elif isinstance(arg, nodes.Name): + args.append(arg.name) + else: + args.append(None) + + return _CallSignature(args, kws, starred_args, starred_kws) + + +def _signature_from_arguments(arguments): + kwarg = arguments.kwarg + vararg = arguments.vararg + args = [ + arg.name + for arg in chain(arguments.posonlyargs, arguments.args) + if arg.name != "self" + ] + kwonlyargs = [arg.name for arg in arguments.kwonlyargs] + return _ParameterSignature(args, kwonlyargs, vararg, kwarg) + + +def _definition_equivalent_to_call(definition, call): + """Check if a definition signature is equivalent to a call.""" + if definition.kwargs: + if definition.kwargs not in call.starred_kws: + return False + elif call.starred_kws: + return False + if definition.varargs: + if definition.varargs not in call.starred_args: + return False + elif call.starred_args: + return False + if any(kw not in call.kws for kw in definition.kwonlyargs): + return False + if definition.args != call.args: + return False + + # No extra kwargs in call. + return all(kw in call.args or kw in definition.kwonlyargs for kw in call.kws) + + +# Deal with parameters overriding in two methods. + + +def _positional_parameters(method): + positional = method.args.args + if method.is_bound() and method.type in {"classmethod", "method"}: + positional = positional[1:] + return positional + + +class _DefaultMissing: + """Sentinel value for missing arg default, use _DEFAULT_MISSING.""" + + +_DEFAULT_MISSING = _DefaultMissing() + + +def _has_different_parameters_default_value(original, overridden): + """ + Check if original and overridden methods arguments have different default values + + Return True if one of the overridden arguments has a default + value different from the default value of the original argument + If one of the method doesn't have argument (.args is None) + return False + """ + if original.args is None or overridden.args is None: + return False + + for param in chain(original.args, original.kwonlyargs): + try: + original_default = original.default_value(param.name) + except astroid.exceptions.NoDefault: + original_default = _DEFAULT_MISSING + try: + overridden_default = overridden.default_value(param.name) + if original_default is _DEFAULT_MISSING: + # Only the original has a default. + return True + except astroid.exceptions.NoDefault: + if original_default is _DEFAULT_MISSING: + # Both have a default, no difference + continue + # Only the override has a default. + return True + + original_type = type(original_default) + if not isinstance(overridden_default, original_type): + # Two args with same name but different types + return True + is_same_fn = ASTROID_TYPE_COMPARATORS.get(original_type) + if is_same_fn is None: + # If the default value comparison is unhandled, assume the value is different + return True + if not is_same_fn(original_default, overridden_default): + # Two args with same type but different values + return True + return False + + +def _has_different_parameters( + original: List[nodes.AssignName], + overridden: List[nodes.AssignName], + dummy_parameter_regex: Pattern, +) -> List[str]: + result = [] + zipped = zip_longest(original, overridden) + for original_param, overridden_param in zipped: + params = (original_param, overridden_param) + if not all(params): + return ["Number of parameters "] + + # 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 " + f"to '{overridden_param.name}' in" + ) + + return result + + +def _different_parameters( + original: nodes.FunctionDef, + overridden: nodes.FunctionDef, + dummy_parameter_regex: Pattern, +) -> List[str]: + """Determine if the two methods have different parameters + + They are considered to have different parameters if: + + * they have different positional parameters, including different names + + * one of the methods is having variadics, while the other is not + + * they have different keyword only parameters. + + """ + output_messages = [] + original_parameters = _positional_parameters(original) + overridden_parameters = _positional_parameters(overridden) + + # Copy kwonlyargs list so that we don't affect later function linting + original_kwonlyargs = original.args.kwonlyargs + + # Allow positional/keyword variadic in overridden to match against any + # positional/keyword argument in original. + # Keep any arguments that are found separately in overridden to satisfy + # later tests + if overridden.args.vararg: + overridden_names = [v.name for v in overridden_parameters] + original_parameters = [ + v for v in original_parameters if v.name in overridden_names + ] + + if overridden.args.kwarg: + overridden_names = [v.name for v in overridden.args.kwonlyargs] + original_kwonlyargs = [ + v for v in original.args.kwonlyargs if v.name in overridden_names + ] + + different_positional = _has_different_parameters( + original_parameters, overridden_parameters, dummy_parameter_regex + ) + different_kwonly = _has_different_parameters( + 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]: + output_messages.append("Number of parameters ") + output_messages += different_positional[1:] + output_messages += different_kwonly[1:] + else: + output_messages += different_positional + output_messages += different_kwonly + else: + if different_positional: + output_messages += different_positional + if different_kwonly: + output_messages += different_kwonly + + if original.name in PYMETHODS: + # Ignore the difference for special methods. If the parameter + # numbers are different, then that is going to be caught by + # unexpected-special-method-signature. + # If the names are different, it doesn't matter, since they can't + # be used as keyword arguments anyway. + output_messages.clear() + + # Arguments will only violate LSP if there are variadics in the original + # that are then removed from the overridden + kwarg_lost = original.args.kwarg and not overridden.args.kwarg + vararg_lost = original.args.vararg and not overridden.args.vararg + + if kwarg_lost or vararg_lost: + output_messages += ["Variadics removed in"] + + return output_messages + + +def _is_invalid_base_class(cls): + return cls.name in INVALID_BASE_CLASSES and is_builtin_object(cls) + + +def _has_data_descriptor(cls, attr): + attributes = cls.getattr(attr) + for attribute in attributes: + try: + for inferred in attribute.infer(): + if isinstance(inferred, astroid.Instance): + try: + inferred.getattr("__get__") + inferred.getattr("__set__") + except astroid.NotFoundError: + continue + else: + return True + except astroid.InferenceError: + # Can't infer, avoid emitting a false positive in this case. + return True + return False + + +def _called_in_methods(func, klass, methods): + """Check if the func was called in any of the given methods, + belonging to the *klass*. Returns True if so, False otherwise. + """ + if not isinstance(func, nodes.FunctionDef): + return False + for method in methods: + try: + inferred = klass.getattr(method) + except astroid.NotFoundError: + continue + for infer_method in inferred: + for call in infer_method.nodes_of_class(nodes.Call): + try: + bound = next(call.func.infer()) + except (astroid.InferenceError, StopIteration): + continue + if not isinstance(bound, astroid.BoundMethod): + continue + func_obj = bound._proxied + if isinstance(func_obj, astroid.UnboundMethod): + func_obj = func_obj._proxied + if func_obj.name == func.name: + return True + return False + + +def _is_attribute_property(name, klass): + """Check if the given attribute *name* is a property in the given *klass*. + + It will look for `property` calls or for functions + with the given name, decorated by `property` or `property` + subclasses. + Returns ``True`` if the name is a property in the given klass, + ``False`` otherwise. + """ + + try: + attributes = klass.getattr(name) + except astroid.NotFoundError: + return False + property_name = "builtins.property" + for attr in attributes: + if attr is astroid.Uninferable: + continue + try: + inferred = next(attr.infer()) + except astroid.InferenceError: + continue + if isinstance(inferred, nodes.FunctionDef) and decorated_with_property( + inferred + ): + return True + if inferred.pytype() != property_name: + continue + + cls = node_frame_class(inferred) + if cls == klass.declared_metaclass(): + continue + return True + return False + + +def _has_bare_super_call(fundef_node): + for call in fundef_node.nodes_of_class(nodes.Call): + func = call.func + if isinstance(func, nodes.Name) and func.name == "super" and not call.args: + return True + return False + + +def _has_same_layout_slots(slots, assigned_value): + inferred = next(assigned_value.infer()) + if isinstance(inferred, nodes.ClassDef): + other_slots = inferred.slots() + if all( + first_slot and second_slot and first_slot.value == second_slot.value + for (first_slot, second_slot) in zip_longest(slots, other_slots) + ): + return True + return False + + +MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass + "F0202": ( + "Unable to check methods signature (%s / %s)", + "method-check-failed", + "Used when Pylint has been unable to check methods signature " + "compatibility for an unexpected reason. Please report this kind " + "if you don't make sense of it.", + ), + "E0202": ( + "An attribute defined in %s line %s hides this method", + "method-hidden", + "Used when a class defines a method which is hidden by an " + "instance attribute from an ancestor class or set by some " + "client code.", + ), + "E0203": ( + "Access to member %r before its definition line %s", + "access-member-before-definition", + "Used when an instance member is accessed before it's actually assigned.", + ), + "W0201": ( + "Attribute %r defined outside __init__", + "attribute-defined-outside-init", + "Used when an instance attribute is defined outside the __init__ method.", + ), + "W0212": ( + "Access to a protected member %s of a client class", # E0214 + "protected-access", + "Used when a protected member (i.e. class member with a name " + "beginning with an underscore) is access outside the class or a " + "descendant of the class where it's defined.", + ), + "E0211": ( + "Method has no argument", + "no-method-argument", + "Used when a method which should have the bound instance as " + "first argument has no argument defined.", + ), + "E0213": ( + 'Method should have "self" as first argument', + "no-self-argument", + 'Used when a method has an attribute different the "self" as ' + "first argument. This is considered as an error since this is " + "a so common convention that you shouldn't break it!", + ), + "C0202": ( + "Class method %s should have %s as first argument", + "bad-classmethod-argument", + "Used when a class method has a first argument named differently " + "than the value specified in valid-classmethod-first-arg option " + '(default to "cls"), recommended to easily differentiate them ' + "from regular instance methods.", + ), + "C0203": ( + "Metaclass method %s should have %s as first argument", + "bad-mcs-method-argument", + "Used when a metaclass method has a first argument named " + "differently than the value specified in valid-classmethod-first" + '-arg option (default to "cls"), recommended to easily ' + "differentiate them from regular instance methods.", + ), + "C0204": ( + "Metaclass class method %s should have %s as first argument", + "bad-mcs-classmethod-argument", + "Used when a metaclass class method has a first argument named " + "differently than the value specified in valid-metaclass-" + 'classmethod-first-arg option (default to "mcs"), recommended to ' + "easily differentiate them from regular instance methods.", + ), + "W0211": ( + "Static method with %r as first argument", + "bad-staticmethod-argument", + 'Used when a static method has "self" or a value specified in ' + "valid-classmethod-first-arg option or " + "valid-metaclass-classmethod-first-arg option as first argument.", + ), + "R0201": ( + "Method could be a function", + "no-self-use", + "Used when a method doesn't use its bound instance, and so could " + "be written as a function.", + ), + "W0221": ( + "%s %s %r method", + "arguments-differ", + "Used when a method has a different number of arguments than in " + "the implemented interface or in an overridden method.", + ), + "W0222": ( + "Signature differs from %s %r method", + "signature-differs", + "Used when a method signature is different than in the " + "implemented interface or in an overridden method.", + ), + "W0223": ( + "Method %r is abstract in class %r but is not overridden", + "abstract-method", + "Used when an abstract method (i.e. raise NotImplementedError) is " + "not overridden in concrete class.", + ), + "W0231": ( + "__init__ method from base class %r is not called", + "super-init-not-called", + "Used when an ancestor class method has an __init__ method " + "which is not called by a derived class.", + ), + "W0232": ( + "Class has no __init__ method", + "no-init", + "Used when a class has no __init__ method, neither its parent classes.", + ), + "W0233": ( + "__init__ method from a non direct base class %r is called", + "non-parent-init-called", + "Used when an __init__ method is called on a class which is not " + "in the direct ancestors for the analysed class.", + ), + "W0235": ( + "Useless super delegation in method %r", + "useless-super-delegation", + "Used whenever we can detect that an overridden method is useless, " + "relying on super() delegation to do the same thing as another method " + "from the MRO.", + ), + "W0236": ( + "Method %r was expected to be %r, found it instead as %r", + "invalid-overridden-method", + "Used when we detect that a method was overridden in a way " + "that does not match its base class " + "which could result in potential bugs at runtime.", + ), + "W0237": ( + "%s %s %r method", + "arguments-renamed", + "Used when a method parameter has a different name than in " + "the implemented interface or in an overridden method.", + ), + "W0238": ( + "Unused private member `%s.%s`", + "unused-private-member", + "Emitted when a private member of a class is defined but not used.", + ), + "W0239": ( + "Method %r overrides a method decorated with typing.final which is defined in class %r", + "overridden-final-method", + "Used when a method decorated with typing.final has been overridden.", + ), + "W0240": ( + "Class %r is a subclass of a class decorated with typing.final: %r", + "subclassed-final-class", + "Used when a class decorated with typing.final has been subclassed.", + ), + "E0236": ( + "Invalid object %r in __slots__, must contain only non empty strings", + "invalid-slots-object", + "Used when an invalid (non-string) object occurs in __slots__.", + ), + "E0237": ( + "Assigning to attribute %r not defined in class slots", + "assigning-non-slot", + "Used when assigning to an attribute not defined in the class slots.", + ), + "E0238": ( + "Invalid __slots__ object", + "invalid-slots", + "Used when an invalid __slots__ is found in class. " + "Only a string, an iterable or a sequence is permitted.", + ), + "E0239": ( + "Inheriting %r, which is not a class.", + "inherit-non-class", + "Used when a class inherits from something which is not a class.", + ), + "E0240": ( + "Inconsistent method resolution order for class %r", + "inconsistent-mro", + "Used when a class has an inconsistent method resolution order.", + ), + "E0241": ( + "Duplicate bases for class %r", + "duplicate-bases", + "Used when a class has duplicate bases.", + ), + "E0242": ( + "Value %r in slots conflicts with class variable", + "class-variable-slots-conflict", + "Used when a value in __slots__ conflicts with a class variable, property or method.", + ), + "E0243": ( + "Invalid __class__ object", + "invalid-class-object", + "Used when an invalid object is assigned to a __class__ property. " + "Only a class is permitted.", + ), + "R0202": ( + "Consider using a decorator instead of calling classmethod", + "no-classmethod-decorator", + "Used when a class method is defined without using the decorator syntax.", + ), + "R0203": ( + "Consider using a decorator instead of calling staticmethod", + "no-staticmethod-decorator", + "Used when a static method is defined without using the decorator syntax.", + ), + "C0205": ( + "Class __slots__ should be a non-string iterable", + "single-string-used-for-slots", + "Used when a class __slots__ is a simple string, rather than an iterable.", + ), + "R0205": ( + "Class %r inherits from object, can be safely removed from bases in python3", + "useless-object-inheritance", + "Used when a class inherit from object, which under python3 is implicit, " + "hence can be safely removed from bases.", + ), + "R0206": ( + "Cannot have defined parameters for properties", + "property-with-parameters", + "Used when we detect that a property also has parameters, which are useless, " + "given that properties cannot be called with additional arguments.", + ), +} + + +def _scope_default(): + return collections.defaultdict(list) + + +class ScopeAccessMap: + """Store the accessed variables per scope.""" + + def __init__(self): + self._scopes = collections.defaultdict(_scope_default) + + def set_accessed(self, node): + """Set the given node as accessed.""" + + frame = node_frame_class(node) + if frame is None: + # The node does not live in a class. + return + self._scopes[frame][node.attrname].append(node) + + def accessed(self, scope): + """Get the accessed variables for the given scope.""" + return self._scopes.get(scope, {}) + + +class ClassChecker(BaseChecker): + """checks for : + * methods without self as first argument + * overridden methods signature + * access only to existent members via self + * attributes not defined in the __init__ method + * unreachable code + """ + + __implements__ = (IAstroidChecker,) + + # configuration section name + name = "classes" + # messages + msgs = MSGS + priority = -2 + # configuration options + options = ( + ( + "defining-attr-methods", + { + "default": ("__init__", "__new__", "setUp", "__post_init__"), + "type": "csv", + "metavar": "", + "help": "List of method names used to declare (i.e. assign) \ +instance attributes.", + }, + ), + ( + "valid-classmethod-first-arg", + { + "default": ("cls",), + "type": "csv", + "metavar": "", + "help": "List of valid names for the first argument in \ +a class method.", + }, + ), + ( + "valid-metaclass-classmethod-first-arg", + { + "default": ("cls",), + "type": "csv", + "metavar": "", + "help": "List of valid names for the first argument in \ +a metaclass class method.", + }, + ), + ( + "exclude-protected", + { + "default": ( + # namedtuple public API. + "_asdict", + "_fields", + "_replace", + "_source", + "_make", + ), + "type": "csv", + "metavar": "", + "help": ( + "List of member names, which should be excluded " + "from the protected access warning." + ), + }, + ), + ( + "check-protected-access-in-special-methods", + { + "default": False, + "type": "yn", + "metavar": "", + "help": "Warn about protected attribute access inside special methods", + }, + ), + ) + + def __init__(self, linter=None): + super().__init__(linter) + self._accessed = ScopeAccessMap() + self._first_attrs = [] + self._meth_could_be_func = None + + def open(self) -> None: + self._mixin_class_rgx = get_global_option(self, "mixin-class-rgx") + py_version = get_global_option(self, "py-version") + self._py38_plus = py_version >= (3, 8) + + @astroid.decorators.cachedproperty + def _dummy_rgx(self): + return get_global_option(self, "dummy-variables-rgx", default=None) + + @astroid.decorators.cachedproperty + def _ignore_mixin(self): + return get_global_option(self, "ignore-mixin-members", default=True) + + @check_messages( + "abstract-method", + "no-init", + "invalid-slots", + "single-string-used-for-slots", + "invalid-slots-object", + "class-variable-slots-conflict", + "inherit-non-class", + "useless-object-inheritance", + "inconsistent-mro", + "duplicate-bases", + ) + def visit_classdef(self, node: nodes.ClassDef) -> None: + """init visit variable _accessed""" + self._check_bases_classes(node) + # if not an exception or a metaclass + if node.type == "class" and has_known_bases(node): + try: + node.local_attr("__init__") + except astroid.NotFoundError: + self.add_message("no-init", args=node, node=node) + self._check_slots(node) + self._check_proper_bases(node) + self._check_typing_final(node) + self._check_consistent_mro(node) + + def _check_consistent_mro(self, node): + """Detect that a class has a consistent mro or duplicate bases.""" + try: + node.mro() + except astroid.InconsistentMroError: + self.add_message("inconsistent-mro", args=node.name, node=node) + except astroid.DuplicateBasesError: + self.add_message("duplicate-bases", args=node.name, node=node) + except NotImplementedError: + # Old style class, there's no mro so don't do anything. + pass + + def _check_proper_bases(self, node): + """ + Detect that a class inherits something which is not + a class or a type. + """ + for base in node.bases: + ancestor = safe_infer(base) + if not ancestor: + continue + if isinstance(ancestor, astroid.Instance) and ancestor.is_subtype_of( + "builtins.type" + ): + continue + + if not isinstance(ancestor, nodes.ClassDef) or _is_invalid_base_class( + ancestor + ): + self.add_message("inherit-non-class", args=base.as_string(), node=node) + + if ancestor.name == object.__name__: + self.add_message( + "useless-object-inheritance", args=node.name, node=node + ) + + def _check_typing_final(self, node: nodes.ClassDef) -> None: + """Detect that a class does not subclass a class decorated with `typing.final`""" + if not self._py38_plus: + return + for base in node.bases: + ancestor = safe_infer(base) + if not ancestor: + continue + + if isinstance(ancestor, nodes.ClassDef) and ( + decorated_with(ancestor, ["typing.final"]) + or uninferable_final_decorators(ancestor.decorators) + ): + self.add_message( + "subclassed-final-class", + args=(node.name, ancestor.name), + node=node, + ) + + @check_messages("unused-private-member", "attribute-defined-outside-init") + def leave_classdef(self, node: nodes.ClassDef) -> None: + """close a class node: + check that instance attributes are defined in __init__ and check + access to existent members + """ + self._check_unused_private_functions(node) + self._check_unused_private_variables(node) + self._check_unused_private_attributes(node) + self._check_attribute_defined_outside_init(node) + + def _check_unused_private_functions(self, node: nodes.ClassDef) -> None: + for function_def in node.nodes_of_class(nodes.FunctionDef): + if not is_attr_private(function_def.name): + continue + parent_scope = function_def.parent.scope() + if isinstance(parent_scope, nodes.FunctionDef): + # Handle nested functions + if function_def.name in ( + n.name for n in parent_scope.nodes_of_class(nodes.Name) + ): + continue + for attribute in node.nodes_of_class(nodes.Attribute): + if ( + attribute.attrname != function_def.name + or attribute.scope() == function_def # We ignore recursive calls + ): + continue + if isinstance(attribute.expr, nodes.Name) and attribute.expr.name in ( + "self", + "cls", + node.name, + ): + # self.__attrname + # cls.__attrname + # node_name.__attrname + break + if isinstance(attribute.expr, nodes.Call): + # type(self).__attrname + inferred = safe_infer(attribute.expr) + if ( + isinstance(inferred, nodes.ClassDef) + and inferred.name == node.name + ): + break + else: + name_stack = [] + curr = parent_scope + # Generate proper names for nested functions + while curr != node: + name_stack.append(curr.name) + curr = curr.parent.scope() + + outer_level_names = f"{'.'.join(reversed(name_stack))}" + function_repr = f"{outer_level_names}.{function_def.name}({function_def.args.as_string()})" + self.add_message( + "unused-private-member", + node=function_def, + args=(node.name, function_repr.lstrip(".")), + ) + + def _check_unused_private_variables(self, node: nodes.ClassDef) -> None: + """Check if private variables are never used within a class""" + for assign_name in node.nodes_of_class(nodes.AssignName): + if isinstance(assign_name.parent, nodes.Arguments): + continue # Ignore function arguments + if not is_attr_private(assign_name.name): + continue + for child in node.nodes_of_class((nodes.Name, nodes.Attribute)): + if isinstance(child, nodes.Name) and child.name == assign_name.name: + break + if isinstance(child, nodes.Attribute): + if not isinstance(child.expr, nodes.Name): + break + if child.attrname == assign_name.name and child.expr.name in ( + "self", + "cls", + node.name, + ): + break + else: + args = (node.name, assign_name.name) + self.add_message("unused-private-member", node=assign_name, args=args) + + def _check_unused_private_attributes(self, node: nodes.ClassDef) -> None: + for assign_attr in node.nodes_of_class(nodes.AssignAttr): + if not is_attr_private(assign_attr.attrname) or not isinstance( + assign_attr.expr, nodes.Name + ): + continue + + # Logic for checking false positive when using __new__, + # Get the returned object names of the __new__ magic function + # Then check if the attribute was consumed in other instance methods + acceptable_obj_names: List[str] = ["self"] + scope = assign_attr.scope() + if isinstance(scope, nodes.FunctionDef) and scope.name == "__new__": + acceptable_obj_names.extend( + [ + return_node.value.name + for return_node in scope.nodes_of_class(nodes.Return) + if isinstance(return_node.value, nodes.Name) + ] + ) + + for attribute in node.nodes_of_class(nodes.Attribute): + if attribute.attrname != assign_attr.attrname: + continue + + if ( + assign_attr.expr.name + in { + "cls", + node.name, + } + and attribute.expr.name in {"cls", "self", node.name} + ): + # If assigned to cls or class name, can be accessed by cls/self/class name + break + + if ( + assign_attr.expr.name in acceptable_obj_names + and attribute.expr.name == "self" + ): + # If assigned to self.attrib, can only be accessed by self + # Or if __new__ was used, the returned object names are acceptable + break + + if assign_attr.expr.name == attribute.expr.name == node.name: + # Recognise attributes which are accessed via the class name + break + + else: + args = (node.name, assign_attr.attrname) + self.add_message("unused-private-member", node=assign_attr, args=args) + + def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: + # check access to existent members on non metaclass classes + if self._ignore_mixin and self._mixin_class_rgx.match(cnode.name): + # We are in a mixin class. No need to try to figure out if + # something is missing, since it is most likely that it will + # miss. + return + + accessed = self._accessed.accessed(cnode) + if cnode.type != "metaclass": + self._check_accessed_members(cnode, accessed) + # checks attributes are defined in an allowed method such as __init__ + if not self.linter.is_message_enabled("attribute-defined-outside-init"): + return + defining_methods = self.config.defining_attr_methods + current_module = cnode.root() + for attr, nodes_lst in cnode.instance_attrs.items(): + # Exclude `__dict__` as it is already defined. + if attr == "__dict__": + continue + + # Skip nodes which are not in the current module and it may screw up + # the output, while it's not worth it + nodes_lst = [ + n + for n in nodes_lst + if not isinstance( + n.statement(future=True), (nodes.Delete, nodes.AugAssign) + ) + and n.root() is current_module + ] + if not nodes_lst: + continue # error detected by typechecking + + # Check if any method attr is defined in is a defining method + # or if we have the attribute defined in a setter. + frames = (node.frame() for node in nodes_lst) + if any( + frame.name in defining_methods or is_property_setter(frame) + for frame in frames + ): + continue + + # check attribute is defined in a parent's __init__ + for parent in cnode.instance_attr_ancestors(attr): + attr_defined = False + # check if any parent method attr is defined in is a defining method + for node in parent.instance_attrs[attr]: + if node.frame().name in defining_methods: + attr_defined = True + if attr_defined: + # we're done :) + break + else: + # check attribute is defined as a class attribute + try: + cnode.local_attr(attr) + except astroid.NotFoundError: + for node in nodes_lst: + if node.frame().name not in defining_methods: + # If the attribute was set by a call in any + # of the defining methods, then don't emit + # the warning. + if _called_in_methods( + node.frame(), cnode, defining_methods + ): + continue + self.add_message( + "attribute-defined-outside-init", args=attr, node=node + ) + + def visit_functiondef(self, node: nodes.FunctionDef) -> None: + """check method arguments, overriding""" + # ignore actual functions + if not node.is_method(): + return + + self._check_useless_super_delegation(node) + self._check_property_with_parameters(node) + + klass = node.parent.frame() + self._meth_could_be_func = True + # check first argument is self if this is actually a method + self._check_first_arg_for_type(node, klass.type == "metaclass") + if node.name == "__init__": + self._check_init(node) + return + # check signature if the method overloads inherited method + for overridden in klass.local_attr_ancestors(node.name): + # get astroid for the searched method + try: + parent_function = overridden[node.name] + except KeyError: + # we have found the method but it's not in the local + # dictionary. + # This may happen with astroid build from living objects + continue + if not isinstance(parent_function, nodes.FunctionDef): + continue + self._check_signature(node, parent_function, "overridden", klass) + self._check_invalid_overridden_method(node, parent_function) + break + + if node.decorators: + for decorator in node.decorators.nodes: + if isinstance(decorator, nodes.Attribute) and decorator.attrname in { + "getter", + "setter", + "deleter", + }: + # attribute affectation will call this method, not hiding it + return + if isinstance(decorator, nodes.Name): + if decorator.name == "property": + # attribute affectation will either call a setter or raise + # an attribute error, anyway not hiding the function + return + + # Infer the decorator and see if it returns something useful + inferred = safe_infer(decorator) + if not inferred: + return + if isinstance(inferred, nodes.FunctionDef): + # Okay, it's a decorator, let's see what it can infer. + try: + inferred = next(inferred.infer_call_result(inferred)) + except astroid.InferenceError: + return + try: + if ( + isinstance(inferred, (astroid.Instance, nodes.ClassDef)) + and inferred.getattr("__get__") + and inferred.getattr("__set__") + ): + return + except astroid.AttributeInferenceError: + pass + + # check if the method is hidden by an attribute + try: + overridden = klass.instance_attr(node.name)[0] + overridden_frame = overridden.frame() + if ( + isinstance(overridden_frame, nodes.FunctionDef) + and overridden_frame.type == "method" + ): + overridden_frame = overridden_frame.parent.frame() + if not ( + isinstance(overridden_frame, nodes.ClassDef) + and klass.is_subtype_of(overridden_frame.qname()) + ): + return + + # If a subclass defined the method then it's not our fault. + for ancestor in klass.ancestors(): + if node.name in ancestor.instance_attrs and is_attr_private(node.name): + return + for obj in ancestor.lookup(node.name)[1]: + if isinstance(obj, nodes.FunctionDef): + return + args = (overridden.root().name, overridden.fromlineno) + self.add_message("method-hidden", args=args, node=node) + except astroid.NotFoundError: + pass + + visit_asyncfunctiondef = visit_functiondef + + def _check_useless_super_delegation(self, function): + """Check if the given function node is an useless method override + + We consider it *useless* if it uses the super() builtin, but having + nothing additional whatsoever than not implementing the method at all. + If the method uses super() to delegate an operation to the rest of the MRO, + and if the method called is the same as the current one, the arguments + passed to super() are the same as the parameters that were passed to + this method, then the method could be removed altogether, by letting + other implementation to take precedence. + """ + + if ( + not function.is_method() + # With decorators is a change of use + or function.decorators + ): + return + + body = function.body + if len(body) != 1: + # Multiple statements, which means this overridden method + # could do multiple things we are not aware of. + return + + statement = body[0] + if not isinstance(statement, (nodes.Expr, nodes.Return)): + # Doing something else than what we are interested into. + return + + call = statement.value + if ( + not isinstance(call, nodes.Call) + # Not a super() attribute access. + or not isinstance(call.func, nodes.Attribute) + ): + return + + # Should be a super call. + try: + super_call = next(call.func.expr.infer()) + except astroid.InferenceError: + return + else: + if not isinstance(super_call, astroid.objects.Super): + return + + # The name should be the same. + if call.func.attrname != function.name: + return + + # Should be a super call with the MRO pointer being the + # current class and the type being the current instance. + current_scope = function.parent.scope() + if ( + super_call.mro_pointer != current_scope + or not isinstance(super_call.type, astroid.Instance) + or super_call.type.name != current_scope.name + ): + return + + # Check values of default args + klass = function.parent.frame() + meth_node = None + for overridden in klass.local_attr_ancestors(function.name): + # get astroid for the searched method + try: + meth_node = overridden[function.name] + except KeyError: + # we have found the method but it's not in the local + # dictionary. + # This may happen with astroid build from living objects + continue + if ( + not isinstance(meth_node, nodes.FunctionDef) + # If the method have an ancestor which is not a + # function then it is legitimate to redefine it + or _has_different_parameters_default_value( + meth_node.args, function.args + ) + ): + return + break + + # Detect if the parameters are the same as the call's arguments. + params = _signature_from_arguments(function.args) + args = _signature_from_call(call) + + if meth_node is not None: + + def form_annotations(arguments): + annotations = chain( + (arguments.posonlyargs_annotations or []), arguments.annotations + ) + return [ann.as_string() for ann in annotations if ann is not None] + + called_annotations = form_annotations(function.args) + overridden_annotations = form_annotations(meth_node.args) + if called_annotations and overridden_annotations: + if called_annotations != overridden_annotations: + return + + if _definition_equivalent_to_call(params, args): + self.add_message( + "useless-super-delegation", node=function, args=(function.name,) + ) + + def _check_property_with_parameters(self, node): + if ( + node.args.args + and len(node.args.args) > 1 + and decorated_with_property(node) + and not is_property_setter(node) + ): + self.add_message("property-with-parameters", node=node) + + def _check_invalid_overridden_method(self, function_node, parent_function_node): + parent_is_property = decorated_with_property( + parent_function_node + ) or is_property_setter_or_deleter(parent_function_node) + current_is_property = decorated_with_property( + function_node + ) or is_property_setter_or_deleter(function_node) + if parent_is_property and not current_is_property: + self.add_message( + "invalid-overridden-method", + args=(function_node.name, "property", function_node.type), + node=function_node, + ) + elif not parent_is_property and current_is_property: + self.add_message( + "invalid-overridden-method", + args=(function_node.name, "method", "property"), + node=function_node, + ) + + parent_is_async = isinstance(parent_function_node, nodes.AsyncFunctionDef) + current_is_async = isinstance(function_node, nodes.AsyncFunctionDef) + + if parent_is_async and not current_is_async: + self.add_message( + "invalid-overridden-method", + args=(function_node.name, "async", "non-async"), + node=function_node, + ) + + elif not parent_is_async and current_is_async: + self.add_message( + "invalid-overridden-method", + args=(function_node.name, "non-async", "async"), + node=function_node, + ) + if ( + decorated_with(parent_function_node, ["typing.final"]) + or uninferable_final_decorators(parent_function_node.decorators) + ) and self._py38_plus: + self.add_message( + "overridden-final-method", + args=(function_node.name, parent_function_node.parent.name), + node=function_node, + ) + + def _check_slots(self, node): + if "__slots__" not in node.locals: + return + for slots in node.igetattr("__slots__"): + # check if __slots__ is a valid type + if slots is astroid.Uninferable: + continue + if not is_iterable(slots) and not is_comprehension(slots): + self.add_message("invalid-slots", node=node) + continue + + if isinstance(slots, nodes.Const): + # a string, ignore the following checks + self.add_message("single-string-used-for-slots", node=node) + continue + if not hasattr(slots, "itered"): + # we can't obtain the values, maybe a .deque? + continue + + if isinstance(slots, nodes.Dict): + values = [item[0] for item in slots.items] + else: + values = slots.itered() + if values is astroid.Uninferable: + return + for elt in values: + try: + self._check_slots_elt(elt, node) + except astroid.InferenceError: + continue + + def _check_slots_elt(self, elt, node): + for inferred in elt.infer(): + if inferred is astroid.Uninferable: + continue + if not isinstance(inferred, nodes.Const) or not isinstance( + inferred.value, str + ): + self.add_message( + "invalid-slots-object", args=inferred.as_string(), node=elt + ) + continue + if not inferred.value: + self.add_message( + "invalid-slots-object", args=inferred.as_string(), node=elt + ) + + # Check if we have a conflict with a class variable. + class_variable = node.locals.get(inferred.value) + if class_variable: + # Skip annotated assignments which don't conflict at all with slots. + if len(class_variable) == 1: + parent = class_variable[0].parent + if isinstance(parent, nodes.AnnAssign) and parent.value is None: + return + self.add_message( + "class-variable-slots-conflict", args=(inferred.value,), node=elt + ) + + def leave_functiondef(self, node: nodes.FunctionDef) -> None: + """on method node, check if this method couldn't be a function + + ignore class, static and abstract methods, initializer, + methods overridden from a parent class. + """ + if node.is_method(): + if node.args.args is not None: + self._first_attrs.pop() + if not self.linter.is_message_enabled("no-self-use"): + return + class_node = node.parent.frame() + if ( + self._meth_could_be_func + and node.type == "method" + and node.name not in PYMETHODS + and not ( + node.is_abstract() + or overrides_a_method(class_node, node.name) + or decorated_with_property(node) + or _has_bare_super_call(node) + or is_protocol_class(class_node) + or is_overload_stub(node) + ) + ): + self.add_message("no-self-use", node=node) + + leave_asyncfunctiondef = leave_functiondef + + def visit_attribute(self, node: nodes.Attribute) -> None: + """check if the getattr is an access to a class member + if so, register it. Also check for access to protected + class member from outside its class (but ignore __special__ + methods) + """ + # Check self + if self._uses_mandatory_method_param(node): + self._accessed.set_accessed(node) + return + if not self.linter.is_message_enabled("protected-access"): + return + + self._check_protected_attribute_access(node) + + @check_messages("assigning-non-slot", "invalid-class-object") + def visit_assignattr(self, node: nodes.AssignAttr) -> None: + if isinstance( + node.assign_type(), nodes.AugAssign + ) and self._uses_mandatory_method_param(node): + self._accessed.set_accessed(node) + self._check_in_slots(node) + self._check_invalid_class_object(node) + + def _check_invalid_class_object(self, node: nodes.AssignAttr) -> None: + if not node.attrname == "__class__": + return + inferred = safe_infer(node.parent.value) + if isinstance(inferred, nodes.ClassDef) or inferred is astroid.Uninferable: + # If is uninferrable, we allow it to prevent false positives + return + self.add_message("invalid-class-object", node=node) + + def _check_in_slots(self, node): + """Check that the given AssignAttr node + is defined in the class slots. + """ + inferred = safe_infer(node.expr) + if not isinstance(inferred, astroid.Instance): + return + + klass = inferred._proxied + if not has_known_bases(klass): + return + if "__slots__" not in klass.locals or not klass.newstyle: + return + # If `__setattr__` is defined on the class, then we can't reason about + # what will happen when assigning to an attribute. + if any( + base.locals.get("__setattr__") + for base in klass.mro() + if base.qname() != "builtins.object" + ): + return + + # If 'typing.Generic' is a base of bases of klass, the cached version + # of 'slots()' might have been evaluated incorrectly, thus deleted cache entry. + if any(base.qname() == "typing.Generic" for base in klass.mro()): + cache = getattr(klass, "__cache", None) + if cache and cache.get(klass.slots) is not None: + del cache[klass.slots] + + slots = klass.slots() + if slots is None: + return + # If any ancestor doesn't use slots, the slots + # defined for this class are superfluous. + if any( + "__slots__" not in ancestor.locals and ancestor.name != "object" + for ancestor in klass.ancestors() + ): + return + + if not any(slot.value == node.attrname for slot in slots): + # If we have a '__dict__' in slots, then + # assigning any name is valid. + if not any(slot.value == "__dict__" for slot in slots): + if _is_attribute_property(node.attrname, klass): + # Properties circumvent the slots mechanism, + # so we should not emit a warning for them. + return + if node.attrname in klass.locals and _has_data_descriptor( + klass, node.attrname + ): + # Descriptors circumvent the slots mechanism as well. + return + if node.attrname == "__class__" and _has_same_layout_slots( + slots, node.parent.value + ): + return + self.add_message("assigning-non-slot", args=(node.attrname,), node=node) + + @check_messages( + "protected-access", "no-classmethod-decorator", "no-staticmethod-decorator" + ) + def visit_assign(self, assign_node: nodes.Assign) -> None: + self._check_classmethod_declaration(assign_node) + node = assign_node.targets[0] + if not isinstance(node, nodes.AssignAttr): + return + + if self._uses_mandatory_method_param(node): + return + self._check_protected_attribute_access(node) + + def _check_classmethod_declaration(self, node): + """Checks for uses of classmethod() or staticmethod() + + When a @classmethod or @staticmethod decorator should be used instead. + A message will be emitted only if the assignment is at a class scope + and only if the classmethod's argument belongs to the class where it + is defined. + `node` is an assign node. + """ + if not isinstance(node.value, nodes.Call): + return + + # check the function called is "classmethod" or "staticmethod" + func = node.value.func + if not isinstance(func, nodes.Name) or func.name not in ( + "classmethod", + "staticmethod", + ): + return + + msg = ( + "no-classmethod-decorator" + if func.name == "classmethod" + else "no-staticmethod-decorator" + ) + # assignment must be at a class scope + parent_class = node.scope() + if not isinstance(parent_class, nodes.ClassDef): + return + + # Check if the arg passed to classmethod is a class member + classmeth_arg = node.value.args[0] + if not isinstance(classmeth_arg, nodes.Name): + return + + method_name = classmeth_arg.name + if any(method_name == member.name for member in parent_class.mymethods()): + self.add_message(msg, node=node.targets[0]) + + def _check_protected_attribute_access(self, node: nodes.Attribute): + """Given an attribute access node (set or get), check if attribute + access is legitimate. Call _check_first_attr with node before calling + this method. Valid cases are: + * self._attr in a method or cls._attr in a classmethod. Checked by + _check_first_attr. + * Klass._attr inside "Klass" class. + * Klass2._attr inside "Klass" class when Klass2 is a base class of + Klass. + """ + attrname = node.attrname + + if ( + is_attr_protected(attrname) + and attrname not in self.config.exclude_protected + ): + + klass = node_frame_class(node) + + # In classes, check we are not getting a parent method + # through the class object or through super + callee = node.expr.as_string() + + # Typing annotations in function definitions can include protected members + if utils.is_node_in_type_annotation_context(node): + return + + # We are not in a class, no remaining valid case + if klass is None: + self.add_message("protected-access", node=node, args=attrname) + return + + # If the expression begins with a call to super, that's ok. + if ( + isinstance(node.expr, nodes.Call) + and isinstance(node.expr.func, nodes.Name) + and node.expr.func.name == "super" + ): + return + + # If the expression begins with a call to type(self), that's ok. + if self._is_type_self_call(node.expr): + return + + # Check if we are inside the scope of a class or nested inner class + inside_klass = True + outer_klass = klass + parents_callee = callee.split(".") + parents_callee.reverse() + for callee in parents_callee: + if not outer_klass or callee != outer_klass.name: + inside_klass = False + break + + # Move up one level within the nested classes + outer_klass = get_outer_class(outer_klass) + + # We are in a class, one remaining valid cases, Klass._attr inside + # Klass + if not (inside_klass or callee in klass.basenames): + # Detect property assignments in the body of the class. + # This is acceptable: + # + # class A: + # b = property(lambda: self._b) + + stmt = node.parent.statement(future=True) + if ( + isinstance(stmt, nodes.Assign) + and len(stmt.targets) == 1 + and isinstance(stmt.targets[0], nodes.AssignName) + ): + name = stmt.targets[0].name + if _is_attribute_property(name, klass): + return + + if ( + self._is_classmethod(node.frame()) + and self._is_inferred_instance(node.expr, klass) + and self._is_class_attribute(attrname, klass) + ): + return + + licit_protected_member = not attrname.startswith("__") + if ( + not self.config.check_protected_access_in_special_methods + and licit_protected_member + and self._is_called_inside_special_method(node) + ): + return + + self.add_message("protected-access", node=node, args=attrname) + + @staticmethod + def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: + """ + Returns true if the node is located inside a special (aka dunder) method + """ + try: + frame_name = node.frame().name + except AttributeError: + return False + return frame_name and frame_name in PYMETHODS + + def _is_type_self_call(self, expr): + return ( + isinstance(expr, nodes.Call) + and isinstance(expr.func, nodes.Name) + and expr.func.name == "type" + and len(expr.args) == 1 + and self._is_mandatory_method_param(expr.args[0]) + ) + + @staticmethod + def _is_classmethod(func): + """Check if the given *func* node is a class method.""" + + return isinstance(func, nodes.FunctionDef) and ( + func.type == "classmethod" or func.name == "__class_getitem__" + ) + + @staticmethod + def _is_inferred_instance(expr, klass): + """Check if the inferred value of the given *expr* is an instance of *klass*.""" + + inferred = safe_infer(expr) + if not isinstance(inferred, astroid.Instance): + return False + + return inferred._proxied is klass + + @staticmethod + def _is_class_attribute(name, klass): + """Check if the given attribute *name* is a class or instance member of the given *klass*. + + Returns ``True`` if the name is a property in the given klass, + ``False`` otherwise. + """ + + try: + klass.getattr(name) + return True + except astroid.NotFoundError: + pass + + try: + klass.instance_attr(name) + return True + except astroid.NotFoundError: + return False + + def visit_name(self, node: nodes.Name) -> None: + """check if the name handle an access to a class member + if so, register it + """ + if self._first_attrs and ( + node.name == self._first_attrs[-1] or not self._first_attrs[-1] + ): + self._meth_could_be_func = False + + def _check_accessed_members(self, node, accessed): + """check that accessed members are defined""" + excs = ("AttributeError", "Exception", "BaseException") + for attr, nodes_lst in accessed.items(): + try: + # is it a class attribute ? + node.local_attr(attr) + # yes, stop here + continue + except astroid.NotFoundError: + pass + # is it an instance attribute of a parent class ? + try: + next(node.instance_attr_ancestors(attr)) + # yes, stop here + continue + except StopIteration: + pass + # is it an instance attribute ? + try: + defstmts = node.instance_attr(attr) + except astroid.NotFoundError: + pass + else: + # filter out augment assignment nodes + defstmts = [stmt for stmt in defstmts if stmt not in nodes_lst] + if not defstmts: + # only augment assignment for this node, no-member should be + # triggered by the typecheck checker + continue + # filter defstmts to only pick the first one when there are + # several assignments in the same scope + scope = defstmts[0].scope() + defstmts = [ + stmt + for i, stmt in enumerate(defstmts) + if i == 0 or stmt.scope() is not scope + ] + # if there are still more than one, don't attempt to be smarter + # than we can be + if len(defstmts) == 1: + defstmt = defstmts[0] + # check that if the node is accessed in the same method as + # it's defined, it's accessed after the initial assignment + frame = defstmt.frame() + lno = defstmt.fromlineno + for _node in nodes_lst: + if ( + _node.frame() is frame + and _node.fromlineno < lno + and not astroid.are_exclusive( + _node.statement(future=True), defstmt, excs + ) + ): + self.add_message( + "access-member-before-definition", + node=_node, + args=(attr, lno), + ) + + def _check_first_arg_for_type(self, node, metaclass=0): + """check the name of first argument, expect: + + * 'self' for a regular method + * 'cls' for a class method or a metaclass regular method (actually + valid-classmethod-first-arg value) + * 'mcs' for a metaclass class method (actually + valid-metaclass-classmethod-first-arg) + * not one of the above for a static method + """ + # don't care about functions with unknown argument (builtins) + if node.args.args is None: + return + if node.args.posonlyargs: + first_arg = node.args.posonlyargs[0].name + elif node.args.args: + first_arg = node.argnames()[0] + else: + first_arg = None + self._first_attrs.append(first_arg) + first = self._first_attrs[-1] + # static method + if node.type == "staticmethod": + if ( + first_arg == "self" + or first_arg in self.config.valid_classmethod_first_arg + or first_arg in self.config.valid_metaclass_classmethod_first_arg + ): + self.add_message("bad-staticmethod-argument", args=first, node=node) + return + self._first_attrs[-1] = None + # class / regular method with no args + elif not node.args.args and not node.args.posonlyargs: + self.add_message("no-method-argument", node=node) + # metaclass + elif metaclass: + # metaclass __new__ or classmethod + if node.type == "classmethod": + self._check_first_arg_config( + first, + self.config.valid_metaclass_classmethod_first_arg, + node, + "bad-mcs-classmethod-argument", + node.name, + ) + # metaclass regular method + else: + self._check_first_arg_config( + first, + self.config.valid_classmethod_first_arg, + node, + "bad-mcs-method-argument", + node.name, + ) + # regular class with class method + elif node.type == "classmethod" or node.name == "__class_getitem__": + self._check_first_arg_config( + first, + self.config.valid_classmethod_first_arg, + node, + "bad-classmethod-argument", + node.name, + ) + # regular class with regular method without self as argument + elif first != "self": + self.add_message("no-self-argument", node=node) + + def _check_first_arg_config(self, first, config, node, message, method_name): + if first not in config: + if len(config) == 1: + valid = repr(config[0]) + else: + valid = ", ".join(repr(v) for v in config[:-1]) + valid = f"{valid} or {config[-1]!r}" + self.add_message(message, args=(method_name, valid), node=node) + + def _check_bases_classes(self, node): + """check that the given class node implements abstract methods from + base classes + """ + + def is_abstract(method): + return method.is_abstract(pass_is_abstract=False) + + # check if this class abstract + if class_is_abstract(node): + return + + methods = sorted( + unimplemented_abstract_methods(node, is_abstract).items(), + key=lambda item: item[0], + ) + for name, method in methods: + owner = method.parent.frame() + if owner is node: + continue + # owner is not this class, it must be a parent class + # check that the ancestor's method is not abstract + if name in node.locals: + # it is redefined as an attribute or with a descriptor + continue + self.add_message("abstract-method", node=node, args=(name, owner.name)) + + def _check_init(self, node): + """check that the __init__ method call super or ancestors'__init__ + method (unless it is used for type hinting with `typing.overload`) + """ + if not self.linter.is_message_enabled( + "super-init-not-called" + ) and not self.linter.is_message_enabled("non-parent-init-called"): + return + klass_node = node.parent.frame() + to_call = _ancestors_to_call(klass_node) + not_called_yet = dict(to_call) + for stmt in node.nodes_of_class(nodes.Call): + expr = stmt.func + if not isinstance(expr, nodes.Attribute) or expr.attrname != "__init__": + continue + # skip the test if using super + if ( + isinstance(expr.expr, nodes.Call) + and isinstance(expr.expr.func, nodes.Name) + and expr.expr.func.name == "super" + ): + return + try: + for klass in expr.expr.infer(): + if klass is astroid.Uninferable: + continue + # The inferred klass can be super(), which was + # assigned to a variable and the `__init__` + # was called later. + # + # base = super() + # base.__init__(...) + + if ( + isinstance(klass, astroid.Instance) + and isinstance(klass._proxied, nodes.ClassDef) + and is_builtin_object(klass._proxied) + and klass._proxied.name == "super" + ): + return + if isinstance(klass, astroid.objects.Super): + return + try: + del not_called_yet[klass] + except KeyError: + if klass not in to_call: + self.add_message( + "non-parent-init-called", node=expr, args=klass.name + ) + except astroid.InferenceError: + continue + for klass, method in not_called_yet.items(): + if decorated_with(node, ["typing.overload"]): + continue + cls = node_frame_class(method) + if klass.name == "object" or (cls and cls.name == "object"): + continue + self.add_message("super-init-not-called", args=klass.name, node=node) + + def _check_signature(self, method1, refmethod, class_type, cls): + """check that the signature of the two given methods match""" + if not ( + isinstance(method1, nodes.FunctionDef) + and isinstance(refmethod, nodes.FunctionDef) + ): + self.add_message( + "method-check-failed", args=(method1, refmethod), node=method1 + ) + return + + instance = cls.instantiate_class() + method1 = astroid.scoped_nodes.function_to_method(method1, instance) + refmethod = astroid.scoped_nodes.function_to_method(refmethod, instance) + + # Don't care about functions with unknown argument (builtins). + if method1.args.args is None or refmethod.args.args is None: + return + + # Ignore private to class methods. + if is_attr_private(method1.name): + return + # Ignore setters, they have an implicit extra argument, + # which shouldn't be taken in consideration. + if is_property_setter(method1): + return + + arg_differ_output = _different_parameters( + refmethod, method1, dummy_parameter_regex=self._dummy_rgx + ) + if len(arg_differ_output) > 0: + for msg in arg_differ_output: + if "Number" in msg: + total_args_method1 = len(method1.args.args) + if method1.args.vararg: + total_args_method1 += 1 + if method1.args.kwarg: + total_args_method1 += 1 + if method1.args.kwonlyargs: + total_args_method1 += len(method1.args.kwonlyargs) + total_args_refmethod = len(refmethod.args.args) + if refmethod.args.vararg: + total_args_refmethod += 1 + if refmethod.args.kwarg: + total_args_refmethod += 1 + if refmethod.args.kwonlyargs: + total_args_refmethod += len(refmethod.args.kwonlyargs) + error_type = "arguments-differ" + msg_args = ( + msg + + f"was {total_args_refmethod} in '{refmethod.parent.name}.{refmethod.name}' and " + f"is now {total_args_method1} in", + class_type, + f"{method1.parent.name}.{method1.name}", + ) + elif "renamed" in msg: + error_type = "arguments-renamed" + msg_args = ( + msg, + class_type, + f"{method1.parent.name}.{method1.name}", + ) + else: + error_type = "arguments-differ" + msg_args = ( + msg, + class_type, + f"{method1.parent.name}.{method1.name}", + ) + self.add_message(error_type, args=msg_args, node=method1) + elif ( + len(method1.args.defaults) < len(refmethod.args.defaults) + and not method1.args.vararg + ): + self.add_message( + "signature-differs", args=(class_type, method1.name), node=method1 + ) + + def _uses_mandatory_method_param(self, node): + """Check that attribute lookup name use first attribute variable name + + Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. + """ + return self._is_mandatory_method_param(node.expr) + + def _is_mandatory_method_param(self, node): + """Check if nodes.Name corresponds to first attribute variable name + + Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. + """ + return ( + self._first_attrs + and isinstance(node, nodes.Name) + and node.name == self._first_attrs[-1] + ) + + +def _ancestors_to_call(klass_node, method="__init__"): + """return a dictionary where keys are the list of base classes providing + the queried method, and so that should/may be called from the method node + """ + to_call = {} + for base_node in klass_node.ancestors(recurs=False): + try: + to_call[base_node] = next(base_node.igetattr(method)) + except astroid.InferenceError: + continue + return to_call From 566a377154ce68e795aa3f952bb70bd987045228 Mon Sep 17 00:00:00 2001 From: "Kian Meng, Ang" Date: Sat, 18 Dec 2021 00:23:17 +0800 Subject: [PATCH 062/357] Fix typos over the whole codebase (#5540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- CONTRIBUTORS.txt | 6 ++-- ChangeLog | 32 +++++++++---------- doc/whatsnew/2.11.rst | 2 +- doc/whatsnew/2.12.rst | 6 ++-- doc/whatsnew/2.13.rst | 2 +- doc/whatsnew/2.7.rst | 2 +- examples/pylintrc | 2 +- pylint/checkers/format.py | 2 +- pylint/checkers/mapreduce_checker.py | 2 +- pylint/checkers/similar.py | 4 +-- pylint/checkers/variables.py | 2 +- pylint/epylint.py | 2 +- pylint/extensions/_check_docs_utils.py | 2 +- pylint/lint/pylinter.py | 6 ++-- pylint/lint/run.py | 2 +- pylintrc | 2 +- tests/benchmark/test_baseline_benchmarks.py | 2 +- .../a/assign/assignment_from_no_return_2.py | 4 +-- .../missing_param_doc_required_Sphinx.py | 4 +-- .../yield/missing_yield_doc_Google.py | 2 +- .../yield/missing_yield_doc_Numpy.py | 2 +- tests/functional/m/method_hidden.py | 2 +- tests/functional/n/not_callable.py | 2 +- tests/functional/o/overloaded_operator.py | 2 +- .../regression_property_no_member_3269.py | 2 +- .../t/too/too_few_public_methods_excluded.py | 2 +- ...ment_except_handler_for_try_with_return.py | 6 ++-- .../u/useless/useless_else_on_loop.py | 2 +- .../u/useless/useless_super_delegation.py | 2 +- tests/unittest_reporting.py | 2 +- 30 files changed, 57 insertions(+), 55 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2752254244..eee4da9a43 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -381,9 +381,9 @@ contributors: * Damien Baty: contributor -* Daniel R. Neal (danrneal): contributer +* Daniel R. Neal (danrneal): contributor -* Jeremy Fleischman (jfly): contributer +* Jeremy Fleischman (jfly): contributor * Shiv Venkatasubrahmanyam @@ -593,3 +593,5 @@ contributors: - Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params * Eero Vuojolahti: contributor + +* Kian-Meng, Ang: contributor diff --git a/ChangeLog b/ChangeLog index 069868d986..10137bdaa1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -56,7 +56,7 @@ Release date: TBA Closes #5504 -* The ``PyLinter`` class will now be initialiazed with a ``TextReporter`` +* The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. * Fatal errors now emit a score of 0.0 regardless of whether the linted module @@ -207,8 +207,8 @@ Release date: 2021-11-24 output files without these will trigger a ``DeprecationWarning``. Expected output files can be easily updated with the ``python tests/test_functional.py --update-functional-output`` command. -* The functional ``testutils`` now correctly check the distinction betweeen ``HIGH`` and - ``UNDEFINED`` confidence. Expected output files without defiend ``confidence`` levels will now +* The functional ``testutils`` now correctly check the distinction between ``HIGH`` and + ``UNDEFINED`` confidence. Expected output files without defined ``confidence`` levels will now trigger a ``DeprecationWarning``. Expected output files can be easily updated with the ``python tests/test_functional.py --update-functional-output`` command. @@ -331,7 +331,7 @@ Release date: 2021-11-24 Closes #5194 -* Fix double emitting of ``not-callable`` on inferrable ``properties`` +* Fix double emitting of ``not-callable`` on inferable ``properties`` Closes #4426 @@ -495,7 +495,7 @@ Release date: 2021-09-16 Closes #4776 -* Added ``py-version`` config key (if ``[MASTER]`` section). Used for version dependant checks. +* Added ``py-version`` config key (if ``[MASTER]`` section). Used for version dependent checks. Will default to whatever Python version pylint is executed with. * ``CodeStyleChecker`` @@ -514,7 +514,7 @@ Release date: 2021-09-16 Closes #4751 -* https is now prefered in the documentation and http://pylint.pycqa.org correctly redirect to https://pylint.pycqa.org +* https is now preferred in the documentation and http://pylint.pycqa.org correctly redirect to https://pylint.pycqa.org Closes #3802 @@ -728,7 +728,7 @@ Release date: 2021-08-20 * Fixed bug with ``cell-var-from-loop`` checker: it no longer has false negatives when both ``unused-variable`` and ``used-before-assignment`` are disabled. -* Fix false postive for ``invalid-all-format`` if the list or tuple builtin functions are used +* Fix false positive for ``invalid-all-format`` if the list or tuple builtin functions are used Closes #4711 @@ -1456,7 +1456,7 @@ Release date: 2021-02-21 Close #3862 -* Fix linter multiprocessing pool shutdown (triggered warnings when runned in parallels with other pytest plugins) +* Fix linter multiprocessing pool shutdown (triggered warnings when ran in parallels with other pytest plugins) Closes #3779 @@ -1483,7 +1483,7 @@ Release date: 2021-02-21 Closes #3468 -* Fix ``useless-super-delegation`` false positive when default keyword argument is a dictionnary. +* Fix ``useless-super-delegation`` false positive when default keyword argument is a dictionary. Close #3773 @@ -3484,7 +3484,7 @@ Release date: 2017-04-13 This message is emitted when pylint finds an one-element tuple, created by a stray comma. This can suggest a potential problem in the - code and it is recommended to use parantheses in order to emphasise the + code and it is recommended to use parentheses in order to emphasise the creation of a tuple, rather than relying on the comma itself. * Don't emit not-callable for instances with unknown bases. @@ -4184,7 +4184,7 @@ Release date: 2015-11-29 directory 'test/extensions' and documentation file 'doc/extensions.rst'. * Added new checker 'extensions.check_docs' that verifies parameter - documention in Sphinx, Google, and Numpy style. + documentation in Sphinx, Google, and Numpy style. * Detect undefined variable cases, where the "definition" of an undefined variable was in del statement. Instead of emitting used-before-assignment, @@ -4280,7 +4280,7 @@ Release date: 2015-11-29 two a binary arithmetic operation is executed between two objects which don't support it (a number plus a string for instance). This is currently disabled, since the it exhibits way too many false - positives, but it will be reenabled as soon as possible. + positives, but it will be re-enabled as soon as possible. * New imported features from astroid into pyreverse: pyreverse.inspector.Project, pyreverse.inspector.project_from_files and pyreverse.inspector.interfaces. @@ -5255,7 +5255,7 @@ Release date: 2012-10-05 * #105337: allow custom reporter in output-format (patch by Kevin Jing Qiu) - * #104420: check for protocol completness and avoid false R0903 + * #104420: check for protocol completeness and avoid false R0903 (patch by Peter Hammond) * #100654: fix grammatical error for W0332 message (using 'l' as @@ -5283,7 +5283,7 @@ Release date: 2012-07-17 except handler contains a tuple of names instead of a single name. (patch by tmarek@google.com) - * #7394: W0212 (access to protected member) not emitted on assigments + * #7394: W0212 (access to protected member) not emitted on assignments (patch by lothiraldan@gmail.com) * #18772; no prototype consistency check for mangled methods (patch by @@ -5878,7 +5878,7 @@ Release date: 2006-03-06 - the C0101 check with its min-name-length option has been removed (this can be specified in the regxp after all...) - W0103 and W0121 are now handled by the variables checker - (W0103 is now W0603 and W0604 has been splitted into different messages) + (W0103 is now W0603 and W0604 has been split into different messages) - W0131 and W0132 messages have been reclassified to C0111 and C0112 respectively - new W0104 message on statement without effect @@ -5915,7 +5915,7 @@ Release date: 2006-01-10 or the default ~/.pylintrc or /etc/pylintrc * fixed W0706 (Identifier used to raise an exception is assigned...) - false positive and reraising a catched exception instance + false positive and reraising a caught exception instance * fixed E0611 (No name get in module blabla) false positive when accessing to a class'__dict__ diff --git a/doc/whatsnew/2.11.rst b/doc/whatsnew/2.11.rst index 647f326cdd..8d098e140c 100644 --- a/doc/whatsnew/2.11.rst +++ b/doc/whatsnew/2.11.rst @@ -67,7 +67,7 @@ Extensions Other Changes ============= -* Added ``py-version`` config key (if ``[MASTER]`` section). Used for version dependant checks. +* Added ``py-version`` config key (if ``[MASTER]`` section). Used for version dependent checks. Will default to whatever Python version pylint is executed with. * The ``invalid-name`` message is now more detailed when using multiple naming style regexes. diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index 4704691d54..c47f2e91a9 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -139,8 +139,8 @@ Other Changes output files without these will trigger a ``DeprecationWarning``. Expected output files can be easily updated with the ``python tests/test_functional.py --update-functional-output`` command. -* The functional ``testutils`` now correctly check the distinction betweeen ``HIGH`` and - ``UNDEFINED`` confidence. Expected output files without defiend ``confidence`` levels will now +* The functional ``testutils`` now correctly check the distinction between ``HIGH`` and + ``UNDEFINED`` confidence. Expected output files without defined ``confidence`` levels will now trigger a ``DeprecationWarning``. Expected output files can be easily updated with the ``python tests/test_functional.py --update-functional-output`` command. @@ -179,7 +179,7 @@ Other Changes Closes #5261 -* Fix double emitting of ``not-callable`` on inferrable ``properties`` +* Fix double emitting of ``not-callable`` on inferable ``properties`` Closes #4426 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 31b6d4e367..72f071510c 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -10,7 +10,7 @@ Summary -- Release highlights New checkers ============ -* ``unnecessary-ellipsis``: Emmitted when the ellipsis constant is used unnecessarily. +* ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily. Closes #5460 diff --git a/doc/whatsnew/2.7.rst b/doc/whatsnew/2.7.rst index 35bdb9348b..6c0fccf181 100644 --- a/doc/whatsnew/2.7.rst +++ b/doc/whatsnew/2.7.rst @@ -35,7 +35,7 @@ Other Changes * Fix false positive for ``builtin-not-iterating`` when ``zip`` or ``map`` receives iterable -* Fix linter multiprocessing pool shutdown which triggered warnings when runned in parallels with other pytest plugins. +* Fix linter multiprocessing pool shutdown which triggered warnings when ran in parallels with other pytest plugins. * Enums are now required to be named in UPPER_CASE by ``invalid-name``. diff --git a/examples/pylintrc b/examples/pylintrc index 2662898dca..eb48b4a1fe 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -66,7 +66,7 @@ confidence= # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if +# disable everything first and then re-enable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 0c0cb8752c..54e571296e 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -452,7 +452,7 @@ def _check_keyword_parentheses( elif token[1] == "for": return # A generator expression can have an 'else' token in it. - # We check the rest of the tokens to see if any problems incure after + # We check the rest of the tokens to see if any problems incur after # the 'else'. elif token[1] == "else": if "(" in (i.string for i in tokens[i:]): diff --git a/pylint/checkers/mapreduce_checker.py b/pylint/checkers/mapreduce_checker.py index 9b90c634a6..b1722fc9bb 100644 --- a/pylint/checkers/mapreduce_checker.py +++ b/pylint/checkers/mapreduce_checker.py @@ -12,7 +12,7 @@ class MapReduceMixin(metaclass=abc.ABCMeta): @abc.abstractmethod def get_map_data(self): - """Returns mergable/reducible data that will be examined""" + """Returns mergeable/reducible data that will be examined""" @abc.abstractmethod def reduce_map_data(self, linter, data): diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index f9cb9edbc7..cadee9f702 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -30,7 +30,7 @@ """a similarities / code duplication command line tool and pylint checker The algorithm is based on comparing the hash value of n successive lines of a file. -First the files are read and any line that doesn't fullfill requirement are removed (comments, docstrings...) +First the files are read and any line that doesn't fulfill requirement are removed (comments, docstrings...) Those stripped lines are stored in the LineSet class which gives access to them. Then each index of the stripped lines collection is associated with the hash of n successive entries of the stripped lines starting at the current index (n is the minimum common lines option). @@ -123,7 +123,7 @@ def __init__( self.effective_cmn_lines_nb = effective_cmn_lines_nb -# Links the indices ot the starting line in both lineset's stripped lines to +# Links the indices to the starting line in both lineset's stripped lines to # the start and end lines in both files CplIndexToCplLines_T = Dict["LineSetStartCouple", CplSuccessiveLinesLimits] diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 27ad863f8c..82b99a805c 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1795,7 +1795,7 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool if defstmt_frame == node_frame and not ref_node.lineno < node.lineno: break - # If the parent of the local refence is anything but a AnnAssign + # If the parent of the local reference is anything but a AnnAssign # Or if the AnnAssign adds a value the variable will now have a value # var = 1 # OR # var: int = 1 diff --git a/pylint/epylint.py b/pylint/epylint.py index 13842b440e..09795d6395 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -125,7 +125,7 @@ def lint(filename, options=()): if line.startswith("No config file found"): continue - # modify the file name thats output to reverse the path traversal we made + # modify the file name that's put out to reverse the path traversal we made parts = line.split(":") if parts and parts[0] == child_path: line = ":".join([filename] + parts[1:]) diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 2a26fe0e29..ad4a5bb32e 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -519,7 +519,7 @@ class GoogleDocstring(Docstring): re_property_returns_line = re.compile( fr""" - ^{re_multiple_type}: # indentifier + ^{re_multiple_type}: # identifier \s* (.*) # Summary line / description """, re.X | re.S | re.M, diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index d6edf1ef9f..5c4cb8f60e 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -376,7 +376,7 @@ def make_options(): "(only on the command line, not in the configuration file " "where it should appear only once). " 'You can also use "--disable=all" to disable everything first ' - "and then reenable specific checks. For example, if you want " + "and then re-enable specific checks. For example, if you want " "to run only the similarities checker, you can use " '"--disable=all --enable=similarities". ' "If you want to run only the classes checker, but have no " @@ -750,7 +750,7 @@ def enable_fail_on_messages(self): fail_on_cats = set() fail_on_msgs = set() for val in fail_on_vals: - # If value is a cateogry, add category, else add message + # If value is a category, add category, else add message if val in MSG_TYPES: fail_on_cats.add(val) else: @@ -1607,7 +1607,7 @@ def _set_msg_status( line: Optional[int] = None, ignore_unknown: bool = False, ) -> None: - """Do some tests and then iterate over message defintions to set state""" + """Do some tests and then iterate over message definitions to set state""" assert scope in {"package", "module"} if msgid == "all": for _msgid in MSG_TYPES: diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 60d26badf5..d607cfd807 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -350,7 +350,7 @@ def __init__( if reporter: # if a custom reporter is provided as argument, it may be overridden # by file parameters, so re-set it here, but before command line - # parsing so it's still overrideable by command line option + # parsing so it's still overridable by command line option linter.set_reporter(reporter) try: args = linter.load_command_line_configuration(args) diff --git a/pylintrc b/pylintrc index 2682bfc1a3..9c82edeb4f 100644 --- a/pylintrc +++ b/pylintrc @@ -65,7 +65,7 @@ enable= # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if +# disable everything first and then re-enable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index 27635f6191..58939fd4db 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -176,7 +176,7 @@ def test_baseline_lots_of_files_j1(self, benchmark): """Establish a baseline with only 'master' checker being run in -j1 We do not register any checkers except the default 'master', so the cost is just - that of the system with a lot of files registerd""" + that of the system with a lot of files registered""" if benchmark.disabled: benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling diff --git a/tests/functional/a/assign/assignment_from_no_return_2.py b/tests/functional/a/assign/assignment_from_no_return_2.py index 4cd5ef1066..f177f6a9a6 100644 --- a/tests/functional/a/assign/assignment_from_no_return_2.py +++ b/tests/functional/a/assign/assignment_from_no_return_2.py @@ -3,10 +3,10 @@ 'E1111': ('Assigning to function call which doesn\'t return', 'Used when an assignment is done on a function call but the \ - infered function doesn\'t return anything.'), + inferred function doesn\'t return anything.'), 'W1111': ('Assigning to function call which only returns None', 'Used when an assignment is done on a function call but the \ - infered function returns nothing but None.'), + inferred function returns nothing but None.'), """ from __future__ import generators, print_function diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py index 0ec15b234a..942de6e6e3 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.py @@ -219,7 +219,7 @@ def test_finds_args_without_type_sphinx( # [inconsistent-return-statements] named_arg, *args ): r"""The Sphinx docstring - In Sphinx docstrings asteriks should be escaped. + In Sphinx docstrings asterisks should be escaped. See https://github.com/PyCQA/pylint/issues/5406 :param named_arg: Returned @@ -238,7 +238,7 @@ def test_finds_kwargs_without_type_sphinx( # [inconsistent-return-statements] named_arg, **kwargs ): r"""The Sphinx docstring - In Sphinx docstrings asteriks should be escaped. + In Sphinx docstrings asterisks should be escaped. See https://github.com/PyCQA/pylint/issues/5406 :param named_arg: Returned diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_Google.py b/tests/functional/ext/docparams/yield/missing_yield_doc_Google.py index 986ca8d08d..9ebfbb30b0 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_Google.py +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_Google.py @@ -4,7 +4,7 @@ import typing -# Test redudant yields docstring variants +# Test redundant yields docstring variants def my_func(self): """This is a docstring. diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.py b/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.py index bd6a4a0fed..ed827550b3 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.py +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.py @@ -3,7 +3,7 @@ # pylint: disable=invalid-name, undefined-variable -# Test redudant yields docstring variants +# Test redundant yields docstring variants def my_func(self): """This is a docstring. diff --git a/tests/functional/m/method_hidden.py b/tests/functional/m/method_hidden.py index c5c8024501..e31bc16c4f 100644 --- a/tests/functional/m/method_hidden.py +++ b/tests/functional/m/method_hidden.py @@ -1,6 +1,6 @@ # pylint: disable=too-few-public-methods, useless-object-inheritance,missing-docstring # pylint: disable=unused-private-member -"""check method hidding ancestor attribute +"""check method hiding ancestor attribute """ from __future__ import print_function diff --git a/tests/functional/n/not_callable.py b/tests/functional/n/not_callable.py index 38232dc465..a7c59ae9cb 100644 --- a/tests/functional/n/not_callable.py +++ b/tests/functional/n/not_callable.py @@ -127,7 +127,7 @@ class UnknownBaseCallable(missing.Blah): UnknownBaseCallable()() # Regression test for #4426 -# If property is inferrable we shouldn't double emit the message +# If property is inferable we shouldn't double emit the message # See: https://github.com/PyCQA/pylint/issues/4426 class ClassWithProperty: @property diff --git a/tests/functional/o/overloaded_operator.py b/tests/functional/o/overloaded_operator.py index c305af689a..c14fb651f1 100644 --- a/tests/functional/o/overloaded_operator.py +++ b/tests/functional/o/overloaded_operator.py @@ -18,4 +18,4 @@ def randint(maximum): return int(5) -print(randint(1).astype()) # we don't wan't an error for astype access +print(randint(1).astype()) # we don't want an error for astype access diff --git a/tests/functional/r/regression/regression_property_no_member_3269.py b/tests/functional/r/regression/regression_property_no_member_3269.py index 784dd90d40..5fa85b6eaf 100644 --- a/tests/functional/r/regression/regression_property_no_member_3269.py +++ b/tests/functional/r/regression/regression_property_no_member_3269.py @@ -16,7 +16,7 @@ class B: @property def test(self): """Overriding implementation of prop which calls the parent""" - return A.test.fget(self) + " overriden" + return A.test.fget(self) + " overridden" if __name__ == "__main__": diff --git a/tests/functional/t/too/too_few_public_methods_excluded.py b/tests/functional/t/too/too_few_public_methods_excluded.py index fecf497ad1..2ee8f1c4a4 100644 --- a/tests/functional/t/too/too_few_public_methods_excluded.py +++ b/tests/functional/t/too/too_few_public_methods_excluded.py @@ -9,5 +9,5 @@ class MyJsonEncoder(JSONEncoder): ... class InheritedInModule(Control): - """This class inherits from a class that doesn't have enough mehods, + """This class inherits from a class that doesn't have enough methods, and its parent is excluded via config, so it doesn't raise.""" diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py index f915a85cc1..3afd9375ac 100644 --- a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py @@ -25,7 +25,7 @@ def func_ok(var): except AttributeError: msg = "Attribute not defined" except ZeroDivisionError: - msg = "Devision by 0" + msg = "Division by 0" print(msg) @@ -36,7 +36,7 @@ def func_ok2(var): except AttributeError as ex: raise Exception from ex except ZeroDivisionError: - msg = "Devision by 0" + msg = "Division by 0" print(msg) @@ -47,5 +47,5 @@ def func_ok3(var): except AttributeError: return except ZeroDivisionError: - msg = "Devision by 0" + msg = "Division by 0" print(msg) diff --git a/tests/functional/u/useless/useless_else_on_loop.py b/tests/functional/u/useless/useless_else_on_loop.py index 0bd3dff67b..c7d9016638 100644 --- a/tests/functional/u/useless/useless_else_on_loop.py +++ b/tests/functional/u/useless/useless_else_on_loop.py @@ -12,7 +12,7 @@ def test_return_for(): return None def test_return_while(): - """else + return is not accetable.""" + """else + return is not acceptable.""" while True: return 1 else: # [useless-else-on-loop] diff --git a/tests/functional/u/useless/useless_super_delegation.py b/tests/functional/u/useless/useless_super_delegation.py index 2e03840402..cc36a0c783 100644 --- a/tests/functional/u/useless/useless_super_delegation.py +++ b/tests/functional/u/useless/useless_super_delegation.py @@ -205,7 +205,7 @@ def with_default_arg_quad(self, first, default_arg="has_been_changed"): super(NotUselessSuper, self).with_default_arg_quad(first, default_arg + "_and_modified") def with_default_unhandled(self, first, default_arg=lambda: True): - # Not useless because the default value type is not explictely handled (Lambda), so assume they are different + # Not useless because the default value type is not explicitly handled (Lambda), so assume they are different super(NotUselessSuper, self).with_default_unhandled(first, default_arg) diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 7579873592..c0fe779167 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -92,7 +92,7 @@ def test_template_option_end_line(linter) -> None: def test_template_option_non_exisiting(linter) -> None: - """Test the msg-template option with a non exisiting options. + """Test the msg-template option with a non existing options. This makes sure that this option remains backwards compatible as new parameters do not break on previous versions""" output = StringIO() From 889a1d0cc5f9f2074159720e72cb879c45faa810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 17 Dec 2021 20:39:49 +0100 Subject: [PATCH 063/357] Fix ``not-callable`` for attributes that alias ``NamedTuple`` (#5537) --- ChangeLog | 4 +++ doc/whatsnew/2.13.rst | 4 +++ pylint/checkers/typecheck.py | 51 +++++++++++++++++++---------- tests/functional/n/not_callable.py | 14 +++++++- tests/functional/n/not_callable.txt | 2 +- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index 10137bdaa1..68ec3a7829 100644 --- a/ChangeLog +++ b/ChangeLog @@ -59,6 +59,10 @@ Release date: TBA * The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. +* Fix false positive ``not-callable`` with attributes that alias ``NamedTuple`` + + Partially closes #1730 + * Fatal errors now emit a score of 0.0 regardless of whether the linted module contained any statements diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 72f071510c..ef72d58552 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -83,6 +83,10 @@ Other Changes * The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. +* Fix false positive ``not-callable`` with attributes that alias ``NamedTuple`` + + Partially closes #1730 + * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index df01cc2fe9..c3e76228df 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1293,24 +1293,8 @@ def visit_call(self, node: nodes.Call) -> None: the inferred function's definition """ called = safe_infer(node.func) - # only function, generator and object defining __call__ are allowed - # Ignore instances of descriptors since astroid cannot properly handle them - # yet - if called and not called.callable(): - if isinstance(called, astroid.Instance) and ( - not has_known_bases(called) - or ( - called.parent is not None - and isinstance(called.scope(), nodes.ClassDef) - and "__get__" in called.locals - ) - ): - # Don't emit if we can't make sure this object is callable. - pass - else: - self.add_message("not-callable", node=node, args=node.func.as_string()) - else: - self._check_uninferable_call(node) + + self._check_not_callable(node, called) try: called, implicit_args, callable_name = _determine_callable(called) @@ -1570,6 +1554,37 @@ def _check_invalid_sequence_index(self, subscript: nodes.Subscript): self.add_message("invalid-sequence-index", node=subscript) return None + def _check_not_callable( + self, node: nodes.Call, inferred_call: Optional[nodes.NodeNG] + ) -> None: + """Checks to see if the not-callable message should be emitted + + Only functions, generators and objects defining __call__ are "callable" + We ignore instances of descriptors since astroid cannot properly handle them yet + """ + # Handle uninferable calls + if not inferred_call or inferred_call.callable(): + self._check_uninferable_call(node) + return + + if not isinstance(inferred_call, astroid.Instance): + self.add_message("not-callable", node=node, args=node.func.as_string()) + return + + # Don't emit if we can't make sure this object is callable. + if not has_known_bases(inferred_call): + return + + if inferred_call.parent and isinstance(inferred_call.scope(), nodes.ClassDef): + # Ignore descriptor instances + if "__get__" in inferred_call.locals: + return + # NamedTuple instances are callable + if inferred_call.qname() == "typing.NamedTuple": + return + + self.add_message("not-callable", node=node, args=node.func.as_string()) + @check_messages("invalid-sequence-index") def visit_extslice(self, node: nodes.ExtSlice) -> None: if not node.parent or not hasattr(node.parent, "value"): diff --git a/tests/functional/n/not_callable.py b/tests/functional/n/not_callable.py index a7c59ae9cb..31d364b885 100644 --- a/tests/functional/n/not_callable.py +++ b/tests/functional/n/not_callable.py @@ -136,13 +136,25 @@ def value(self): CLASS_WITH_PROP = ClassWithProperty().value() # [not-callable] -# Test typing.Namedtuple not callable +# Test typing.Namedtuple is callable # See: https://github.com/PyCQA/pylint/issues/1295 import typing Named = typing.NamedTuple("Named", [("foo", int), ("bar", int)]) named = Named(1, 2) + +# NamedTuple is callable, even if it aliased to a attribute +# See https://github.com/PyCQA/pylint/issues/1730 +class TestNamedTuple: + def __init__(self, field: str) -> None: + self.my_tuple = typing.NamedTuple("Tuple", [(field, int)]) + self.item: self.my_tuple + + def set_item(self, item: int) -> None: + self.item = self.my_tuple(item) + + # Test descriptor call def func(): pass diff --git a/tests/functional/n/not_callable.txt b/tests/functional/n/not_callable.txt index f079bbfe83..e8a06b0030 100644 --- a/tests/functional/n/not_callable.txt +++ b/tests/functional/n/not_callable.txt @@ -7,4 +7,4 @@ not-callable:32:12:32:17::INT is not callable:UNDEFINED not-callable:67:0:67:13::PROP.test is not callable:UNDEFINED not-callable:68:0:68:13::PROP.custom is not callable:UNDEFINED not-callable:137:18:137:45::ClassWithProperty().value is not callable:UNDEFINED -not-callable:190:0:190:16::get_number(10) is not callable:UNDEFINED +not-callable:202:0:202:16::get_number(10) is not callable:UNDEFINED From fd18848e4491fe67b79537a79725655c81fe26ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 17 Dec 2021 22:38:28 +0100 Subject: [PATCH 064/357] Add regression test for issue #5382 (#5550) --- .../regression_node_statement_two.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/functional/r/regression_02/regression_node_statement_two.py diff --git a/tests/functional/r/regression_02/regression_node_statement_two.py b/tests/functional/r/regression_02/regression_node_statement_two.py new file mode 100644 index 0000000000..e2db947915 --- /dev/null +++ b/tests/functional/r/regression_02/regression_node_statement_two.py @@ -0,0 +1,29 @@ +"""Test to see we don't crash on this code in pandas. +See: https://github.com/pandas-dev/pandas/blob/master/pandas/core/indexes/period.py +Reported in https://github.com/PyCQA/pylint/issues/5382 +""" +# pylint: disable=missing-function-docstring, missing-class-docstring, no-self-use, unused-argument +# pylint: disable=too-few-public-methods, no-method-argument, invalid-name + + +def my_decorator(*params): + def decorator(decorated): + return decorated + + return decorator + + +class ClassWithProperty: + def f(): + return "string" + + f.__name__ = "name" + f.__doc__ = "docstring" + + hour = property(f) + + +class ClassWithDecorator: + @my_decorator(ClassWithProperty.hour.fget) + def my_property(self) -> str: + return "a string" From ddd2123e09f97ef91d5745e9790d6f24a5493156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 17 Dec 2021 23:57:45 +0100 Subject: [PATCH 065/357] Fix crash on uninferable decorators on Python 3.6 and 3.7 (#5549) Co-authored-by: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> --- ChangeLog | 2 ++ doc/whatsnew/2.13.rst | 2 ++ pylint/checkers/utils.py | 6 +++++- tests/functional/o/overridden_final_method_regression.py | 6 ++++++ tests/functional/o/overridden_final_method_regression.txt | 1 + 5 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/functional/o/overridden_final_method_regression.py create mode 100644 tests/functional/o/overridden_final_method_regression.txt diff --git a/ChangeLog b/ChangeLog index 68ec3a7829..7b312ff495 100644 --- a/ChangeLog +++ b/ChangeLog @@ -79,6 +79,8 @@ Release date: TBA Closes #5323 +* Fixed crash on uninferable decorators on Python 3.6 and 3.7 + * Add checker ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily. Closes #5460 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index ef72d58552..8f71a4b122 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -73,6 +73,8 @@ Other Changes Closes #5065 +* Fixed crash on uninferable decorators on Python 3.6 and 3.7 + * Fatal errors now emit a score of 0.0 regardless of whether the linted module contained any statements diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 1f85399a7d..cb04c21190 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -876,7 +876,11 @@ def uninferable_final_decorators( except AttributeError: continue elif isinstance(decorator, nodes.Name): - import_node = decorator.lookup(decorator.name)[1][0] + 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: continue diff --git a/tests/functional/o/overridden_final_method_regression.py b/tests/functional/o/overridden_final_method_regression.py new file mode 100644 index 0000000000..a1f72b3807 --- /dev/null +++ b/tests/functional/o/overridden_final_method_regression.py @@ -0,0 +1,6 @@ +"""Test a crash regression for the overridden-final-method checker on uninferable decorators""" + + +@unknown_decorator # [undefined-variable] +def crash_test(): + """A docstring""" diff --git a/tests/functional/o/overridden_final_method_regression.txt b/tests/functional/o/overridden_final_method_regression.txt new file mode 100644 index 0000000000..a8cb239542 --- /dev/null +++ b/tests/functional/o/overridden_final_method_regression.txt @@ -0,0 +1 @@ +undefined-variable:4:1:4:18:crash_test:Undefined variable 'unknown_decorator':UNDEFINED From 42e4b4bf475b4b179430deed6e4c8b38ba7965ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 17 Dec 2021 23:58:20 +0100 Subject: [PATCH 066/357] Add typing to ``_determine_callable`` (#5548) --- pylint/checkers/typecheck.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index c3e76228df..9a8d8cbbe5 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -69,7 +69,7 @@ from collections import deque from collections.abc import Sequence from functools import singledispatch -from typing import Any, Callable, Iterator, List, Optional, Pattern, Tuple +from typing import Any, Callable, Iterator, List, Optional, Pattern, Tuple, Union import astroid import astroid.exceptions @@ -100,6 +100,14 @@ from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option +CallableObjects = Union[ + bases.BoundMethod, + bases.UnboundMethod, + nodes.FunctionDef, + nodes.Lambda, + nodes.ClassDef, +] + STR_FORMAT = {"builtins.str.format"} ASYNCIO_COROUTINE = "asyncio.coroutines.coroutine" BUILTIN_TUPLE = "builtins.tuple" @@ -559,16 +567,24 @@ def _emit_no_member( return True -def _determine_callable(callable_obj): +def _determine_callable( + callable_obj: nodes.NodeNG, +) -> Tuple[CallableObjects, int, str]: + # pylint: disable=fixme + # TODO: The typing of the second return variable is actually Literal[0,1] + # We need typing on astroid.NodeNG.implicit_parameters for this + # TODO: The typing of the third return variable can be narrowed to a Literal + # We need typing on astroid.NodeNG.type for this + # Ordering is important, since BoundMethod is a subclass of UnboundMethod, # and Function inherits Lambda. parameters = 0 if hasattr(callable_obj, "implicit_parameters"): parameters = callable_obj.implicit_parameters() - if isinstance(callable_obj, astroid.BoundMethod): + if isinstance(callable_obj, bases.BoundMethod): # Bound methods have an extra implicit 'self' argument. return callable_obj, parameters, callable_obj.type - if isinstance(callable_obj, astroid.UnboundMethod): + if isinstance(callable_obj, bases.UnboundMethod): return callable_obj, parameters, "unbound method" if isinstance(callable_obj, nodes.FunctionDef): return callable_obj, parameters, callable_obj.type From ffede32040511d342272f8de31bb09c2e7b7ae2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 17 Dec 2021 23:59:57 +0100 Subject: [PATCH 067/357] Fixed extremely long processing of long lines with comma's (#5534) Co-authored-by: Pierre Sassoulas --- ChangeLog | 4 +++ doc/whatsnew/2.13.rst | 4 +++ .../refactoring/refactoring_checker.py | 27 +++++++---------- tests/checkers/unittest_refactoring.py | 30 +++++++++++++++++++ tests/regrtest_data/very_long_line.py | 2 ++ 5 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 tests/regrtest_data/very_long_line.py diff --git a/ChangeLog b/ChangeLog index 7b312ff495..5ea915f92d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,10 @@ Release date: TBA Closes #85, #2615 +* Fixed extremely long processing of long lines with comma's. + + Closes #5483 + * Fixed a false positive for ``assigning-non-slot`` when the slotted class defined ``__setattr__``. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 8f71a4b122..4264bb61c6 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -32,6 +32,10 @@ Extensions Other Changes ============= +* Fixed extremely long processing of long lines with comma's. + + Closes #5483 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 2c687d418d..acaf7191cd 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -66,23 +66,18 @@ def _is_trailing_comma(tokens: List[tokenize.TokenInfo], index: int) -> bool: if token.exact_type != tokenize.COMMA: return False # Must have remaining tokens on the same line such as NEWLINE - left_tokens = itertools.islice(tokens, index + 1, None) - - def same_start_token( - other_token: tokenize.TokenInfo, _token: tokenize.TokenInfo = token - ) -> bool: - return other_token.start[0] == _token.start[0] + left_tokens = list(itertools.islice(tokens, index + 1, None)) + + more_tokens_on_line = False + for remaining_token in left_tokens: + if remaining_token.start[0] == token.start[0]: + more_tokens_on_line = True + # If one of the remaining same line tokens is not NEWLINE or COMMENT + # the comma is not trailing. + if remaining_token.type not in (tokenize.NEWLINE, tokenize.COMMENT): + return False - same_line_remaining_tokens = list( - itertools.takewhile(same_start_token, left_tokens) - ) - # Note: If the newline is tokenize.NEWLINE and not tokenize.NL - # then the newline denotes the end of expression - is_last_element = all( - other_token.type in (tokenize.NEWLINE, tokenize.COMMENT) - for other_token in same_line_remaining_tokens - ) - if not same_line_remaining_tokens or not is_last_element: + if not more_tokens_on_line: return False def get_curline_index_start(): diff --git a/tests/checkers/unittest_refactoring.py b/tests/checkers/unittest_refactoring.py index 3c87a8d333..accc4b0670 100644 --- a/tests/checkers/unittest_refactoring.py +++ b/tests/checkers/unittest_refactoring.py @@ -1,9 +1,31 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +import os +import signal +from contextlib import contextmanager + import astroid +import pytest from pylint.checkers.refactoring import ImplicitBooleanessChecker +from pylint.lint import Run +from pylint.reporters.text import TextReporter + +PARENT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +REGR_DATA = os.path.join(PARENT_DIR, "regrtest_data") + + +@contextmanager +def timeout(timeout_s: float): + def _handle(_signum, _frame): + pytest.fail("Test took too long") + + signal.signal(signal.SIGALRM, _handle) + signal.setitimer(signal.ITIMER_REAL, timeout_s) + yield + signal.setitimer(signal.ITIMER_REAL, 0) + signal.signal(signal.SIGALRM, signal.SIG_DFL) def test_class_tree_detection() -> None: @@ -46,3 +68,11 @@ class ChildClassWithoutBool(ClassWithoutBool): "dict", "object", ] + + +@pytest.mark.skipif(not hasattr(signal, "setitimer"), reason="Assumes POSIX signals") +def test_process_tokens() -> None: + with timeout(8.0): + with pytest.raises(SystemExit) as cm: + Run([os.path.join(REGR_DATA, "very_long_line.py")], reporter=TextReporter()) + assert cm.value.code == 0 diff --git a/tests/regrtest_data/very_long_line.py b/tests/regrtest_data/very_long_line.py new file mode 100644 index 0000000000..82988e8707 --- /dev/null +++ b/tests/regrtest_data/very_long_line.py @@ -0,0 +1,2 @@ +# A list of 1000 tuples +very_long_list = [[None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None], [None, None]] From 57916d57df0c6eb9a91f61bc029675f3433a4294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 18 Dec 2021 00:03:57 +0100 Subject: [PATCH 068/357] Remove unnecessary `if` statement in variable consumption checker (#5531) Co-authored-by: Pierre Sassoulas --- pylint/checkers/variables.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 82b99a805c..315698c58d 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1389,23 +1389,19 @@ def _check_consumer( # class A: # x = lambda attr: f + attr # f = 42 - if isinstance(frame, nodes.ClassDef) and node.name in frame.locals: - if isinstance(node.parent, nodes.Arguments): - if stmt.fromlineno <= defstmt.fromlineno: - # Doing the following is fine: - # class A: - # x = 42 - # y = lambda attr=x: attr - self.add_message( - "used-before-assignment", - args=node.name, - node=node, - ) - else: - self.add_message( - "undefined-variable", args=node.name, node=node - ) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + # We check lineno because doing the following is fine: + # class A: + # x = 42 + # y = lambda attr: x + attr + if ( + isinstance(frame, nodes.ClassDef) + and node.name in frame.locals + and stmt.fromlineno <= defstmt.fromlineno + ): + self.add_message( + "used-before-assignment", args=node.name, node=node + ) + elif current_consumer.scope_type == "lambda": self.add_message("undefined-variable", args=node.name, node=node) return (VariableVisitConsumerAction.CONSUME, found_nodes) From bb9cb4b4428d2e5769737e9ca23e46e85614412a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:10:20 +0100 Subject: [PATCH 069/357] Fix ``used-before-assignment`` for conditional self-referential typing (#5532) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/variables.py | 2 +- .../u/use/used_before_assignment_typing.py | 13 +++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 5ea915f92d..08b017a63d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,11 @@ Release date: TBA Closes #3793 +* Fixed false positive for ``used-before-assignment`` with self-referential type + annotation in conditional statements within class methods. + + Closes #5499 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 4264bb61c6..c5e33bb643 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -60,6 +60,11 @@ Other Changes Closes #3793 +* Fixed false positive for ``used-before-assignment`` with self-referential type + annotation in conditional statements within class methods. + + Closes #5499 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 315698c58d..40fc99b633 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1817,7 +1817,7 @@ def _is_first_level_self_reference( """ if ( node.frame().parent == defstmt - and node.statement(future=True) not in node.frame().body + and node.statement(future=True) == node.frame() ): # Check if used as type annotation # Break but don't emit message if postponed evaluation is enabled diff --git a/tests/functional/u/use/used_before_assignment_typing.py b/tests/functional/u/use/used_before_assignment_typing.py index a9961c8906..33d81356e6 100644 --- a/tests/functional/u/use/used_before_assignment_typing.py +++ b/tests/functional/u/use/used_before_assignment_typing.py @@ -61,3 +61,16 @@ def inner_method(self, other: MyOtherClass) -> bool: def self_referential_optional_within_method(self) -> None: variable: Optional[MyOtherClass] = self print(variable) + + +class MyThirdClass: + """Class to test self referential variable typing within conditionals. + This regressed, reported in: https://github.com/PyCQA/pylint/issues/5499 + """ + + def function(self, var: int) -> None: + if var < 0.5: + _x: MyThirdClass = self + + def other_function(self) -> None: + _x: MyThirdClass = self From bfeca4ccb520a1057dc74bac84c5fc874b06380d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 18 Dec 2021 12:01:15 +0100 Subject: [PATCH 070/357] Check if decorator returns use keyword (``unexpected-keyword-arg``) (#5547) * Improve coverage * Remove unnecessary declaration * Change spacing --- ChangeLog | 4 + doc/whatsnew/2.13.rst | 4 + pylint/checkers/typecheck.py | 44 +++++++ tests/functional/u/unexpected_keyword_arg.py | 118 ++++++++++++++++++ tests/functional/u/unexpected_keyword_arg.txt | 4 + 5 files changed, 174 insertions(+) create mode 100644 tests/functional/u/unexpected_keyword_arg.py create mode 100644 tests/functional/u/unexpected_keyword_arg.txt diff --git a/ChangeLog b/ChangeLog index 08b017a63d..5f9508643c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -107,6 +107,10 @@ Release date: TBA * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* Fixed false positive ``unexpected-keyword-arg`` for decorators. + + Closes #258 + * ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions .. code-block:: python diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index c5e33bb643..357f78b59e 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -101,6 +101,10 @@ Other Changes * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* Fixed false positive ``unexpected-keyword-arg`` for decorators. + + Closes #258 + * ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions .. code-block:: python diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 9a8d8cbbe5..6885e8cbf5 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1457,6 +1457,10 @@ def visit_call(self, node: nodes.Call) -> None: elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass + elif isinstance( + called, nodes.FunctionDef + ) and self._keyword_argument_is_in_all_decorator_returns(called, keyword): + pass elif not overload_function: # Unexpected keyword argument. self.add_message( @@ -1496,6 +1500,46 @@ def visit_call(self, node: nodes.Call) -> None: ): self.add_message("missing-kwoa", node=node, args=(name, callable_name)) + @staticmethod + def _keyword_argument_is_in_all_decorator_returns( + func: nodes.FunctionDef, keyword: str + ) -> bool: + """Check if the keyword argument exists in all signatures of the + return values of all decorators of the function. + """ + if not func.decorators: + return False + + for decorator in func.decorators.nodes: + inferred = safe_infer(decorator) + + # If we can't infer the decorator we assume it satisfies consumes + # the keyword, so we don't raise false positives + if not inferred: + return True + + # We only check arguments of function decorators + if not isinstance(inferred, nodes.FunctionDef): + return False + + for return_value in inferred.infer_call_result(): + # infer_call_result() returns nodes.Const.None for None return values + # so this also catches non-returning decorators + if not isinstance(return_value, nodes.FunctionDef): + return False + + # If the return value uses a kwarg the keyword will be consumed + if return_value.args.kwarg: + continue + + # Check if the keyword is another type of argument + if return_value.args.is_argument(keyword): + continue + + return False + + return True + def _check_invalid_sequence_index(self, subscript: nodes.Subscript): # Look for index operations where the parent is a sequence type. # If the types can be determined, only allow indices to be int, diff --git a/tests/functional/u/unexpected_keyword_arg.py b/tests/functional/u/unexpected_keyword_arg.py new file mode 100644 index 0000000000..e7b648899e --- /dev/null +++ b/tests/functional/u/unexpected_keyword_arg.py @@ -0,0 +1,118 @@ +"""Tests for unexpected-keyword-arg""" +# pylint: disable=undefined-variable, too-few-public-methods, missing-function-docstring, missing-class-docstring + + +def non_param_decorator(func): + """Decorator without a parameter""" + + def new_func(): + func() + + return new_func + + +def param_decorator(func): + """Decorator with a parameter""" + + def new_func(internal_arg=3): + func(junk=internal_arg) + + return new_func + + +def kwargs_decorator(func): + """Decorator with kwargs. + The if ... else makes the double decoration with param_decorator valid. + """ + + def new_func(**kwargs): + if "internal_arg" in kwargs: + func(junk=kwargs["internal_arg"]) + else: + func(junk=kwargs["junk"]) + + return new_func + + +@non_param_decorator +def do_something(junk=None): + """A decorated function. This should not be passed a keyword argument""" + print(junk) + + +do_something(internal_arg=2) # [unexpected-keyword-arg] + + +@param_decorator +def do_something_decorated(junk=None): + """A decorated function. This should be passed a keyword argument""" + print(junk) + + +do_something_decorated(internal_arg=2) + + +@kwargs_decorator +def do_something_decorated_too(junk=None): + """A decorated function. This should be passed a keyword argument""" + print(junk) + + +do_something_decorated_too(internal_arg=2) + + +@non_param_decorator +@kwargs_decorator +def do_something_double_decorated(junk=None): + """A decorated function. This should not be passed a keyword argument. + non_param_decorator will raise an exception if a keyword argument is passed. + """ + print(junk) + + +do_something_double_decorated(internal_arg=2) # [unexpected-keyword-arg] + + +@param_decorator +@kwargs_decorator +def do_something_double_decorated_correct(junk=None): + """A decorated function. This should be passed a keyword argument""" + print(junk) + + +do_something_double_decorated_correct(internal_arg=2) + + +# Test that we don't crash on Class decoration +class DecoratorClass: + pass + + +@DecoratorClass +def crash_test(): + pass + + +crash_test(internal_arg=2) # [unexpected-keyword-arg] + + +# Test that we don't emit a false positive for uninferable decorators +@unknown_decorator +def crash_test_two(): + pass + + +crash_test_two(internal_arg=2) + + +# Test that we don't crash on decorators that don't return anything +def no_return_decorator(func): + print(func) + + +@no_return_decorator +def test_no_return(): + pass + + +test_no_return(internal_arg=2) # [unexpected-keyword-arg] diff --git a/tests/functional/u/unexpected_keyword_arg.txt b/tests/functional/u/unexpected_keyword_arg.txt new file mode 100644 index 0000000000..3cc968e883 --- /dev/null +++ b/tests/functional/u/unexpected_keyword_arg.txt @@ -0,0 +1,4 @@ +unexpected-keyword-arg:43:0:43:28::Unexpected keyword argument 'internal_arg' in function call:UNDEFINED +unexpected-keyword-arg:73:0:73:45::Unexpected keyword argument 'internal_arg' in function call:UNDEFINED +unexpected-keyword-arg:96:0:96:26::Unexpected keyword argument 'internal_arg' in function call:UNDEFINED +unexpected-keyword-arg:118:0:118:30::Unexpected keyword argument 'internal_arg' in function call:UNDEFINED From 4f95e4d1db7bebe62aca1b757a54370b2546a33f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 19 Dec 2021 11:00:23 -0500 Subject: [PATCH 071/357] Update default evaluation formula to match that in default pylintrc (#5553) --- pylint/lint/pylinter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 5c4cb8f60e..ba310483c3 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -294,11 +294,11 @@ def make_options(): "metavar": "", "group": "Reports", "level": 1, - "default": "10.0 - ((float(5 * error + warning + refactor + " + "default": "0 if fatal else 10.0 - ((float(5 * error + warning + refactor + " "convention) / statement) * 10)", "help": "Python expression which should return a score less " - "than or equal to 10. You have access to the variables " - "'error', 'warning', 'refactor', and 'convention' which " + "than or equal to 10. You have access to the variables 'fatal', " + "'error', 'warning', 'refactor', 'convention', and 'info' which " "contain the number of messages in each category, as well as " "'statement' which is the total number of statements " "analyzed. This score is used by the global " From cdb57a8bfef91df7771c339b95931184f81ef940 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 20 Dec 2021 10:23:43 +0100 Subject: [PATCH 072/357] Ignore files with name that starts like an emacs lock files (#5554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix 'path' shadowing variable from outer scope * Ignore file that starts like emacs's file lock Closes #367 Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 7 +++++++ doc/whatsnew/2.13.rst | 8 ++++++++ pylint/lint/expand_modules.py | 4 ++-- pylint/lint/pylinter.py | 5 +++-- pylint/testutils/configuration_test.py | 13 +++++++++++-- pylint/utils/linterstats.py | 2 +- tests/functional/e/.#emacs_file_lock.py | 4 ++++ tests/functional/e/.#emacs_file_lock_by_conf.py | 3 +++ tests/functional/e/.#emacs_file_lock_by_conf.rc | 2 ++ .../e/.#emacs_file_lock_redefined_conf.py | 3 +++ .../e/.#emacs_file_lock_redefined_conf.rc | 2 ++ .../e/.#emacs_file_lock_redefined_conf.txt | 1 + 12 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 tests/functional/e/.#emacs_file_lock.py create mode 100644 tests/functional/e/.#emacs_file_lock_by_conf.py create mode 100644 tests/functional/e/.#emacs_file_lock_by_conf.rc create mode 100644 tests/functional/e/.#emacs_file_lock_redefined_conf.py create mode 100644 tests/functional/e/.#emacs_file_lock_redefined_conf.rc create mode 100644 tests/functional/e/.#emacs_file_lock_redefined_conf.txt diff --git a/ChangeLog b/ChangeLog index 5f9508643c..128ae03af3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -30,6 +30,13 @@ Release date: TBA Closes #5499 +* By default, pylint does no longer take files starting with ``.#`` into account. Those are + considered `emacs file locks`. See + https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Locks.html. + This behavior can be reverted by redefining the ``ignore-patterns`` option. + + Closes #367 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 357f78b59e..5e5d73493a 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -32,6 +32,14 @@ Extensions Other Changes ============= +* By default, pylint does no longer take files starting with ``.#`` into account. Those are + considered `emacs file locks`_. This behavior can be reverted by redefining the + ``ignore-patterns`` option. + + Closes #367 + +.. _`emacs file locks`: https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Locks.html + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 8eaf98847b..7a0dc7004f 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -8,8 +8,8 @@ def _modpath_from_file(filename, is_namespace, path=None): - def _is_package_cb(path, parts): - return modutils.check_modpath_has_init(path, parts) or is_namespace + def _is_package_cb(inner_path, parts): + return modutils.check_modpath_has_init(inner_path, parts) or is_namespace return modutils.modpath_from_file_with_callback( filename, path=path, is_package_cb=_is_package_cb diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index ba310483c3..bfeec152d0 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -223,9 +223,10 @@ def make_options(): "type": "regexp_csv", "metavar": "[,...]", "dest": "black_list_re", - "default": (), + "default": (r"^\.#",), "help": "Files or directories matching the regex patterns are" - " skipped. The regex matches against base names, not paths.", + " skipped. The regex matches against base names, not paths. The default value " + "ignores emacs file locks", }, ), ( diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index 809fd4eb55..d878fae76b 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -5,11 +5,13 @@ import copy import json import logging +import re import unittest from pathlib import Path from typing import Any, Dict, List, Tuple, Union from unittest.mock import Mock +from pylint.constants import PY38_PLUS from pylint.lint import Run # We use Any in this typing because the configuration contains real objects and constants @@ -18,6 +20,13 @@ PylintConfiguration = Dict[str, ConfigurationValue] +if not PY38_PLUS: + # We need to deepcopy a compiled regex pattern + # In python 3.6 and 3.7 this require a hack + # See https://stackoverflow.com/a/56935186 + copy._deepcopy_dispatch[type(re.compile(""))] = lambda r, _: r # type: ignore[attr-defined] + + def get_expected_or_default( tested_configuration_file: Union[str, Path], suffix: str, @@ -31,7 +40,7 @@ def get_expected_or_default( with open(expected_result_path, encoding="utf8") as f: expected = f.read() # logging is helpful to realize your file is not taken into - # account after a misspell of the file name. The output of the + # account after a misspelling of the file name. The output of the # program is checked during the test so printing messes with the result. logging.info("%s exists.", expected_result_path) else: @@ -139,7 +148,7 @@ def run_using_a_configuration_file( # would not be accessible outside the `with` block. with unittest.mock.patch("sys.exit") as mocked_exit: # Do not actually run checks, that could be slow. We don't mock - # `Pylinter.check`: it calls `Pylinter.initialize` which is + # `PyLinter.check`: it calls `PyLinter.initialize` which is # needed to properly set up messages inclusion/exclusion # in `_msg_states`, used by `is_message_enabled`. check = "pylint.lint.pylinter.check_parallel" diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py index cb20853522..4a05c93df0 100644 --- a/pylint/utils/linterstats.py +++ b/pylint/utils/linterstats.py @@ -155,7 +155,7 @@ def __str__(self) -> str: {self.percent_duplicated_lines}""" def init_single_module(self, module_name: str) -> None: - """Use through Pylinter.set_current_module so Pyliner.current_name is consistent.""" + """Use through PyLinter.set_current_module so PyLinter.current_name is consistent.""" self.by_module[module_name] = ModuleStats( convention=0, error=0, fatal=0, info=0, refactor=0, statement=0, warning=0 ) diff --git a/tests/functional/e/.#emacs_file_lock.py b/tests/functional/e/.#emacs_file_lock.py new file mode 100644 index 0000000000..b2674f4147 --- /dev/null +++ b/tests/functional/e/.#emacs_file_lock.py @@ -0,0 +1,4 @@ +# The name is invalid, but we should not analyse this file +# Because its filename reseambles an emacs file lock ignored by default +# https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Locks.html +# See https://github.com/PyCQA/pylint/issues/367 diff --git a/tests/functional/e/.#emacs_file_lock_by_conf.py b/tests/functional/e/.#emacs_file_lock_by_conf.py new file mode 100644 index 0000000000..151168cc86 --- /dev/null +++ b/tests/functional/e/.#emacs_file_lock_by_conf.py @@ -0,0 +1,3 @@ +# The name is invalid, but we should not analyse this file +# Because it resembles an emacs file lock and is ignored by the configuration +# See https://github.com/PyCQA/pylint/issues/367 diff --git a/tests/functional/e/.#emacs_file_lock_by_conf.rc b/tests/functional/e/.#emacs_file_lock_by_conf.rc new file mode 100644 index 0000000000..4140338cd1 --- /dev/null +++ b/tests/functional/e/.#emacs_file_lock_by_conf.rc @@ -0,0 +1,2 @@ +[MASTER] +ignore-patterns=^\.# diff --git a/tests/functional/e/.#emacs_file_lock_redefined_conf.py b/tests/functional/e/.#emacs_file_lock_redefined_conf.py new file mode 100644 index 0000000000..4b26dee73a --- /dev/null +++ b/tests/functional/e/.#emacs_file_lock_redefined_conf.py @@ -0,0 +1,3 @@ +# [invalid-name] +# The name is invalid and we should analyse this file +# as ignore-patterns is redefined in the configuration diff --git a/tests/functional/e/.#emacs_file_lock_redefined_conf.rc b/tests/functional/e/.#emacs_file_lock_redefined_conf.rc new file mode 100644 index 0000000000..76cd083fd3 --- /dev/null +++ b/tests/functional/e/.#emacs_file_lock_redefined_conf.rc @@ -0,0 +1,2 @@ +[MASTER] +ignore-patterns="" diff --git a/tests/functional/e/.#emacs_file_lock_redefined_conf.txt b/tests/functional/e/.#emacs_file_lock_redefined_conf.txt new file mode 100644 index 0000000000..52050dcc84 --- /dev/null +++ b/tests/functional/e/.#emacs_file_lock_redefined_conf.txt @@ -0,0 +1 @@ +invalid-name:1:0:None:None::Module name "#emacs_file_lock_redefined_conf" doesn't conform to snake_case naming style:HIGH From 94ce3fd4743901aeb89743699da74c9307dc0ec1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 20 Dec 2021 10:29:46 +0100 Subject: [PATCH 073/357] Fix unexpected types used in 'get_global_option' (#5555) --- tests/lint/unittest_expand_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 4d679ad641..4d2cc812eb 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -113,7 +113,7 @@ def test_expand_modules(self, files_or_modules, expected): files_or_modules, ignore_list, ignore_list_re, - get_global_option(self, "ignore-paths"), + get_global_option(self.checker, "ignore-paths"), ) modules.sort(key=lambda d: d["name"]) assert modules == expected From 2145868fcdadc656e6fc151afecea57e513ed77a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 14:19:27 +0100 Subject: [PATCH 074/357] Bump actions/upload-artifact from 2.3.0 to 2.3.1 (#5559) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2.3.0...v2.3.1) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e560de31cf..96f06bbfd0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -220,7 +220,7 @@ jobs: . venv/bin/activate pytest --benchmark-disable --cov --cov-report= tests/ - name: Upload coverage artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: coverage-${{ matrix.python-version }} path: .coverage @@ -313,7 +313,7 @@ jobs: run: >- echo "::set-output name=datetime::"$(date "+%Y%m%d_%H%M") - name: Upload benchmark artifact - uses: actions/upload-artifact@v2.3.0 + uses: actions/upload-artifact@v2.3.1 with: name: benchmark-${{ runner.os }}-${{ matrix.python-version }}_${{ From 51e72bb88036450620cca60f38ba0c969b67d851 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 14:19:40 +0100 Subject: [PATCH 075/357] Bump sphinx from 4.3.1 to 4.3.2 (#5560) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.3.1 to 4.3.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.3.1...v4.3.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 586c5d9672..943b5e598e 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ -Sphinx==4.3.1 +Sphinx==4.3.2 python-docs-theme==2021.11.1 -e . From 9d078690e3e7f30900272c6f7d3e79ef7699b2a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 14:29:52 +0100 Subject: [PATCH 076/357] Update tbump requirement from ~=6.6.0 to ~=6.6.1 (#5561) Updates the requirements on [tbump](https://github.com/dmerejkowsky/tbump) to permit the latest version. - [Release notes](https://github.com/dmerejkowsky/tbump/releases) - [Changelog](https://github.com/dmerejkowsky/tbump/blob/main/Changelog.rst) - [Commits](https://github.com/dmerejkowsky/tbump/compare/v6.6.0...v6.6.1) --- updated-dependencies: - dependency-name: tbump dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1fd84ce7d4..84cb74280f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -3,7 +3,7 @@ coveralls~=3.3 coverage~=6.2 pre-commit~=2.16;python_full_version>="3.6.2" -tbump~=6.6.0 +tbump~=6.6.1 pyenchant~=3.2 pytest-cov~=3.0 pytest-profiling~=1.7 From f8b2c3daae4bec93f0ea892980efe58b363befe4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 14:30:04 +0100 Subject: [PATCH 077/357] Bump flake8-typing-imports from 1.11.0 to 1.12.0 (#5562) Bumps [flake8-typing-imports](https://github.com/asottile/flake8-typing-imports) from 1.11.0 to 1.12.0. - [Release notes](https://github.com/asottile/flake8-typing-imports/releases) - [Commits](https://github.com/asottile/flake8-typing-imports/compare/v1.11.0...v1.12.0) --- updated-dependencies: - dependency-name: flake8-typing-imports dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test_pre_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 0823f177c6..e942387712 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -2,6 +2,6 @@ # in .pre-commit-config.yaml black==21.12b0 flake8==4.0.1 -flake8-typing-imports==1.11.0 +flake8-typing-imports==1.12.0 isort==5.10.1 mypy==0.920 From 4077ddb5d462e2c0e70c6756e220c34fd6d7224e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:14:35 +0100 Subject: [PATCH 078/357] Add requirement files to list of files to run primer against (#5566) --- .github/workflows/primer-test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index d4f7b15048..08e9d125d4 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -9,6 +9,7 @@ on: paths: - "pylint/**" - "tests/primer/**" + - "requirements*" env: CACHE_VERSION: 3 From 10798c97a455254f13dbe5d68a535337126d10ec Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 20 Dec 2021 22:08:58 +0100 Subject: [PATCH 079/357] Revert "Update tbump requirement from ~=6.6.0 to ~=6.6.1 (#5561)" (#5567) This reverts commit 9d078690e3e7f30900272c6f7d3e79ef7699b2a2. --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 84cb74280f..1fd84ce7d4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -3,7 +3,7 @@ coveralls~=3.3 coverage~=6.2 pre-commit~=2.16;python_full_version>="3.6.2" -tbump~=6.6.1 +tbump~=6.6.0 pyenchant~=3.2 pytest-cov~=3.0 pytest-profiling~=1.7 From 0a042a8a42b7ade556b4eaf028bb2bc073227918 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 20 Dec 2021 15:33:31 +0100 Subject: [PATCH 080/357] Fix some typoes before adding typing for checker registering --- examples/custom.py | 10 +++------- pylint/checkers/classes/special_methods_checker.py | 2 +- .../refactoring/implicit_booleaness_checker.py | 2 +- pylint/interfaces.py | 14 +++++--------- tests/test_check_parallel.py | 8 ++++---- 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index bd9797e2ad..c7429f629f 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -7,7 +7,7 @@ # This is our checker class. # Checkers should always inherit from `BaseChecker`. class MyAstroidChecker(BaseChecker): - """Add class member attributes to the class locals dictionary.""" + """Add class member attributes to the class local's dictionary.""" # This class variable defines the type of checker that we are implementing. # In this case, we are implementing an AST checker. @@ -31,8 +31,7 @@ class MyAstroidChecker(BaseChecker): options = ( # Each option definition has a name which is used on the command line # and in config files, and a dictionary of arguments - # (similar to those to those to - # argparse.ArgumentParser.add_argument). + # (similar to argparse.ArgumentParser.add_argument). ( "store-locals-indicator", { @@ -48,10 +47,7 @@ class MyAstroidChecker(BaseChecker): def visit_call(self, node: nodes.Call) -> None: """Called when a :class:`.nodes.Call` node is visited. - See :mod:`astroid` for the description of available nodes. - - :param node: The node to check. - """ + See :mod:`astroid` for the description of available nodes.""" if not ( isinstance(node.func, nodes.Attribute) and isinstance(node.func.expr, nodes.Name) diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py index adfb7b3f17..51c52b3ae4 100644 --- a/pylint/checkers/classes/special_methods_checker.py +++ b/pylint/checkers/classes/special_methods_checker.py @@ -25,7 +25,7 @@ def _safe_infer_call_result(node, caller, context=None): Safely infer the return value of a function. Returns None if inference failed or if there is some ambiguity (more than - one node has been inferred). Otherwise returns inferred value. + one node has been inferred). Otherwise, returns inferred value. """ try: inferit = node.infer_call_result(caller, context=context) diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index 02e5d82571..29277e3a23 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -149,7 +149,7 @@ def _check_use_implicit_booleaness_not_comparison( node.left ) or utils.is_empty_dict_literal(node.left) - # Check both left hand side and right hand side for literals + # Check both left-hand side and right-hand side for literals for operator, comparator in node.ops: is_right_empty_literal = utils.is_base_container( comparator diff --git a/pylint/interfaces.py b/pylint/interfaces.py index eef47449ca..8fd61589ff 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -64,9 +64,7 @@ def implements( obj: "BaseChecker", interface: Union[Type["Interface"], Tuple[Type["Interface"], ...]], ) -> bool: - """Return whether the given object (maybe an instance or class) implements - the interface. - """ + """Does the given object (maybe an instance or class) implements the interface.""" kimplements = getattr(obj, "__implements__", ()) if not isinstance(kimplements, (list, tuple)): kimplements = (kimplements,) @@ -74,9 +72,7 @@ def implements( class IChecker(Interface): - """This is a base interface, not designed to be used elsewhere than for - sub interfaces definition. - """ + """Base interface, not to be used elsewhere than for sub interfaces definition.""" def open(self): """called before visiting project (i.e. set of modules)""" @@ -86,12 +82,12 @@ def close(self): class IRawChecker(IChecker): - """interface for checker which need to parse the raw file""" + """Interface for checker which need to parse the raw file""" def process_module(self, node: nodes.Module) -> None: """process a module - the module's content is accessible via ``astroid.stream`` + The module's content is accessible via ``astroid.stream`` """ @@ -101,7 +97,7 @@ class ITokenChecker(IChecker): def process_tokens(self, tokens): """Process a module. - tokens is a list of all source code tokens in the file. + Tokens is a list of all source code tokens in the file. """ diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index d1b5a64893..01fc352dc4 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -82,7 +82,7 @@ class ParallelTestChecker(BaseChecker, MapReduceMixin): On non-parallel builds: it works on all the files in a single run. - On parallel builds: lint.parallel calls ``open`` once per file. + On parallel builds: ``lint.parallel`` calls ``open`` once per file. So if files are treated by separate processes, no messages will be raised from the individual process, all messages will be raised @@ -274,7 +274,7 @@ def test_sequential_checkers_work(self) -> None: """Tests original basic types of checker works as expected in -jN This means that a sequential checker should return the same data for a given - file-stream irrespective of whether its run in -j1 or -jN + file-stream irrespective of whether it's run in -j1 or -jN """ linter = PyLinter(reporter=Reporter()) @@ -443,7 +443,7 @@ def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers) file_infos = _gen_file_datas(num_files) - # Loop for single-proc and mult-proc so we can ensure the same linter-config + # Loop for single-proc and mult-proc, so we can ensure the same linter-config for do_single_proc in range(2): linter = PyLinter(reporter=Reporter()) @@ -515,7 +515,7 @@ def test_map_reduce(self, num_files, num_jobs, num_checkers): # with the number of files. file_infos = _gen_file_datas(num_files) - # Loop for single-proc and mult-proc so we can ensure the same linter-config + # Loop for single-proc and mult-proc, so we can ensure the same linter-config for do_single_proc in range(2): linter = PyLinter(reporter=Reporter()) From 86c7fd95b7a85b6eb6de4d0eb9b885ee620496bf Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 20 Dec 2021 15:36:02 +0100 Subject: [PATCH 081/357] Fix the typing for BroadTryClause.visit_tryexcept We're calling it from visit_tryfinally it's not strictly the expected for the visitor pattern --- pylint/extensions/broad_try_clause.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylint/extensions/broad_try_clause.py b/pylint/extensions/broad_try_clause.py index 4ccdf258d4..b460850c23 100644 --- a/pylint/extensions/broad_try_clause.py +++ b/pylint/extensions/broad_try_clause.py @@ -10,6 +10,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE """Looks for try/except statements with too much code in the try clause.""" +from typing import Union from astroid import nodes @@ -59,7 +60,7 @@ def _count_statements(self, try_node): return statement_count - def visit_tryexcept(self, node: nodes.TryExcept) -> None: + def visit_tryexcept(self, node: Union[nodes.TryExcept, nodes.TryFinally]) -> None: try_clause_statements = self._count_statements(node) if try_clause_statements > self.config.max_try_statements: msg = f"try clause contains {try_clause_statements} statements, expected at most {self.config.max_try_statements}" From f9c65555a1f7846fbde577d0b65a97e315223426 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 20 Dec 2021 15:36:54 +0100 Subject: [PATCH 082/357] Remove unneccessary use of parenthesis in add_message --- pylint/extensions/docparams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index a0c66f16c2..05c2620123 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -589,7 +589,7 @@ class constructor. ): self.add_message( "missing-any-param-doc", - args=(warning_node.name), + args=warning_node.name, node=warning_node, ) else: From feabd3d58288fe6df819a70d848e251c83ecf0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 09:40:49 +0100 Subject: [PATCH 083/357] Add missing settings to pylintrc (#5556) Co-authored-by: Pierre Sassoulas --- pylintrc | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 203 insertions(+), 17 deletions(-) diff --git a/pylintrc b/pylintrc index 9c82edeb4f..8dec450744 100644 --- a/pylintrc +++ b/pylintrc @@ -11,6 +11,14 @@ # paths. ignore=CVS +# 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. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns=^\.# + # Pickle collected data for later comparisons. persistent=yes @@ -27,7 +35,8 @@ load-plugins= pylint.extensions.redefined_variable_type, pylint.extensions.comparison_placement, -# Use multiple processes to speed up Pylint. +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. jobs=1 # When enabled, pylint would attempt to guess common misconfiguration and emit @@ -46,6 +55,19 @@ extension-pkg-allow-list= # Minimum supported python version py-version = 3.6.2 +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + [MESSAGES CONTROL] @@ -55,7 +77,8 @@ confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. enable= use-symbolic-message-instead, useless-suppression, @@ -89,11 +112,6 @@ disable= # mypackage.mymodule.MyReporterClass. output-format=text -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - # Tells whether to display a full report or only the messages reports=no @@ -108,6 +126,9 @@ evaluation=0 if fatal else 10.0 - ((float(5 * error + warning + refactor + conve # used to format the message information. See doc for all details #msg-template= +# Activate the evaluation score. +score=yes + [LOGGING] @@ -115,12 +136,19 @@ evaluation=0 if fatal else 10.0 - ((float(5 * error + warning + refactor + conve # function parameter format logging-modules=logging +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO +# Regular expression of note tags to take in consideration. +#notes-rgx= + [SIMILARITIES] @@ -136,6 +164,9 @@ ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no +# Signatures are removed from the similarity computation +ignore-signatures=no + [VARIABLES] @@ -154,6 +185,20 @@ additional-builtins= # name must start or end with one of those strings. callbacks=cb_,_cb +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.* + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + [FORMAT] @@ -167,6 +212,10 @@ ignore-long-lines=^\s*(# )??$ # else. single-line-if-stmt=no +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + # List of optional constructs for which whitespace checking is disabled no-space-check=trailing-comma,dict-separator @@ -189,9 +238,17 @@ expected-line-ending-format= # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= @@ -199,60 +256,97 @@ name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=no +# Naming style matching correct function names. +function-naming-style=snake_case + # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct variable names. +variable-naming-style=snake_case + # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# Naming style matching correct attribute names. +attr-naming-style=snake_case + # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,}$ # Naming hint for attribute names attr-name-hint=[a-z_][a-z0-9_]{2,}$ +# Naming style matching correct argument names. +argument-naming-style=snake_case + # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for argument names argument-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ +# Naming style matching correct class names. +class-naming-style=PascalCase + # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ +# Naming style matching correct module names. +module-naming-style=snake_case + # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# Naming style matching correct method names. +method-naming-style=snake_case + # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,}$ @@ -277,16 +371,19 @@ property-classes=abc.abstractproperty # A class is considered mixin if its name matches the mixin-class-rgx option. ignore-mixin-members=yes -# Regex pattern to define which classes are considered mixins. +# Regex pattern to define which classes are considered mixins if ignore-mixin- +# members is set to 'yes' mixin-class-rgx=.*MixIn # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis) +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. ignored-modules= -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. ignored-classes=SQLObject, optparse.Values, thread._local, _thread._local # List of members which are set dynamically and missed by pylint inference @@ -298,6 +395,29 @@ generated-members=REQUEST,acl_users,aq_parent # contextlib.contextmanager. contextmanager-decorators=contextlib.contextmanager +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 [SPELLING] @@ -308,6 +428,10 @@ spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= @@ -315,16 +439,15 @@ spelling-private-dict-file= # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + [DESIGN] # Maximum number of arguments for function / method max-args=10 -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - # Maximum number of locals for function / method body max-locals=25 @@ -352,6 +475,9 @@ min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=25 +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + # List of regular expressions of class ancestor names to # ignore when counting public methods (see R0903). exclude-too-few-public-methods= @@ -371,9 +497,23 @@ valid-metaclass-classmethod-first-arg=mcs # warning. exclude-protected=_asdict,_fields,_replace,_source,_make +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no [IMPORTS] +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec @@ -389,6 +529,16 @@ ext-import-graph= # not be disabled) int-import-graph= +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + [EXCEPTIONS] @@ -399,11 +549,47 @@ overgeneral-exceptions=Exception [TYPING] -# Annotations are used exclusively for type checking +# Set to ``no`` if the app / library does **NOT** need to support runtime +# introspection of type annotations. If you use type annotations +# **exclusively** for type checking of an application, you're probably fine. +# For libraries, evaluate if some users what to access the type hints at +# runtime first, e.g., through ``typing.get_type_hints``. Applies to Python +# versions 3.7 - 3.9 runtime-typing = no -[pylint.DEPRECATED_BUILTINS] +[DEPRECATED_BUILTINS] # List of builtins function names that should not be used, separated by a comma bad-functions=map,input + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[CODE_STYLE] + +# Max line length for which to sill emit suggestions. Used to prevent optional +# suggestions which would get split by a code formatter (e.g., black). Will +# default to the setting for ``max-line-length``. +#max-line-length-suggestions= From ca06014c8de155d76b1da7eebb4be877cba40007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 12:57:44 +0100 Subject: [PATCH 084/357] Add typing to some functions in ``testutils`` (#5573) --- pylint/testutils/configuration_test.py | 2 +- pylint/testutils/functional/test_file.py | 4 ++-- pylint/testutils/get_test_info.py | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index d878fae76b..b0fd0b1408 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -30,7 +30,7 @@ def get_expected_or_default( tested_configuration_file: Union[str, Path], suffix: str, - default: ConfigurationValue, + default: str, ) -> str: """Return the expected value from the file if it exists, or the given default.""" expected = default diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index b25fe509b9..bf2469e498 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -4,7 +4,7 @@ import configparser import sys from os.path import basename, exists, join -from typing import List, Tuple +from typing import Callable, Dict, List, Tuple, Union def parse_python_version(ver_str: str) -> Tuple[int, ...]: @@ -45,7 +45,7 @@ class TestFileOptions(TypedDict): class FunctionalTestFile: """A single functional test case file with options.""" - _CONVERTERS = { + _CONVERTERS: Dict[str, Callable[[str], Union[Tuple[int, ...], List[str]]]] = { "min_pyver": parse_python_version, "max_pyver": parse_python_version, "min_pyver_end_position": parse_python_version, diff --git a/pylint/testutils/get_test_info.py b/pylint/testutils/get_test_info.py index 3b63a8e555..9900c7326c 100644 --- a/pylint/testutils/get_test_info.py +++ b/pylint/testutils/get_test_info.py @@ -3,11 +3,14 @@ from glob import glob from os.path import basename, join, splitext +from typing import List, Tuple from pylint.testutils.constants import SYS_VERS_STR -def _get_tests_info(input_dir, msg_dir, prefix, suffix): +def _get_tests_info( + input_dir: str, msg_dir: str, prefix: str, suffix: str +) -> List[Tuple[str, str]]: """get python input examples and output messages We use following conventions for input files and messages: From 2a69387352bd1c941759faaa22458de3609b7627 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 21 Dec 2021 08:58:15 -0500 Subject: [PATCH 085/357] Fix #5557: Don't emit `comparison-with-callable` if the callable raises (#5563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix #5557: Don't emit `comparison-with-callable` if the callable raises Typing constants such as `typing.Any` raise when called. Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/base.py | 20 +++++++++++-------- .../functional/c/comparison_with_callable.py | 11 ++++++++++ ...mparison_with_callable_typing_constants.py | 18 +++++++++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 tests/functional/c/comparison_with_callable_typing_constants.py diff --git a/ChangeLog b/ChangeLog index 128ae03af3..f1b8c1aab1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -63,6 +63,11 @@ Release date: TBA Closes #3675 +* Fix ``comparison-with-callable`` false positive for callables that raise, such + as typing constants. + + Closes #5557 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 5e5d73493a..05d4e495f5 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -99,6 +99,11 @@ Other Changes * ``fatal`` was added to the variables permitted in score evaluation expressions. +* Fix ``comparison-with-callable`` false positive for callables that raise, such + as typing constants. + + Closes #5557 + * The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 9d61cf10f6..46eb0d17dd 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -2511,14 +2511,18 @@ def _check_callable_comparison(self, node): left_operand, right_operand = node.left, node.ops[0][1] # this message should be emitted only when there is comparison of bare callable # with non bare callable. - if ( - sum( - 1 - for operand in (left_operand, right_operand) - if isinstance(utils.safe_infer(operand), bare_callables) - ) - == 1 - ): + number_of_bare_callables = 0 + for operand in left_operand, right_operand: + inferred = utils.safe_infer(operand) + # Ignore callables that raise, as well as typing constants + # implemented as functions (that raise via their decorator) + if ( + isinstance(inferred, bare_callables) + and "typing._SpecialForm" not in inferred.decoratornames() + and not any(isinstance(x, nodes.Raise) for x in inferred.body) + ): + number_of_bare_callables += 1 + if number_of_bare_callables == 1: self.add_message("comparison-with-callable", node=node) @utils.check_messages( diff --git a/tests/functional/c/comparison_with_callable.py b/tests/functional/c/comparison_with_callable.py index fb02729d84..2afe430356 100644 --- a/tests/functional/c/comparison_with_callable.py +++ b/tests/functional/c/comparison_with_callable.py @@ -58,3 +58,14 @@ def fake_property(self, prop): b = 786 if a == b: pass + + +def eventually_raise(): + print() + raise Exception + + +if a == eventually_raise: + # Does not emit comparison-with-callable because the + # function (eventually) raises + pass diff --git a/tests/functional/c/comparison_with_callable_typing_constants.py b/tests/functional/c/comparison_with_callable_typing_constants.py new file mode 100644 index 0000000000..70aa9763f5 --- /dev/null +++ b/tests/functional/c/comparison_with_callable_typing_constants.py @@ -0,0 +1,18 @@ +"""Typing constants are actually implemented as functions, but they +raise when called, so Pylint uses that to avoid false positives for +comparison-with-callable. +""" +from typing import Any, Optional + + +def check_any(type_) -> bool: + """See https://github.com/PyCQA/pylint/issues/5557""" + return type_ == Any + + +def check_optional(type_) -> bool: + """ + Unlike Any, Optional does not raise in its body. + It raises via its decorator: typing._SpecialForm.__call__() + """ + return type_ == Optional From 52576b7552b68593a3ea28e20cd3751ccdc106d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 14:58:52 +0100 Subject: [PATCH 086/357] Fix ``used-before-assignment`` for assignment expressions in lambda (#5530) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/variables.py | 4 ---- .../functional/u/undefined/undefined_variable_py38.py | 11 +++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index f1b8c1aab1..77e77dfe56 100644 --- a/ChangeLog +++ b/ChangeLog @@ -50,6 +50,11 @@ Release date: TBA * ``used-before-assignment`` now checks names in try blocks. +* Fixed false positive with ``used-before-assignment`` for assignment expressions + in lambda statements. + + Closes #5360, #3877 + * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 05d4e495f5..219aec5124 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -86,6 +86,11 @@ Other Changes * ``used-before-assignment`` now checks names in try blocks. +* Fixed false positive with ``used-before-assignment`` for assignment expressions + in lambda statements. + + Closes #5360, #3877 + * Require Python ``3.6.2`` to run pylint. Closes #5065 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 40fc99b633..8e2f868391 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1402,10 +1402,6 @@ def _check_consumer( "used-before-assignment", args=node.name, node=node ) - elif current_consumer.scope_type == "lambda": - self.add_message("undefined-variable", args=node.name, node=node) - return (VariableVisitConsumerAction.CONSUME, found_nodes) - elif self._is_only_type_assignment(node, defstmt): self.add_message("undefined-variable", args=node.name, node=node) return (VariableVisitConsumerAction.CONSUME, found_nodes) diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index 3c00bfd854..81fa19f527 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -97,3 +97,14 @@ def no_parameters_in_function_default() -> None: COMPREHENSION_FIVE = {i: (else_assign_2 := i) if False else 0 for i in range(10)} print(else_assign_2) # [undefined-variable] + + +# Tests for assignment expressions in lambda statements + +things = [] +sorted_things = sorted( + things, + key=lambda thing: x_0 + if (x_0 := thing.this_value) < (x_1 := thing.that_value) + else x_1, +) From d98cef76aac425be62a71fb689c5b510b429cef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 15:56:14 +0100 Subject: [PATCH 087/357] Add typing to file visiting attributes of ``PyLinter`` (#5576) --- pylint/lint/pylinter.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index bfeec152d0..fec268f749 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -540,11 +540,13 @@ def __init__( self.msgs_store = MessageDefinitionStore() self._checkers = collections.defaultdict(list) self._pragma_lineno = {} - self._ignore_file = False - # visit variables + + # Attributes related to visiting files self.file_state = FileState() self.current_name: Optional[str] = None - self.current_file = None + self.current_file: Optional[str] = None + self._ignore_file = False + self.stats = LinterStats() self.fail_on_symbols = [] # init options @@ -1491,7 +1493,7 @@ def _add_one_message( message_definition.msgid, message_definition.symbol, MessageLocationTuple( - abspath, + abspath or "", path, module or "", obj, From 797d99b5e85549aa60b3edea039d79f5a4040662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 16:10:58 +0100 Subject: [PATCH 088/357] Add typing to checker and plugin attributes of ``PyLinter`` (#5574) --- pylint/lint/pylinter.py | 26 ++++++++++++++++++---- pylint/reporters/reports_handler_mix_in.py | 8 +++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index fec268f749..d89da0b933 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -11,7 +11,19 @@ import traceback import warnings from io import TextIOWrapper -from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Type, Union +from typing import ( + Any, + DefaultDict, + Dict, + Iterable, + Iterator, + List, + Optional, + Sequence, + Set, + Type, + Union, +) import astroid from astroid import AstroidError, nodes @@ -537,8 +549,15 @@ def __init__( self._reporters: Dict[str, Type[reporters.BaseReporter]] = {} """Dictionary of possible but non-initialized reporters""" + # Attributes for checkers and plugins + self._checkers: DefaultDict[ + str, List[checkers.BaseChecker] + ] = collections.defaultdict(list) + """Dictionary of registered and initialized checkers""" + self._dynamic_plugins: Set[str] = set() + """Set of loaded plugin names""" + self.msgs_store = MessageDefinitionStore() - self._checkers = collections.defaultdict(list) self._pragma_lineno = {} # Attributes related to visiting files @@ -585,7 +604,6 @@ def __init__( ("RP0003", "Messages", report_messages_stats), ) self.register_checker(self) - self._dynamic_plugins = set() self._error_mode = False self.load_provider_defaults() @@ -722,7 +740,7 @@ def report_order(self): # checkers manipulation methods ############################################ - def register_checker(self, checker): + def register_checker(self, checker: checkers.BaseChecker) -> None: """register a new checker checker is an object implementing IRawChecker or / and IAstroidChecker diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index 27af31027a..a71e1bc7b7 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -4,15 +4,15 @@ import collections from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Tuple +from pylint import checkers from pylint.exceptions import EmptyReportError -from pylint.interfaces import IChecker from pylint.reporters.ureports.nodes import Section from pylint.utils import LinterStats if TYPE_CHECKING: from pylint.lint.pylinter import PyLinter -ReportsDict = DefaultDict[IChecker, List[Tuple[str, str, Callable]]] +ReportsDict = DefaultDict[checkers.BaseChecker, List[Tuple[str, str, Callable]]] class ReportsHandlerMixIn: @@ -24,12 +24,12 @@ def __init__(self) -> None: self._reports: ReportsDict = collections.defaultdict(list) self._reports_state: Dict[str, bool] = {} - def report_order(self) -> List[IChecker]: + def report_order(self) -> List[checkers.BaseChecker]: """Return a list of reporters""" return list(self._reports) def register_report( - self, reportid: str, r_title: str, r_cb: Callable, checker: IChecker + self, reportid: str, r_title: str, r_cb: Callable, checker: checkers.BaseChecker ) -> None: """register a report From 996da9ef9b6ce3cdefaa35afbb2550c5f795c635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:42:24 +0100 Subject: [PATCH 089/357] Rename ``init_linter`` fixture to ``initialized_linter``, add filename (#5581) --- tests/lint/unittest_lint.py | 40 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index f504efcf63..582e9faa8d 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -249,9 +249,9 @@ def reporter(): @pytest.fixture -def init_linter(linter: PyLinter) -> PyLinter: +def initialized_linter(linter: PyLinter) -> PyLinter: linter.open() - linter.set_current_module("toto") + linter.set_current_module("toto", "mydir/toto") linter.file_state = FileState("toto") return linter @@ -273,8 +273,8 @@ def visit_class(self, _): linter.check(["abc"]) -def test_enable_message(init_linter: PyLinter) -> None: - linter = init_linter +def test_enable_message(initialized_linter: PyLinter) -> None: + linter = initialized_linter assert linter.is_message_enabled("W0101") assert linter.is_message_enabled("W0102") linter.disable("W0101", scope="package") @@ -290,8 +290,8 @@ def test_enable_message(init_linter: PyLinter) -> None: assert linter.is_message_enabled("W0102", 1) -def test_enable_message_category(init_linter: PyLinter) -> None: - linter = init_linter +def test_enable_message_category(initialized_linter: PyLinter) -> None: + linter = initialized_linter assert linter.is_message_enabled("W0101") assert linter.is_message_enabled("C0202") linter.disable("W", scope="package") @@ -309,11 +309,11 @@ def test_enable_message_category(init_linter: PyLinter) -> None: assert linter.is_message_enabled("C0202", line=1) -def test_message_state_scope(init_linter: PyLinter) -> None: +def test_message_state_scope(initialized_linter: PyLinter) -> None: class FakeConfig: confidence = ["HIGH"] - linter = init_linter + linter = initialized_linter linter.disable("C0202") assert MSG_STATE_SCOPE_CONFIG == linter._get_message_state_scope("C0202") linter.disable("W0101", scope="module", line=3) @@ -327,8 +327,8 @@ class FakeConfig: ) -def test_enable_message_block(init_linter: PyLinter) -> None: - linter = init_linter +def test_enable_message_block(initialized_linter: PyLinter) -> None: + linter = initialized_linter linter.open() filepath = join(REGRTEST_DATA_DIR, "func_block_disable_msg.py") linter.set_current_module("func_block_disable_msg") @@ -385,12 +385,12 @@ def test_enable_message_block(init_linter: PyLinter) -> None: assert fs._suppression_mapping["E1101", 110] == 109 -def test_enable_by_symbol(init_linter: PyLinter) -> None: +def test_enable_by_symbol(initialized_linter: PyLinter) -> None: """messages can be controlled by symbolic names. The state is consistent across symbols and numbers. """ - linter = init_linter + linter = initialized_linter assert linter.is_message_enabled("W0101") assert linter.is_message_enabled("unreachable") assert linter.is_message_enabled("W0102") @@ -652,8 +652,10 @@ def test_full_documentation(linter: PyLinter) -> None: assert re.search(regexp, output) -def test_list_msgs_enabled(init_linter: PyLinter, capsys: CaptureFixture) -> None: - linter = init_linter +def test_list_msgs_enabled( + initialized_linter: PyLinter, capsys: CaptureFixture +) -> None: + linter = initialized_linter linter.enable("W0101", scope="package") linter.disable("W0102", scope="package") linter.list_messages_enabled() @@ -865,12 +867,12 @@ def test_multiprocessing(jobs: int) -> None: assert len(messages) == len(set(messages)) -def test_filename_with__init__(init_linter: PyLinter) -> None: +def test_filename_with__init__(initialized_linter: PyLinter) -> None: # This tracks a regression where a file whose name ends in __init__.py, # such as flycheck__init__.py, would accidentally lead to linting the # entire containing directory. reporter = testutils.GenericTestReporter() - linter = init_linter + linter = initialized_linter linter.open() linter.set_reporter(reporter) filepath = join(INPUT_DIR, "not__init__.py") @@ -879,15 +881,15 @@ def test_filename_with__init__(init_linter: PyLinter) -> None: assert len(messages) == 0 -def test_by_module_statement_value(init_linter: PyLinter) -> None: +def test_by_module_statement_value(initialized_linter: PyLinter) -> None: """Test "statement" for each module analyzed of computed correctly.""" - linter = init_linter + linter = initialized_linter linter.check([os.path.join(os.path.dirname(__file__), "data")]) by_module_stats = linter.stats.by_module for module, module_stats in by_module_stats.items(): - linter2 = init_linter + linter2 = initialized_linter if module == "data": linter2.check([os.path.join(os.path.dirname(__file__), "data/__init__.py")]) else: From 45b5e7ba3ae3d3fdca3ef0eccdae715986069cee Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 21 Dec 2021 20:13:48 +0100 Subject: [PATCH 090/357] Add missing method to ``_ManHelpFormatter`` (#5577) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- pylint/config/man_help_formatter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pylint/config/man_help_formatter.py b/pylint/config/man_help_formatter.py index b265d654c5..e8e0198247 100644 --- a/pylint/config/man_help_formatter.py +++ b/pylint/config/man_help_formatter.py @@ -6,7 +6,6 @@ import time -# pylint: disable=abstract-method; by design? class _ManHelpFormatter(optparse.HelpFormatter): def __init__( self, indent_increment=0, max_help_position=24, width=79, short_first=0 @@ -107,6 +106,10 @@ def format_tail(pkginfo): """ return tail + def format_usage(self, usage): + """Taken from optparse.IndentedHelpFormatter""" + return f"Usage: {usage}\n" + def _generate_manpage(optparser, pkginfo, section=1, stream=sys.stdout, level=0): formatter = _ManHelpFormatter() From 29bf25c1e4e93477cc4cf7e1250b007e4a8b07e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 21 Dec 2021 22:41:35 +0100 Subject: [PATCH 091/357] Add types to option attributes of ``PyLinter`` and reorganize ``init`` (#5579) --- pylint/lint/pylinter.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index d89da0b933..1619159c6d 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -21,6 +21,7 @@ Optional, Sequence, Set, + Tuple, Type, Union, ) @@ -72,6 +73,8 @@ else: from typing_extensions import Literal +OptionDict = Dict[str, Union[str, bool, int, Iterable[Union[str, int]]]] + MANAGER = astroid.MANAGER @@ -216,7 +219,7 @@ class PyLinter( crash_file_path: str = "pylint-crash-%Y-%m-%d-%H.txt" @staticmethod - def make_options(): + def make_options() -> Tuple[Tuple[str, OptionDict], ...]: return ( ( "ignore", @@ -526,7 +529,7 @@ def make_options(): ), ) - option_groups = ( + base_option_groups = ( ("Messages control", "Options controlling analysis messages"), ("Reports", "Options related to output formatting and reporting"), ) @@ -557,21 +560,26 @@ def __init__( self._dynamic_plugins: Set[str] = set() """Set of loaded plugin names""" - self.msgs_store = MessageDefinitionStore() - self._pragma_lineno = {} - # Attributes related to visiting files self.file_state = FileState() self.current_name: Optional[str] = None self.current_file: Optional[str] = None self._ignore_file = False + self._pragma_lineno: Dict[str, int] = {} + # Attributes related to stats self.stats = LinterStats() - self.fail_on_symbols = [] - # init options - self._external_opts = options - self.options = options + PyLinter.make_options() - self.option_groups = option_groups + PyLinter.option_groups + + # Attributes related to (command-line) options and their parsing + # pylint: disable-next=fixme + # TODO: Make these implicitly typing when typing for __init__ parameter is added + self._external_opts: Tuple[Tuple[str, OptionDict], ...] = options + self.options: Tuple[Tuple[str, OptionDict], ...] = ( + options + PyLinter.make_options() + ) + self.option_groups: Tuple[Tuple[str, str], ...] = ( + option_groups + PyLinter.base_option_groups + ) self._options_methods = { "enable": self.enable, "disable": self.disable, @@ -581,8 +589,12 @@ def __init__( "disable-msg": self._options_methods["disable"], "enable-msg": self._options_methods["enable"], } + self.fail_on_symbols: List[str] = [] + """List of message symbols on which pylint should fail, set by --fail-on""" + self._error_mode = False - # Attributes related to message (state) handling + # Attributes related to messages (states) and their handling + self.msgs_store = MessageDefinitionStore() self.msg_status = 0 self._msgs_state: Dict[str, bool] = {} self._by_id_managed_msgs: List[ManagedMessage] = [] @@ -604,7 +616,6 @@ def __init__( ("RP0003", "Messages", report_messages_stats), ) self.register_checker(self) - self._error_mode = False self.load_provider_defaults() def load_default_plugins(self): From c0fbbb7adbc39cbd67bbd0e0b1ca4e9421c12643 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 21 Dec 2021 23:44:51 +0100 Subject: [PATCH 092/357] Fix typos accross the whole codebase (#5575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Jacob Walls --- pylint/checkers/base.py | 38 ++++++++--------- pylint/checkers/base_checker.py | 4 +- pylint/checkers/design_analysis.py | 5 +-- pylint/checkers/exceptions.py | 42 +++++++++---------- pylint/checkers/format.py | 4 +- .../refactoring/recommendation_checker.py | 2 +- pylint/checkers/strings.py | 11 +++-- pylint/checkers/typecheck.py | 13 +++--- pylint/checkers/utils.py | 21 +++++----- pylint/checkers/variables.py | 3 +- pylint/config/configuration_mixin.py | 2 +- pylint/config/find_default_config_files.py | 2 +- pylint/config/option_manager_mixin.py | 10 ++--- pylint/epylint.py | 4 +- pylint/extensions/_check_docs_utils.py | 24 ++++++----- pylint/extensions/code_style.py | 2 +- pylint/extensions/comparetozero.py | 2 +- pylint/extensions/docparams.py | 7 ++-- pylint/extensions/emptystring.py | 2 +- pylint/extensions/redefined_variable_type.py | 2 +- pylint/extensions/typing.py | 2 +- pylint/interfaces.py | 12 +++--- pylint/lint/parallel.py | 4 +- pylint/lint/pylinter.py | 8 ++-- pylint/lint/run.py | 6 +-- pylint/lint/utils.py | 4 +- pylint/pyreverse/diadefslib.py | 2 +- pylint/pyreverse/diagrams.py | 14 +++---- pylint/pyreverse/mermaidjs_printer.py | 2 +- pylint/reporters/multi_reporter.py | 2 +- pylint/reporters/ureports/base_writer.py | 4 +- pylint/reporters/ureports/nodes.py | 2 +- pylint/testutils/configuration_test.py | 2 +- pylint/testutils/output_line.py | 4 +- pylint/testutils/primer.py | 2 +- pylint/testutils/pyreverse.py | 2 +- pylint/utils/ast_walker.py | 4 +- pylint/utils/pragma_parser.py | 2 +- pylint/utils/utils.py | 8 ++-- script/fix_documentation.py | 6 +-- tests/checkers/unittest_imports.py | 5 ++- tests/checkers/unittest_spelling.py | 2 +- tests/checkers/unittest_stdlib.py | 2 +- tests/checkers/unittest_typecheck.py | 6 ++- .../config/test_functional_config_loading.py | 8 ++-- tests/message/unittest_message.py | 8 ++-- tests/primer/test_primer_stdlib.py | 2 +- tests/pyreverse/test_diadefs.py | 2 +- tests/test_functional.py | 4 +- tests/test_regr.py | 4 +- tests/unittest_reporting.py | 4 +- 51 files changed, 171 insertions(+), 168 deletions(-) diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 46eb0d17dd..652330f116 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -298,13 +298,13 @@ def _get_break_loop_node(break_node): def _loop_exits_early(loop): """ - Returns true if a loop may ends up in a break statement. + Returns true if a loop may end with a break statement. Args: loop (astroid.For, astroid.While): the loop node inspected. Returns: - bool: True if the loop may ends up in a break statement, False otherwise. + bool: True if the loop may end with a break statement, False otherwise. """ loop_nodes = (nodes.For, nodes.While) definition_nodes = (nodes.FunctionDef, nodes.ClassDef) @@ -349,7 +349,7 @@ def _get_properties(config): def _determine_function_name_type(node: nodes.FunctionDef, config=None): - """Determine the name type whose regex the a function's name should match. + """Determine the name type whose regex the function's name should match. :param node: A function node. :param config: Configuration from which to pull additional property classes. @@ -747,7 +747,7 @@ def visit_while(self, node: nodes.While) -> None: @utils.check_messages("nonexistent-operator") def visit_unaryop(self, node: nodes.UnaryOp) -> None: - """check use of the non-existent ++ and -- operator operator""" + """Check use of the non-existent ++ and -- operators""" if ( (node.op in "+-") and isinstance(node.operand, nodes.UnaryOp) @@ -1244,7 +1244,7 @@ def _has_variadic_argument(args, variadic_name): @utils.check_messages("unnecessary-lambda") def visit_lambda(self, node: nodes.Lambda) -> None: - """check whether or not the lambda is suspicious""" + """Check whether the lambda is suspicious""" # if the body of the lambda is a call expression with the same # argument list as the lambda itself, then the lambda is # possibly unnecessary and at least suspicious. @@ -1357,9 +1357,9 @@ def is_iterable(internal_node): @utils.check_messages("unreachable", "lost-exception") def visit_return(self, node: nodes.Return) -> None: - """1 - check is the node has a right sibling (if so, that's some + """1 - check if the node has a right sibling (if so, that's some unreachable code) - 2 - check is the node is inside the finally clause of a try...finally + 2 - check if the node is inside the 'finally' clause of a 'try...finally' block """ self._check_unreachable(node) @@ -1375,9 +1375,9 @@ def visit_continue(self, node: nodes.Continue) -> None: @utils.check_messages("unreachable", "lost-exception") def visit_break(self, node: nodes.Break) -> None: - """1 - check is the node has a right sibling (if so, that's some + """1 - check if the node has a right sibling (if so, that's some unreachable code) - 2 - check is the node is inside the finally clause of a try...finally + 2 - check if the node is inside the 'finally' clause of a 'try...finally' block """ # 1 - Is it right sibling ? @@ -1490,14 +1490,14 @@ def _check_unreachable(self, node): self.add_message("unreachable", node=unreach_stmt) def _check_not_in_finally(self, node, node_name, breaker_classes=()): - """check that a node is not inside a finally clause of a - try...finally statement. - If we found before a try...finally block a parent which its type is - in breaker_classes, we skip the whole check.""" + """check that a node is not inside a 'finally' clause of a + 'try...finally' statement. + If we find a parent which type is in breaker_classes before + a 'try...finally' block we skip the whole check.""" # if self._tryfinallys is empty, we're not an in try...finally block if not self._tryfinallys: return - # the node could be a grand-grand...-children of the try...finally + # the node could be a grand-grand...-child of the 'try...finally' _parent = node.parent _node = node while _parent and not isinstance(_parent, breaker_classes): @@ -1612,9 +1612,9 @@ def _check_self_assigning_variable(self, node): continue if not isinstance(target, nodes.AssignName): continue + # Check that the scope is different from a class level, which is usually + # a pattern to expose module level attributes as class level ones. if isinstance(scope, nodes.ClassDef) and target.name in scope_locals: - # Check that the scope is different than a class level, which is usually - # a pattern to expose module level attributes as class level ones. continue if target.name == lhs_name.name: self.add_message( @@ -2218,7 +2218,7 @@ def _check_docstring( report_missing=True, confidence=interfaces.HIGH, ): - """check the node has a non empty docstring""" + """Check if the node has a non-empty docstring""" docstring = node.doc if docstring is None: docstring = _infer_dunder_doc_attribute(node) @@ -2229,7 +2229,7 @@ def _check_docstring( lines = utils.get_node_last_lineno(node) - node.lineno if node_type == "module" and not lines: - # If the module has no body, there's no reason + # If the module does not have a body, there's no reason # to require a docstring. return max_lines = self.config.docstring_min_length @@ -2469,7 +2469,7 @@ def _check_literal_comparison(self, literal, node: nodes.Compare): is_const = False if isinstance(literal, nodes.Const): if isinstance(literal.value, bool) or literal.value is None: - # Not interested in this values. + # Not interested in these values. return is_const = isinstance(literal.value, (bytes, str, int, float)) diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index ca1ff39dc7..8fe44a2e23 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -86,7 +86,7 @@ def get_full_documentation(self, msgs, options, reports, doc=None, module=None): # Provide anchor to link against result += get_rst_title(f"{checker_title} Documentation", "^") result += f"{cleandoc(doc)}\n\n" - # options might be an empty generator and not be False when casted to boolean + # options might be an empty generator and not be False when cast to boolean options = list(options) if options: result += get_rst_title(f"{checker_title} Options", "^") @@ -186,7 +186,7 @@ def get_message_definition(self, msgid): raise InvalidMessageError(error_msg) def open(self): - """called before visiting project (i.e set of modules)""" + """called before visiting project (i.e. set of modules)""" def close(self): """called after visiting project (i.e set of modules)""" diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index e6537a4177..8a26234869 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -634,9 +634,8 @@ def visit_if(self, node: nodes.If) -> None: self._inc_all_stmts(branches) def _check_boolean_expressions(self, node): - """Go through "if" node `node` and counts its boolean expressions - - if the "if" node test is a BoolOp node + """Go through "if" node `node` and count its boolean expressions + if the 'if' node test is a BoolOp node """ condition = node.test if not isinstance(condition, astroid.BoolOp): diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 5af1679f1d..b9b6ceb042 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -447,7 +447,7 @@ def gather_exceptions_from_handler( bare_raise = False handler_having_bare_raise = None - excs_in_bare_handler = [] + exceptions_in_bare_handler = [] for handler in node.handlers: if bare_raise: # check that subsequent handler is not parent of handler which had bare raise. @@ -457,14 +457,14 @@ def gather_exceptions_from_handler( excs_in_current_handler = gather_exceptions_from_handler(handler) if not excs_in_current_handler: break - if excs_in_bare_handler is None: + if exceptions_in_bare_handler is None: # It can be `None` when the inference failed break for exc_in_current_handler in excs_in_current_handler: inferred_current = utils.safe_infer(exc_in_current_handler) if any( utils.is_subclass_of(utils.safe_infer(e), inferred_current) - for e in excs_in_bare_handler + for e in exceptions_in_bare_handler ): bare_raise = False break @@ -475,7 +475,7 @@ def gather_exceptions_from_handler( if handler.body[0].exc is None: bare_raise = True handler_having_bare_raise = handler - excs_in_bare_handler = gather_exceptions_from_handler(handler) + exceptions_in_bare_handler = gather_exceptions_from_handler(handler) else: if bare_raise: self.add_message("try-except-raise", node=handler_having_bare_raise) @@ -525,50 +525,50 @@ def visit_tryexcept(self, node: nodes.TryExcept) -> None: ) else: try: - excs = list(_annotated_unpack_infer(handler.type)) + exceptions = list(_annotated_unpack_infer(handler.type)) except astroid.InferenceError: continue - for part, exc in excs: - if exc is astroid.Uninferable: + for part, exception in exceptions: + if exception is astroid.Uninferable: continue - if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex( - exc - ): - exc = exc._proxied + if isinstance( + exception, astroid.Instance + ) and utils.inherit_from_std_ex(exception): + exception = exception._proxied - self._check_catching_non_exception(handler, exc, part) + self._check_catching_non_exception(handler, exception, part) - if not isinstance(exc, nodes.ClassDef): + if not isinstance(exception, nodes.ClassDef): continue exc_ancestors = [ anc - for anc in exc.ancestors() + for anc in exception.ancestors() if isinstance(anc, nodes.ClassDef) ] for previous_exc in exceptions_classes: if previous_exc in exc_ancestors: - msg = f"{previous_exc.name} is an ancestor class of {exc.name}" + msg = f"{previous_exc.name} is an ancestor class of {exception.name}" self.add_message( "bad-except-order", node=handler.type, args=msg ) if ( - exc.name in self.config.overgeneral_exceptions - and exc.root().name == utils.EXCEPTIONS_MODULE + exception.name in self.config.overgeneral_exceptions + and exception.root().name == utils.EXCEPTIONS_MODULE and not _is_raising(handler.body) ): self.add_message( - "broad-except", args=exc.name, node=handler.type + "broad-except", args=exception.name, node=handler.type ) - if exc in exceptions_classes: + if exception in exceptions_classes: self.add_message( - "duplicate-except", args=exc.name, node=handler.type + "duplicate-except", args=exception.name, node=handler.type ) - exceptions_classes += [exc for _, exc in excs] + exceptions_classes += [exc for _, exc in exceptions] def register(linter): diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 54e571296e..0cee7b90b2 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -47,7 +47,7 @@ """Python code format's checker. -By default try to follow Guido's style guide : +By default, try to follow Guido's style guide : https://www.python.org/doc/essays/styleguide/ @@ -594,7 +594,7 @@ def visit_default(self, node: nodes.NodeNG) -> None: prev_sibl = node.previous_sibling() if prev_sibl is not None: prev_line = prev_sibl.fromlineno - # The line on which a finally: occurs in a try/finally + # The line on which a 'finally': occurs in a 'try/finally' # is not directly represented in the AST. We infer it # by taking the last line of the body and adding 1, which # should be the line of finally: diff --git a/pylint/checkers/refactoring/recommendation_checker.py b/pylint/checkers/refactoring/recommendation_checker.py index 5424a765ce..3094ffd07e 100644 --- a/pylint/checkers/refactoring/recommendation_checker.py +++ b/pylint/checkers/refactoring/recommendation_checker.py @@ -337,7 +337,7 @@ def visit_const(self, node: nodes.Const) -> None: def _detect_replacable_format_call(self, node: nodes.Const) -> None: """Check whether a string is used in a call to format() or '%' and whether it - can be replaced by a f-string""" + can be replaced by an f-string""" if ( isinstance(node.parent, nodes.Attribute) and node.parent.attrname == "format" diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index bc573979f6..064902b5d7 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -518,7 +518,7 @@ def _check_new_format(self, node, func): # only if the .format got at least one keyword argument. # This means that the format strings accepts both # positional and named fields and we should warn - # when one of the them is missing or is extra. + # when one of them is missing or is extra. check_args = True else: check_args = True @@ -961,7 +961,7 @@ def _is_long_string(string_token: str) -> bool: string_token: The string token to be parsed. Returns: - A boolean representing whether or not this token matches a longstring + A boolean representing whether this token matches a longstring regex. """ return bool( @@ -973,15 +973,14 @@ def _is_long_string(string_token: str) -> bool: def _get_quote_delimiter(string_token: str) -> str: """Returns the quote character used to delimit this token string. - This function does little checking for whether the token is a well-formed - string. + This function checks whether the token is a well-formed string. Args: string_token: The token to be parsed. Returns: - A string containing solely the first quote delimiter character in the passed - string. + A string containing solely the first quote delimiter character in the + given string. Raises: ValueError: No quote delimiter characters are present. diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 6885e8cbf5..b4f224c06c 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -161,7 +161,7 @@ def _is_owner_ignored(owner, attrname, ignored_classes, ignored_modules): if fnmatch.fnmatch(module_qname, ignore): return True - # Otherwise we might have a root module name being ignored, + # Otherwise, we might have a root module name being ignored, # and the qualified owner has more levels of depth. parts = deque(module_name.split(".")) current_module = "" @@ -1184,14 +1184,14 @@ def _check_dundername_is_string(self, node): Check a string is assigned to self.__name__ """ - # Check the left hand side of the assignment is .__name__ + # Check the left-hand side of the assignment is .__name__ lhs = node.targets[0] if not isinstance(lhs, nodes.AssignAttr): return if not lhs.attrname == "__name__": return - # If the right hand side is not a string + # If the right-hand side is not a string rhs = node.value if isinstance(rhs, nodes.Const) and isinstance(rhs.value, str): return @@ -1271,7 +1271,7 @@ def _check_argument_order(self, node, call_site, called, called_param_names): # extract argument names, if they have names calling_parg_names = [p.name for p in call_site.positional_arguments] - # Additionally get names of keyword arguments to use in a full match + # Additionally, get names of keyword arguments to use in a full match # against parameters calling_kwarg_names = [ arg.name for arg in call_site.keyword_arguments.values() @@ -1664,7 +1664,7 @@ def _check_invalid_slice_index(self, node: nodes.Slice) -> None: if index_type is None or index_type is astroid.Uninferable: continue - # Constants must of type int or None + # Constants must be of type int or None if isinstance(index_type, nodes.Const): if isinstance(index_type.value, (int, type(None))): continue @@ -1733,7 +1733,8 @@ def visit_with(self, node: nodes.With) -> None: # See the test file for not_context_manager for a couple # of self explaining tests. - # Retrieve node from all previusly visited nodes in the the inference history + # Retrieve node from all previously visited nodes in the + # inference history context_path_names: Iterator[Any] = filter( None, _unflatten(context.path) ) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index cb04c21190..d89b39d9ff 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -119,7 +119,7 @@ # # * None: variable number of parameters # * number: exactly that number of parameters -# * tuple: this are the odd ones. Basically it means that the function +# * tuple: these are the odd ones. Basically it means that the function # can work with any number of arguments from that tuple, # although it's best to implement it in order to accept # all of them. @@ -400,7 +400,7 @@ def is_defined_before(var_node: nodes.Name) -> bool: for parent in var_node.node_ancestors(): if is_defined_in_scope(var_node, varname, parent): return True - # possibly multiple statements on the same line using semi colon separator + # possibly multiple statements on the same line using semicolon separator stmt = var_node.statement(future=True) _node = stmt.previous_sibling() lineno = stmt.fromlineno @@ -511,11 +511,11 @@ def __init__(self, index): def parse_format_string( format_string: str, ) -> Tuple[Set[str], int, Dict[str, str], List[str]]: - """Parses a format string, returning a tuple of (keys, num_args), where keys - is the set of mapping keys in the format string, and num_args is the number - of arguments required by the format string. Raises - IncompleteFormatString or UnsupportedFormatCharacter if a - parse error occurs.""" + """Parses a format string, returning a tuple of (keys, num_args), where 'keys' + is the set of mapping keys in the format string, and 'num_args' is the number + of arguments required by the format string. Raises IncompleteFormatString or + UnsupportedFormatCharacter if a parse error occurs. + """ keys = set() key_types = {} pos_types = [] @@ -928,8 +928,7 @@ def unimplemented_abstract_methods( # Old style class, it will not have a mro. return {} except astroid.ResolveError: - # Probably inconsistent hierarchy, don'try - # to figure this out here. + # Probably inconsistent hierarchy, don't try to figure this out here. return {} for ancestor in mro: for obj in ancestor.values(): @@ -1461,7 +1460,7 @@ def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool: @lru_cache(maxsize=1024) def is_overload_stub(node: nodes.NodeNG) -> bool: - """Check if a node if is a function stub decorated with typing.overload. + """Check if a node is a function stub decorated with typing.overload. :param node: Node to check. :returns: True if node is an overload function stub. False otherwise. @@ -1680,7 +1679,7 @@ def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: def is_function_body_ellipsis(node: nodes.FunctionDef) -> bool: - """Checks whether a function body only consisst of a single Ellipsis""" + """Checks whether a function body only consists of a single Ellipsis""" return ( len(node.body) == 1 and isinstance(node.body[0], nodes.Expr) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 8e2f868391..a32099dfdb 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -54,8 +54,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""variables checkers for Python code -""" +"""Variables checkers for Python code""" import collections import copy import itertools diff --git a/pylint/config/configuration_mixin.py b/pylint/config/configuration_mixin.py index deca7f4bc9..2d34933f08 100644 --- a/pylint/config/configuration_mixin.py +++ b/pylint/config/configuration_mixin.py @@ -6,7 +6,7 @@ class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): - """basic mixin for simple configurations which don't need the + """Basic mixin for simple configurations which don't need the manager / providers model""" def __init__(self, *args, **kwargs): diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index dd051f9c60..36fd351f05 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -71,7 +71,7 @@ def find_default_config_files() -> Iterator[str]: def find_pylintrc() -> Optional[str]: - """search the pylint rc file and return its path if it find it, else None""" + """Search the pylint rc file and return its path if it finds it, else return None""" for config_file in find_default_config_files(): if config_file.endswith("pylintrc"): return config_file diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 13938feae3..e6e031e7df 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -212,8 +212,8 @@ def generate_config( continue if section not in sections: sections.append(section) - alloptions = options_by_section.setdefault(section, []) - alloptions += options + all_options = options_by_section.setdefault(section, []) + all_options += options stream = stream or sys.stdout printed = False for section in sections: @@ -245,7 +245,7 @@ def load_provider_defaults(self): def read_config_file(self, config_file=None, verbose=None): """Read the configuration file but do not load it (i.e. dispatching - values to each options provider) + values to each option's provider) """ for help_level in range(1, self._maxlevel + 1): opt = "-".join(["long"] * help_level) + "-help" @@ -282,7 +282,7 @@ def read_config_file(self, config_file=None, verbose=None): # Use this encoding in order to strip the BOM marker, if any. with open(config_file, encoding="utf_8_sig") as fp: parser.read_file(fp) - # normalize sections'title + # normalize each section's title for sect, values in list(parser._sections.items()): if sect.startswith("pylint."): sect = sect[len("pylint.") :] @@ -332,7 +332,7 @@ def _parse_toml( def load_config_file(self): """Dispatch values previously read from a configuration file to each - options provider)""" + option's provider""" parser = self.cfgfile_parser for section in parser.sections(): for option, value in parser.items(section): diff --git a/pylint/epylint.py b/pylint/epylint.py index 09795d6395..98b11afd56 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -67,8 +67,8 @@ def _get_env(): - """Extracts the environment PYTHONPATH and appends the current sys.path to - those.""" + """Extracts the environment PYTHONPATH and appends the current 'sys.path' + to it.""" env = dict(os.environ) env["PYTHONPATH"] = os.pathsep.join(sys.path) return env diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index ad4a5bb32e..611a04aa25 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -122,7 +122,7 @@ def _split_multiple_exc_types(target: str) -> List[str]: def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: """ - Gets all of the possible raised exception types for the given raise node. + Gets all the possible raised exception types for the given raise node. .. note:: @@ -133,11 +133,11 @@ def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: :returns: A list of exception types possibly raised by :param:`node`. """ - excs = [] + exceptions = [] if isinstance(node.exc, nodes.Name): inferred = utils.safe_infer(node.exc) if inferred: - excs = [inferred] + exceptions = [inferred] elif node.exc is None: handler = node.parent while handler and not isinstance(handler, nodes.ExceptHandler): @@ -145,15 +145,15 @@ def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: if handler and handler.type: try: - for exc in astroid.unpack_infer(handler.type): - if exc is not astroid.Uninferable: - excs.append(exc) + for exception in astroid.unpack_infer(handler.type): + if exception is not astroid.Uninferable: + exceptions.append(exception) except astroid.InferenceError: pass else: target = _get_raise_target(node) if isinstance(target, nodes.ClassDef): - excs = [target] + exceptions = [target] elif isinstance(target, nodes.FunctionDef): for ret in target.nodes_of_class(nodes.Return): if ret.frame() != target: @@ -163,12 +163,16 @@ def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: val = utils.safe_infer(ret.value) if val and utils.inherit_from_std_ex(val): if isinstance(val, nodes.ClassDef): - excs.append(val) + exceptions.append(val) elif isinstance(val, astroid.Instance): - excs.append(val.getattr("__class__")[0]) + exceptions.append(val.getattr("__class__")[0]) try: - return {exc for exc in excs if not utils.node_ignores_exception(node, exc.name)} + return { + exc + for exc in exceptions + if not utils.node_ignores_exception(node, exc.name) + } except astroid.InferenceError: return set() diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index 8d789720ad..332ffaa92f 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -274,7 +274,7 @@ def _check_prev_sibling_to_if_stmt( def _check_ignore_assignment_expr_suggestion( node: nodes.If, name: Optional[str] ) -> bool: - """Return True if suggestion for assignment expr should be ignore. + """Return True if suggestion for assignment expr should be ignored. E.g., in cases where a match statement would be a better fit (multiple conditions). diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py index ced62f0a60..e543f1ccfa 100644 --- a/pylint/extensions/comparetozero.py +++ b/pylint/extensions/comparetozero.py @@ -28,7 +28,7 @@ def _is_constant_zero(node): class CompareToZeroChecker(checkers.BaseChecker): """Checks for comparisons to zero. - Most of the times you should use the fact that integers with a value of 0 are false. + Most of the time you should use the fact that integers with a value of 0 are false. An exception to this rule is when 0 is allowed in the program and has a different meaning than None! """ diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 05c2620123..4fadf9458d 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -521,9 +521,8 @@ class constructor. :param warning_node: The node to assign the warnings to :type warning_node: :class:`astroid.scoped_nodes.Node` - :param accept_no_param_doc: Whether or not to allow no parameters - to be documented. - If None then this value is read from the configuration. + :param accept_no_param_doc: Whether to allow no parameters to be + documented. If None then this value is read from the configuration. :type accept_no_param_doc: bool or None """ # Tolerate missing param or type declarations if there is a link to @@ -589,7 +588,7 @@ class constructor. ): self.add_message( "missing-any-param-doc", - args=warning_node.name, + args=(warning_node.name,), node=warning_node, ) else: diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py index 3e62937db1..4e466aca4c 100644 --- a/pylint/extensions/emptystring.py +++ b/pylint/extensions/emptystring.py @@ -23,7 +23,7 @@ class CompareToEmptyStringChecker(checkers.BaseChecker): """Checks for comparisons to empty string. - Most of the times you should use the fact that empty strings are false. + Most of the time you should use the fact that empty strings are false. An exception to this rule is when an empty string value is allowed in the program and has a different meaning than None! """ diff --git a/pylint/extensions/redefined_variable_type.py b/pylint/extensions/redefined_variable_type.py index 864c50ddf5..a2b5f2a203 100644 --- a/pylint/extensions/redefined_variable_type.py +++ b/pylint/extensions/redefined_variable_type.py @@ -30,7 +30,7 @@ class MultipleTypesChecker(BaseChecker): - Currently, if an attribute is set to different types in 2 methods of a same class, it won't be detected (see functional test) - One could improve the support for inference on assignment with tuples, - ifexpr, etc. Also it would be great to have support for inference on + ifexpr, etc. Also, it would be great to have support for inference on str.split() """ diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 01bfe1b285..79cb18ed3d 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -218,7 +218,7 @@ def _check_for_typing_alias( - OR: Python 3.7+ with postponed evaluation in a type annotation context - For Python 3.7+: Only emitt message if change doesn't create + For Python 3.7+: Only emit message if change doesn't create any name collisions, only ever used in a type annotation context, and can safely be replaced. """ diff --git a/pylint/interfaces.py b/pylint/interfaces.py index 8fd61589ff..7670ad19aa 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -64,15 +64,15 @@ def implements( obj: "BaseChecker", interface: Union[Type["Interface"], Tuple[Type["Interface"], ...]], ) -> bool: - """Does the given object (maybe an instance or class) implements the interface.""" - kimplements = getattr(obj, "__implements__", ()) - if not isinstance(kimplements, (list, tuple)): - kimplements = (kimplements,) - return any(issubclass(i, interface) for i in kimplements) + """Does the given object (maybe an instance or class) implement the interface.""" + implements_ = getattr(obj, "__implements__", ()) + if not isinstance(implements_, (list, tuple)): + implements_ = (implements_,) + return any(issubclass(i, interface) for i in implements_) class IChecker(Interface): - """Base interface, not to be used elsewhere than for sub interfaces definition.""" + """Base interface, to be used only for sub interfaces definition.""" def open(self): """called before visiting project (i.e. set of modules)""" diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index 56cee9aa75..eaacb710d7 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -78,7 +78,7 @@ def _worker_check_single_file( def _merge_mapreduce_data(linter, all_mapreduce_data): """Merges map/reduce data across workers, invoking relevant APIs on checkers""" - # First collate the data, preparing it so we can send it to the checkers for + # First collate the data and prepare it, so we can send it to the checkers for # validation. The intent here is to collect all the mapreduce data for all checker- # runs across processes - that will then be passed to a static method on the # checkers to be reduced and further processed. @@ -113,7 +113,7 @@ def check_parallel(linter, jobs, files: Iterable[FileItem], arguments=None): pool = multiprocessing.Pool( # pylint: disable=consider-using-with jobs, initializer=initializer, initargs=[linter] ) - # ..and now when the workers have inherited the linter, the actual reporter + # ...and now when the workers have inherited the linter, the actual reporter # can be set back here on the parent process so that results get stored into # correct reporter linter.set_reporter(original_reporter) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 1619159c6d..023455f68e 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -974,7 +974,7 @@ def prepare_checkers(self): # pylint: disable=unused-argument @staticmethod def should_analyze_file(modname, path, is_argument=False): - """Returns whether or not a module should be checked. + """Returns whether a module should be checked. This implementation returns True for all python source file, indicating that all files should be linted. @@ -1059,7 +1059,7 @@ def check_single_file_item(self, file: FileItem) -> None: The arguments are the same that are documented in _check_files - The initialize() method should be called before calling this method + initialize() should be called before calling this method """ with self._astroid_module_checker() as check_astroid_module: self._check_file(self.get_ast, check_astroid_module, file) @@ -1408,8 +1408,8 @@ def _is_one_message_enabled(self, msgid: str, line: Optional[int]) -> bool: fallback = True lines = self.file_state._raw_module_msgs_state.get(msgid, {}) - # Doesn't consider scopes, as a disable can be in a different scope - # than that of the current line. + # Doesn't consider scopes, as a 'disable' can be in a + # different scope than that of the current line. closest_lines = reversed( [ (message_line, enable) diff --git a/pylint/lint/run.py b/pylint/lint/run.py index d607cfd807..a349298c1a 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -46,7 +46,7 @@ def cb_list_confidence_levels(option, optname, value, parser): def cb_init_hook(optname, value): - """exec arbitrary code to set sys.path for instance""" + """Execute arbitrary code to set 'sys.path' for instance""" exec(value) # pylint: disable=exec-used @@ -348,9 +348,9 @@ def __init__( linter.load_config_file() if reporter: - # if a custom reporter is provided as argument, it may be overridden + # If a custom reporter is provided as argument, it may be overridden # by file parameters, so re-set it here, but before command line - # parsing so it's still overridable by command line option + # parsing, so it's still overridable by command line option linter.set_reporter(reporter) try: args = linter.load_command_line_configuration(args) diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index ed0cefb770..fb1093d809 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -119,10 +119,10 @@ def _patch_sys_path(args): @contextlib.contextmanager def fix_import_path(args): - """Prepare sys.path for running the linter checks. + """Prepare 'sys.path' for running the linter checks. Within this context, each of the given arguments is importable. - Paths are added to sys.path in corresponding order to the arguments. + Paths are added to 'sys.path' in corresponding order to the arguments. We avoid adding duplicate directories to sys.path. `sys.path` is reset to its original value upon exiting this context. """ diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index d8192946ae..aee686da52 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -220,7 +220,7 @@ def __init__(self, config): self.config = config def get_diadefs(self, project, linker): - """Get the diagrams configuration data + """Get the diagram's configuration data :param project:The pyreverse project :type project: pyreverse.utils.Project diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 93ccf8ae8b..5fb7cb9dd9 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -16,9 +16,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""diagram objects -""" - +"""diagram objects""" import astroid from astroid import nodes @@ -31,7 +29,7 @@ class Figure: class Relationship(Figure): - """a relation ship from an object in the diagram to another""" + """A relationship from an object in the diagram to another""" def __init__(self, from_object, to_object, relation_type, name=None): super().__init__() @@ -85,12 +83,12 @@ def get_relationships(self, role): ) def add_relationship(self, from_object, to_object, relation_type, name=None): - """create a relation ship""" + """create a relationship""" rel = Relationship(from_object, to_object, relation_type, name) self.relationships.setdefault(relation_type, []).append(rel) def get_relationship(self, from_object, relation_type): - """return a relation ship or None""" + """return a relationship or None""" for rel in self.relationships.get(relation_type, ()): if rel.from_object is from_object: return rel @@ -176,7 +174,7 @@ def classe(self, name): raise KeyError(name) def extract_relationships(self): - """extract relation ships between nodes in the diagram""" + """Extract relationships between nodes in the diagram""" for obj in self.classes(): node = obj.node obj.attrs = self.get_attrs(node) @@ -256,7 +254,7 @@ def add_from_depend(self, node, from_module): obj.node.depends.append(from_module) def extract_relationships(self): - """extract relation ships between nodes in the diagram""" + """Extract relationships between nodes in the diagram""" super().extract_relationships() for obj in self.classes(): # ownership diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index c8214ab8e0..d215b559d1 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -84,7 +84,7 @@ def _close_graph(self) -> None: class HTMLMermaidJSPrinter(MermaidJSPrinter): - """Printer for MermaidJS diagrams wrapped in an html boilerplate""" + """Printer for MermaidJS diagrams wrapped in a html boilerplate""" HTML_OPEN_BOILERPLATE = """ diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index cbdc36962c..3eb75181e9 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -50,7 +50,7 @@ def out(self): @out.setter def out(self, output: Optional[AnyFile] = None): """ - MultiReporter doesn't have it's own output. This method is only + MultiReporter doesn't have its own output. This method is only provided for API parity with BaseReporter and should not be called with non-None values for 'output'. """ diff --git a/pylint/reporters/ureports/base_writer.py b/pylint/reporters/ureports/base_writer.py index edf5885278..e87acd7eaf 100644 --- a/pylint/reporters/ureports/base_writer.py +++ b/pylint/reporters/ureports/base_writer.py @@ -36,7 +36,7 @@ def format(self, layout, stream: TextIO = sys.stdout, encoding=None) -> None: """format and write the given layout into the stream object unicode policy: unicode strings may be found in the layout; - try to call stream.write with it, but give it back encoded using + try to call 'stream.write' with it, but give it back encoded using the given encoding if it fails """ if not encoding: @@ -69,7 +69,7 @@ def begin_format(self) -> None: self.section = 0 def end_format(self) -> None: - """finished to format a layout""" + """Finished formatting a layout""" def get_table_content(self, table: "Table") -> List[List[str]]: """trick to get table content without actually writing it diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index 4f7cde24d7..280f97528e 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -148,7 +148,7 @@ class Title(BaseLayout): attributes : * BaseLayout attributes - A title must not contains a section nor a paragraph! + A title must not contain a section nor a paragraph! """ diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index b0fd0b1408..4e6f265bb4 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -22,7 +22,7 @@ if not PY38_PLUS: # We need to deepcopy a compiled regex pattern - # In python 3.6 and 3.7 this require a hack + # In python 3.6 and 3.7 this requires a hack # See https://stackoverflow.com/a/56935186 copy._deepcopy_dispatch[type(re.compile(""))] = lambda r, _: r # type: ignore[attr-defined] diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index c3836a763e..9c0c762395 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -93,8 +93,8 @@ def from_msg(cls, msg: Message, check_endline: bool = True) -> "OutputLine": @staticmethod def _get_column(column: str) -> int: - """Handle column numbers with the exception of pylint < 3.8 not having them - in the ast parser. + """Handle column numbers except for python < 3.8. The ast parser in those versions doesn't + return them. """ if not PY38_PLUS: # We check the column only for the new better ast parser introduced in python 3.8 diff --git a/pylint/testutils/primer.py b/pylint/testutils/primer.py index 30fa841ade..b8b33ae3e7 100644 --- a/pylint/testutils/primer.py +++ b/pylint/testutils/primer.py @@ -65,7 +65,7 @@ def paths_to_lint(self) -> List[str]: def pylint_args(self) -> List[str]: options: List[str] = [] if self.pylintrc is not None: - # There is an error if rcfile is given but does not exists + # There is an error if rcfile is given but does not exist options += [f"--rcfile={self.pylintrc}"] return self.paths_to_lint + options + self.pylint_additional_args diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index 15c5de4cb0..f70fe540f7 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -8,7 +8,7 @@ # A NamedTuple is not possible as some tests need to modify attributes during the test. class PyreverseConfig: # pylint: disable=too-many-instance-attributes, too-many-arguments """Holds the configuration options for Pyreverse. - The default values correspond to the defaults of the options parser.""" + The default values correspond to the defaults of the options' parser.""" def __init__( self, diff --git a/pylint/utils/ast_walker.py b/pylint/utils/ast_walker.py index 4f76a9805e..f846c9c21f 100644 --- a/pylint/utils/ast_walker.py +++ b/pylint/utils/ast_walker.py @@ -49,10 +49,10 @@ def add_checker(self, checker): cid = cls.__name__.lower() if cid not in vcids: visits[cid].append(visit_default) - # for now we have no "leave_default" method in Pylint + # For now, we have no "leave_default" method in Pylint def walk(self, astroid): - """call visit events of astroid checkers for the given node, recurse on + """Call visit events of astroid checkers for the given node, recurse on its children, then leave events. """ cid = astroid.__class__.__name__.lower() diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index 02ad254930..1cd1213a7b 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -9,7 +9,7 @@ # so that an option can be continued with the reasons # why it is active or disabled. OPTION_RGX = r""" - \s* # Any number of whithespace + \s* # Any number of whitespace \#? # One or zero hash .* # Anything (as much as possible) (\s* # Beginning of first matched group and any number of whitespaces diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index bf37734fd2..e84ffa4724 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -129,7 +129,7 @@ def get_rst_title(title, character): def get_rst_section(section, options, doc=None): - """format an options section using as a ReStructuredText formatted output""" + """format an option's section using as a ReStructuredText formatted output""" result = "" if section: result += get_rst_title(section, "'") @@ -350,7 +350,7 @@ def _format_option_value(optdict, value): def format_section( stream: TextIO, section: str, options: List[Tuple], doc: Optional[str] = None ) -> None: - """format an options section using the INI format""" + """Format an option's section using the INI format""" if doc: print(_comment(doc), file=stream) print(f"[{section}]", file=stream) @@ -386,9 +386,9 @@ class IsortDriver: def __init__(self, config): if HAS_ISORT_5: self.isort5_config = isort.api.Config( - # There is not typo here. EXTRA_standard_library is + # There is no typo here. EXTRA_standard_library is # what most users want. The option has been named - # KNOWN_standard_library for ages in pylint and we + # KNOWN_standard_library for ages in pylint, and we # don't want to break compatibility. extra_standard_library=config.known_standard_library, known_third_party=config.known_third_party, diff --git a/script/fix_documentation.py b/script/fix_documentation.py index b03f70ead4..36fd931f0f 100644 --- a/script/fix_documentation.py +++ b/script/fix_documentation.py @@ -83,14 +83,14 @@ def main(argv: Union[List[str], None] = None) -> int: return_value: int = 0 for file_name in args.filenames: with open(file_name, encoding="utf-8") as fp: - orignal_content = fp.read() - content = orignal_content + original_content = fp.read() + content = original_content # Modify files content = fix_inline_code_blocks(content) if file_name == args.changelog: content = changelog_insert_empty_lines(content, args.subtitle_prefix) # If modified, write changes and eventually return 1 - if orignal_content != content: + if original_content != content: with open(file_name, "w", encoding="utf-8") as fp: fp.write(content) return_value |= 1 diff --git a/tests/checkers/unittest_imports.py b/tests/checkers/unittest_imports.py index 507b0d0550..c5285d2598 100644 --- a/tests/checkers/unittest_imports.py +++ b/tests/checkers/unittest_imports.py @@ -60,8 +60,11 @@ def test_relative_beyond_top_level_two() -> None: f"{os.path.join(REGR_DATA, 'beyond_top_two')} -d all -e relative-beyond-top-level", return_std=True, ) + top_level_function = os.path.join( + REGR_DATA, "beyond_top_two/namespace_package/top_level_function.py" + ) output2, errors2 = lint.py_run( - f"{os.path.join(REGR_DATA, 'beyond_top_two/namespace_package/top_level_function.py')} -d all -e relative-beyond-top-level", + f"{top_level_function} -d all -e relative-beyond-top-level", return_std=True, ) diff --git a/tests/checkers/unittest_spelling.py b/tests/checkers/unittest_spelling.py index 29bc1bca61..838e4755a4 100644 --- a/tests/checkers/unittest_spelling.py +++ b/tests/checkers/unittest_spelling.py @@ -75,7 +75,7 @@ def test_check_bad_coment(self): @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) @set_config(max_spelling_suggestions=2) - def test_check_bad_coment_custom_suggestion_count(self): + def test_check_bad_comment_custom_suggestion_count(self): with self.assertAddsMessages( MessageTest( "wrong-spelling-in-comment", diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py index f38d3e89e4..9529af3ada 100644 --- a/tests/checkers/unittest_stdlib.py +++ b/tests/checkers/unittest_stdlib.py @@ -45,7 +45,7 @@ def test_deprecated_no_qname_on_unexpected_nodes(self) -> None: While this test might seem weird since it uses a transform, it's actually testing a crash that happened in production, but there was no way to retrieve the code for which this - occurred (how an AssignAttr got to be the result of a function inference beats me..)""" + occurred (how an AssignAttr got to be the result of a function inference beats me...)""" def infer_func( node: Name, context: Optional[Any] = None diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index 8411203ae4..cafb3b40f9 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -46,7 +46,8 @@ class TestTypeChecker(CheckerTestCase): - "Tests for pylint.checkers.typecheck" + """Tests for pylint.checkers.typecheck""" + CHECKER_CLASS = typecheck.TypeChecker @set_config(suggestion_mode=False) @@ -95,7 +96,8 @@ def test_nomember_on_c_extension_info_msg(self) -> None: class TestTypeCheckerOnDecorators(CheckerTestCase): - "Tests for pylint.checkers.typecheck on decorated functions." + """Tests for pylint.checkers.typecheck on decorated functions.""" + CHECKER_CLASS = typecheck.TypeChecker def test_issue3882_class_decorators(self) -> None: diff --git a/tests/config/test_functional_config_loading.py b/tests/config/test_functional_config_loading.py index 6ee86d6796..0dacb69739 100644 --- a/tests/config/test_functional_config_loading.py +++ b/tests/config/test_functional_config_loading.py @@ -6,7 +6,7 @@ files by providing a file with the appropriate extension in the ``tests/config/functional`` directory. -Let's say you have a regression_list_crash.toml file to test. Then if there is an error in the conf, +Let's say you have a regression_list_crash.toml file to test. Then, if there is an error in the conf, add ``regression_list_crash.out`` alongside your file with the expected output of pylint in it. Use ``{relpath}`` and ``{abspath}`` for the path of the file. The exit code will have to be 2 (error) if this file exists. @@ -34,8 +34,8 @@ HERE = Path(__file__).parent USER_SPECIFIC_PATH = HERE.parent.parent FUNCTIONAL_DIR = HERE / "functional" -# We use string then recast to path so we can use -k in pytest. -# Otherwise we get 'configuration_path0' as a test name. The path is relative to the functional +# We use string then recast to path, so we can use -k in pytest. +# Otherwise, we get 'configuration_path0' as a test name. The path is relative to the functional # directory because otherwise the string would be very lengthy. ACCEPTED_CONFIGURATION_EXTENSIONS = ("toml", "ini", "cfg") CONFIGURATION_PATHS = [ @@ -83,7 +83,7 @@ def test_functional_config_loading( ) mock_exit.assert_called_once_with(expected_code) out, err = capsys.readouterr() - # rstrip() applied so we can have a final newline in the expected test file + # 'rstrip()' applied, so we can have a final newline in the expected test file assert expected_output.rstrip() == out.rstrip(), msg assert sorted(expected_loaded_configuration.keys()) == sorted( runner.linter.config.__dict__.keys() diff --git a/tests/message/unittest_message.py b/tests/message/unittest_message.py index 64cadf91a8..d402e1d330 100644 --- a/tests/message/unittest_message.py +++ b/tests/message/unittest_message.py @@ -11,13 +11,13 @@ def test_new_message(message_definitions: ValuesView[MessageDefinition]) -> None: def build_message( - message_definition: MessageDefinition, location_value: MessageLocationTuple + message_definition_: MessageDefinition, location_value: MessageLocationTuple ) -> Message: return Message( - symbol=message_definition.symbol, - msg_id=message_definition.msgid, + symbol=message_definition_.symbol, + msg_id=message_definition_.msgid, location=location_value, - msg=message_definition.msg, + msg=message_definition_.msg, confidence=HIGH, ) diff --git a/tests/primer/test_primer_stdlib.py b/tests/primer/test_primer_stdlib.py index f403cbbf55..96c847bc8f 100644 --- a/tests/primer/test_primer_stdlib.py +++ b/tests/primer/test_primer_stdlib.py @@ -46,7 +46,7 @@ def _patch_stdout(out): def test_primer_stdlib_no_crash( test_module_location: str, test_module_name: str, capsys: CaptureFixture ) -> None: - """Test that pylint does not produces any crashes or fatal errors on stdlib modules""" + """Test that pylint does not produce any crashes or fatal errors on stdlib modules""" __tracebackhide__ = True # pylint: disable=unused-variable os.chdir(test_module_location) with _patch_stdout(io.StringIO()): diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index 0885598e1d..b172792808 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -130,7 +130,7 @@ def test_functional_relation_extraction( """functional test of relations extraction; different classes possibly in different modules""" # XXX should be catching pyreverse environment problem but doesn't - # pyreverse doesn't extracts the relations but this test ok + # pyreverse doesn't extract the relations but this test ok project = get_project("data") handler = DiadefsHandler(default_config) diadefs = handler.get_diadefs(project, Linker(project, tag=True)) diff --git a/tests/test_functional.py b/tests/test_functional.py index eb923401d8..27fda03097 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -74,8 +74,8 @@ def test_functional( lint_test.runTest() warning = None try: - # Catch :x: DeprecationWarning: invalid escape sequence - # so it's not shown during tests + # Catch :x: DeprecationWarning: invalid escape sequence, + # so, it's not shown during tests warning = recwarn.pop() except AssertionError: pass diff --git a/tests/test_regr.py b/tests/test_regr.py index 0936e0d066..99b6002755 100644 --- a/tests/test_regr.py +++ b/tests/test_regr.py @@ -51,8 +51,8 @@ def disable(): @pytest.fixture def finalize_linter(linter: PyLinter) -> Iterator[PyLinter]: - """call reporter.finalize() to cleanup - pending messages if a test finished badly + """Call reporter.finalize() to clean up pending messages if a test + finished badly. """ yield linter linter.reporter = cast( # Due to fixture diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index c0fe779167..dbf2eb1fda 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -91,8 +91,8 @@ def test_template_option_end_line(linter) -> None: assert out_lines[2] == "my_mod:2:0:2:4: C0301: Line too long (3/4) (line-too-long)" -def test_template_option_non_exisiting(linter) -> None: - """Test the msg-template option with a non existing options. +def test_template_option_non_existing(linter) -> None: + """Test the msg-template option with non-existent options. This makes sure that this option remains backwards compatible as new parameters do not break on previous versions""" output = StringIO() From 21cb6881846c8ad506bb53970802ba378388f2f1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 23 Dec 2021 11:27:15 +0100 Subject: [PATCH 093/357] Add a code of conduct from template (#5589) * Add a code of conduct from template (then auto fixed by pre-commit) --- CODE_OF_CONDUCT.md | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..6d30aaaae0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,120 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a +harassment-free experience for everyone, regardless of age, body size, visible or +invisible disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, +inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community +include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and + learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of any + kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without + their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional + setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in response to +any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an +individual is officially representing the community in public spaces. Examples of +representing our community include using an official e-mail address, posting via an +official social media account, or acting as an appointed representative at an online or +offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to +the community leaders responsible for enforcement at pierre.sassoulas at gmail.com. All +complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter +of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the +consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity +around the nature of the violation and an explanation of why the behavior was +inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with +the people involved, including unsolicited interaction with those enforcing the Code of +Conduct, for a specified period of time. This includes avoiding interactions in +community spaces as well as external channels like social media. Violating these terms +may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained +inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication +with the community for a specified period of time. No public or private interaction with +the people involved, including unsolicited interaction with those enforcing the Code of +Conduct, is allowed during this period. Violating these terms may lead to a permanent +ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, +including sustained inappropriate behavior, harassment of an individual, or aggression +toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, +available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 1a122dd7c4ace87574440bb78fc9b4ddcbd123d1 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 23 Dec 2021 14:05:22 +0100 Subject: [PATCH 094/357] Upgrade mypy to 0.930 (#5592) --- .pre-commit-config.yaml | 2 +- pylint/checkers/utils.py | 2 +- requirements_test_pre_commit.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c3bd79b48..dbcd90a67b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: types: [text] # necessary to include ChangeLog file files: ^(ChangeLog|doc/(.*/)*.*\.rst) - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.920 + rev: v0.930 hooks: - id: mypy name: mypy diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index d89b39d9ff..cd72e38ee3 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -337,7 +337,7 @@ def is_builtin_object(node: nodes.NodeNG) -> bool: def is_builtin(name: str) -> bool: """return true if could be considered as a builtin defined by python""" - return name in builtins or name in SPECIAL_BUILTINS # type: ignore[operator] + return name in builtins or name in SPECIAL_BUILTINS # type: ignore[attr-defined] def is_defined_in_scope( diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index e942387712..007e59b4fc 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,4 @@ black==21.12b0 flake8==4.0.1 flake8-typing-imports==1.12.0 isort==5.10.1 -mypy==0.920 +mypy==0.930 From b4bf5168621f0a4bf7cca795862e2b5c139fc8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 23 Dec 2021 23:02:34 +0100 Subject: [PATCH 095/357] Create ``_config_initialization`` util and use in functional tests (#5591) --- pylint/config/config_initialization.py | 75 ++++++++++++++++++++ pylint/lint/run.py | 42 +---------- pylint/testutils/lint_module_test.py | 15 ++-- tests/testutils/data/init_hook.py | 3 + tests/testutils/data/init_hook.rc | 3 + tests/testutils/test_functional_testutils.py | 21 ++++++ 6 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 pylint/config/config_initialization.py create mode 100644 tests/testutils/data/init_hook.py create mode 100644 tests/testutils/data/init_hook.rc create mode 100644 tests/testutils/test_functional_testutils.py diff --git a/pylint/config/config_initialization.py b/pylint/config/config_initialization.py new file mode 100644 index 0000000000..46031bae3a --- /dev/null +++ b/pylint/config/config_initialization.py @@ -0,0 +1,75 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +import sys +from typing import TYPE_CHECKING, List, Optional, Union + +from pylint import reporters +from pylint.utils import utils + +if TYPE_CHECKING: + from pylint.lint import PyLinter + + +def _config_initialization( + linter: "PyLinter", + args_list: List[str], + reporter: Union[reporters.BaseReporter, reporters.MultiReporter, None] = None, + config_file: Optional[str] = None, + verbose_mode: Optional[bool] = None, +) -> List[str]: + """Parse all available options, read config files and command line arguments and + set options accordingly. + """ + + # Read the config file. The parser is stored on linter.cfgfile_parser + try: + linter.read_config_file(config_file=config_file, verbose=verbose_mode) + except OSError as ex: + print(ex, file=sys.stderr) + sys.exit(32) + config_parser = linter.cfgfile_parser + + # Run init hook, if present, before loading plugins + if config_parser.has_option("MASTER", "init-hook"): + exec( # pylint: disable=exec-used + utils._unquote(config_parser.get("MASTER", "init-hook")) + ) + + # Load plugins if specified in the config file + if config_parser.has_option("MASTER", "load-plugins"): + plugins = utils._splitstrip(config_parser.get("MASTER", "load-plugins")) + linter.load_plugin_modules(plugins) + + # Now we can load file config, plugins (which can + # provide options) have been registered + linter.load_config_file() + + if reporter: + # If a custom reporter is provided as argument, it may be overridden + # by file parameters, so re-set it here, but before command line + # parsing, so it's still overridable by command line option + linter.set_reporter(reporter) + + # Load command line arguments + try: + args_list = linter.load_command_line_configuration(args_list) + except SystemExit as exc: + if exc.code == 2: # bad options + exc.code = 32 + raise + + # args_list should now only be a list of files/directories to lint. All options have + # been removed from the list + if not args_list: + print(linter.help()) + sys.exit(32) + + # We have loaded configuration from config file and command line. Now, we can + # load plugin specific configuration. + linter.load_plugin_configuration() + + # Now that plugins are loaded, get list of all fail_on messages, and enable them + linter.enable_fail_on_messages() + + return args_list diff --git a/pylint/lint/run.py b/pylint/lint/run.py index a349298c1a..a7539cd6ac 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -6,6 +6,7 @@ import warnings from pylint import __pkginfo__, extensions, interfaces +from pylint.config.config_initialization import _config_initialization from pylint.constants import DEFAULT_PYLINT_HOME, OLD_DEFAULT_PYLINT_HOME, full_version from pylint.lint.pylinter import PyLinter from pylint.lint.utils import ArgumentPreprocessingError, preprocess_options @@ -327,40 +328,8 @@ def __init__( # read configuration linter.disable("I") linter.enable("c-extension-no-member") - try: - linter.read_config_file(verbose=self.verbose) - except OSError as ex: - print(ex, file=sys.stderr) - sys.exit(32) - config_parser = linter.cfgfile_parser - # run init hook, if present, before loading plugins - if config_parser.has_option("MASTER", "init-hook"): - cb_init_hook( - "init-hook", utils._unquote(config_parser.get("MASTER", "init-hook")) - ) - # is there some additional plugins in the file configuration, in - if config_parser.has_option("MASTER", "load-plugins"): - plugins = utils._splitstrip(config_parser.get("MASTER", "load-plugins")) - linter.load_plugin_modules(plugins) - # now we can load file config and command line, plugins (which can - # provide options) have been registered - linter.load_config_file() - - if reporter: - # If a custom reporter is provided as argument, it may be overridden - # by file parameters, so re-set it here, but before command line - # parsing, so it's still overridable by command line option - linter.set_reporter(reporter) - try: - args = linter.load_command_line_configuration(args) - except SystemExit as exc: - if exc.code == 2: # bad options - exc.code = 32 - raise - if not args: - print(linter.help()) - sys.exit(32) + args = _config_initialization(linter, args, reporter, verbose_mode=self.verbose) if linter.config.jobs < 0: print( @@ -378,13 +347,6 @@ def __init__( elif linter.config.jobs == 0: linter.config.jobs = _cpu_count() - # We have loaded configuration from config file and command line. Now, we can - # load plugin specific configuration. - linter.load_plugin_configuration() - - # Now that plugins are loaded, get list of all fail_on messages, and enable them - linter.enable_fail_on_messages() - if self._output: try: with open(self._output, "w", encoding="utf-8") as output: diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index b7e1ffc67f..e89f82ace1 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -14,6 +14,7 @@ from _pytest.config import Config from pylint import checkers +from pylint.config.config_initialization import _config_initialization from pylint.lint import PyLinter from pylint.message.message import Message from pylint.testutils.constants import _EXPECTED_RE, _OPERATORS, UPDATE_OPTION @@ -24,7 +25,6 @@ ) from pylint.testutils.output_line import OutputLine from pylint.testutils.reporter_for_tests import FunctionalTestReporter -from pylint.utils import utils MessageCounter = CounterType[Tuple[int, str]] @@ -44,15 +44,14 @@ def __init__( self._linter.disable("locally-disabled") self._linter.disable("useless-suppression") try: - self._linter.read_config_file(test_file.option_file) - if self._linter.cfgfile_parser.has_option("MASTER", "load-plugins"): - plugins = utils._splitstrip( - self._linter.cfgfile_parser.get("MASTER", "load-plugins") - ) - self._linter.load_plugin_modules(plugins) - self._linter.load_config_file() + _config_initialization( + self._linter, + [test_file.source], + config_file=test_file.option_file, + ) except NoFileError: pass + self._test_file = test_file self._config = config self._check_end_position = ( diff --git a/tests/testutils/data/init_hook.py b/tests/testutils/data/init_hook.py new file mode 100644 index 0000000000..f16492d3aa --- /dev/null +++ b/tests/testutils/data/init_hook.py @@ -0,0 +1,3 @@ +"""This file should never be tested as the init-hook in the configuration +file prevents the test runner from getting here. +""" diff --git a/tests/testutils/data/init_hook.rc b/tests/testutils/data/init_hook.rc new file mode 100644 index 0000000000..b0efa254be --- /dev/null +++ b/tests/testutils/data/init_hook.rc @@ -0,0 +1,3 @@ +[MASTER] + +init-hook=raise RuntimeError diff --git a/tests/testutils/test_functional_testutils.py b/tests/testutils/test_functional_testutils.py new file mode 100644 index 0000000000..c1e852e15b --- /dev/null +++ b/tests/testutils/test_functional_testutils.py @@ -0,0 +1,21 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +"""Tests for the functional test framework.""" + +from pathlib import Path + +import pytest + +from pylint import testutils +from pylint.testutils.functional import FunctionalTestFile + +HERE = Path(__file__).parent +DATA_DIRECTORY = HERE / "data" + + +def test_parsing_of_pylintrc_init_hook() -> None: + """Test that we correctly parse an init-hook in a settings file.""" + with pytest.raises(RuntimeError): + test_file = FunctionalTestFile(str(DATA_DIRECTORY), "init_hook.py") + testutils.LintModuleTest(test_file) From 99a8f70b50182a1d67705d0f148db9c6dbf7418c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 27 Dec 2021 10:28:46 +0100 Subject: [PATCH 096/357] Add documentation about profiling and performance analysis (#5597) Co-authored-by: Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> --- doc/development_guide/index.rst | 1 + doc/development_guide/profiling.rst | 118 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 doc/development_guide/profiling.rst diff --git a/doc/development_guide/index.rst b/doc/development_guide/index.rst index 50343e4c9e..190efa3692 100644 --- a/doc/development_guide/index.rst +++ b/doc/development_guide/index.rst @@ -8,3 +8,4 @@ Development contribute testing + profiling diff --git a/doc/development_guide/profiling.rst b/doc/development_guide/profiling.rst new file mode 100644 index 0000000000..13a8bbaf4d --- /dev/null +++ b/doc/development_guide/profiling.rst @@ -0,0 +1,118 @@ +.. -*- coding: utf-8 -*- +.. _profiling: + +=================================== + Profiling and performance analysis +=================================== + +Performance analysis for Pylint +------------------------------- + +To analyse the performance of Pylint we recommend to use the ``cProfile`` module +from ``stdlib``. Together with the ``pstats`` module this should give you all the tools +you need to profile a Pylint run and see which functions take how long to run. + +The documentation for both modules can be found at cProfile_. + +To profile a run of Pylint over itself you can use the following code and run it from the base directory. +Note that ``cProfile`` will create a document called ``stats`` that is then read by ``pstats``. The +human-readable output will be stored by ``pstats`` in ``./profiler_stats``. It will be sorted by +``cumulative time``: + +.. sourcecode:: python + + import cProfile + import pstats + import sys + + sys.argv = ["pylint", "pylint"] + cProfile.run("from pylint import __main__", "stats") + + with open("profiler_stats", "w", encoding="utf-8") as file: + stats = pstats.Stats("stats", stream=file) + stats.sort_stats("cumtime") + stats.print_stats() + +You can also interact with the stats object by sorting or restricting the output. +For example, to only print functions from the ``pylint`` module and sort by cumulative time you could +use: + +.. sourcecode:: python + + import cProfile + import pstats + import sys + + sys.argv = ["pylint", "pylint"] + cProfile.run("from pylint import __main__", "stats") + + with open("profiler_stats", "w", encoding="utf-8") as file: + stats = pstats.Stats("stats", stream=file) + stats.sort_stats("cumtime") + stats.print_stats("pylint/pylint") + +Lastly, to profile a run over your own module or code you can use: + +.. sourcecode:: python + + import cProfile + import pstats + import sys + + sys.argv = ["pylint", "your_dir/your_file"] + cProfile.run("from pylint import __main__", "stats") + + with open("profiler_stats", "w", encoding="utf-8") as file: + stats = pstats.Stats("stats", stream=file) + stats.sort_stats("cumtime") + stats.print_stats() + +The documentation of the ``pstats`` module discusses other possibilites to interact with +the profiling output. + + +Performance analysis of a specific checker +------------------------------------------ + +To analyse the performance of specific checker within Pylint we can use the human-readable output +created by ``pstats``. + +If you search in the ``profiler_stats`` file for the file name of the checker you will find all functional +calls from functions within the checker. Let's say we want to check the ``visit_importfrom`` method of the +``variables`` checker:: + + ncalls tottime percall cumtime percall filename:lineno(function) + 622 0.006 0.000 8.039 0.013 /MY_PROGRAMMING_DIR/pylint/pylint/checkers/variables.py:1445(visit_importfrom) + +The previous line tells us that this method was called 622 times during the profile and we were inside the +function itself for 6 ms in total. The time per call is less than a millisecond (0.006 / 622) +and thus is displayed as being 0. + +Often you are more interested in the cumulative time (per call). This refers to the time spend within the function +and any of the functions it called or they functions they called (etc.). In our example, the ``visit_importfrom`` +method and all of its child-functions took a little over 8 seconds to exectute, with an execution time of +0.013 ms per call. + +You can also search the ``profiler_stats`` for an individual function you want to check. For example +``_analyse_fallback_blocks``, a function called by ``visit_importfrom`` in the ``variables`` checker. This +allows more detailed analysis of specific functions:: + + ncalls tottime percall cumtime percall filename:lineno(function) + 1 0.000 0.000 0.000 0.000 /MY_PROGRAMMING_DIR/pylint/pylint/checkers/variables.py:1511(_analyse_fallback_blocks) + + +Parsing the profiler stats with other tools +------------------------------------------- + +Often you might want to create a visual representation of your profiling stats. A good tool +to do this is gprof2dot_. This tool can create a ``.dot`` file from the profiling stats +created by ``cProfile`` and ``pstats``. You can then convert the ``.dot`` file to a ``.png`` +file with one of the many converters found online. + +You can read the gprof2dot_ documentation for installation instructions for your specific environment. + +Another option would be snakeviz_. + +.. _cProfile: https://docs.python.org/3/library/profile.html +.. _gprof2dot: https://github.com/jrfonseca/gprof2dot +.. _snakeviz: https://jiffyclub.github.io/snakeviz/ From 691b41b3ddd3b533bce4d6a1f03ebc1e5cacf508 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 27 Dec 2021 21:01:03 +0100 Subject: [PATCH 097/357] Bump ci cache version (#5602) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 96f06bbfd0..e0eeef0793 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: pull_request: ~ env: - CACHE_VERSION: 3 + CACHE_VERSION: 4 DEFAULT_PYTHON: 3.8 PRE_COMMIT_CACHE: ~/.cache/pre-commit From 7d2584251552283bf95893019e74dad0390dc65c Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 Dec 2021 21:30:34 +0100 Subject: [PATCH 098/357] Add typing and uniformize the checker registering in Pylinter (#5558) Remove verbose docstring in code, keep them in example and doc Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- doc/how_tos/custom_checkers.rst | 14 ++++++- doc/how_tos/plugins.rst | 28 ++++++++++++- doc/how_tos/transform_plugins.rst | 13 +++++- examples/custom.py | 12 ++++-- examples/custom_raw.py | 12 +++++- examples/deprecation_checker.py | 12 +++--- pylint/checkers/async.py | 7 +++- pylint/checkers/base.py | 8 ++-- pylint/checkers/classes/__init__.py | 7 +++- pylint/checkers/design_analysis.py | 8 ++-- pylint/checkers/ellipsis_checker.py | 9 ++-- pylint/checkers/exceptions.py | 8 ++-- pylint/checkers/format.py | 9 ++-- pylint/checkers/imports.py | 11 ++--- pylint/checkers/logging.py | 8 ++-- pylint/checkers/misc.py | 8 ++-- pylint/checkers/newstyle.py | 8 +++- pylint/checkers/raw_metrics.py | 8 ++-- pylint/checkers/refactoring/__init__.py | 8 +++- pylint/checkers/similar.py | 7 +++- pylint/checkers/spelling.py | 8 ++-- pylint/checkers/stdlib.py | 11 ++--- pylint/checkers/strings.py | 8 ++-- pylint/checkers/threading_checker.py | 8 +++- pylint/checkers/typecheck.py | 18 ++++++-- pylint/checkers/unsupported_version.py | 9 ++-- pylint/checkers/variables.py | 8 ++-- pylint/extensions/bad_builtin.py | 12 +++--- pylint/extensions/broad_try_clause.py | 8 ++-- pylint/extensions/check_docs.py | 9 ++-- pylint/extensions/check_elif.py | 12 +++--- pylint/extensions/code_style.py | 10 +++-- pylint/extensions/comparetozero.py | 8 ++-- pylint/extensions/comparison_placement.py | 8 +++- pylint/extensions/confusing_elif.py | 13 +++--- .../extensions/consider_ternary_expression.py | 12 +++--- pylint/extensions/docparams.py | 15 +++---- pylint/extensions/docstyle.py | 11 +++-- pylint/extensions/empty_comment.py | 7 +++- pylint/extensions/emptystring.py | 8 ++-- pylint/extensions/for_any_all.py | 4 -- pylint/extensions/mccabe.py | 12 +++--- pylint/extensions/overlapping_exceptions.py | 8 ++-- pylint/extensions/redefined_variable_type.py | 12 +++--- pylint/extensions/set_membership.py | 10 +++-- pylint/extensions/typing.py | 10 +++-- pylint/extensions/while_used.py | 12 +++--- pylint/lint/pylinter.py | 6 +-- pylint/reporters/json_reporter.py | 1 - pylint/reporters/reports_handler_mix_in.py | 41 +++++++++++-------- pylint/reporters/text.py | 1 - tests/checkers/unittest_similar.py | 2 - .../dummy_plugin/dummy_plugin.py | 2 +- 53 files changed, 328 insertions(+), 201 deletions(-) diff --git a/doc/how_tos/custom_checkers.rst b/doc/how_tos/custom_checkers.rst index 0f6ff60626..30c70d63a6 100644 --- a/doc/how_tos/custom_checkers.rst +++ b/doc/how_tos/custom_checkers.rst @@ -201,7 +201,19 @@ Add the ``register`` function to the top level of the file. .. code-block:: python - def register(linter): + from typing import TYPE_CHECKING + + import astroid + + if TYPE_CHECKING: + from pylint.lint import PyLinter + + + def register(linter: "PyLinter") -> None: + """This required method auto registers the checker during initialization. + + :param linter: The linter to register the checker to. + """ linter.register_checker(UniqueReturnChecker(linter)) We are now ready to debug and test our checker! diff --git a/doc/how_tos/plugins.rst b/doc/how_tos/plugins.rst index a68bd22f82..bc2c0f14cb 100644 --- a/doc/how_tos/plugins.rst +++ b/doc/how_tos/plugins.rst @@ -25,7 +25,19 @@ So a basic hello-world plugin can be implemented as: .. sourcecode:: python # Inside hello_plugin.py - def register(linter): + from typing import TYPE_CHECKING + + import astroid + + if TYPE_CHECKING: + from pylint.lint import PyLinter + + + def register(linter: "PyLinter") -> None: + """This required method auto registers the checker during initialization. + + :param linter: The linter to register the checker to. + """ print('Hello world') @@ -43,7 +55,19 @@ We can extend hello-world plugin to ignore some specific names using .. sourcecode:: python # Inside hello_plugin.py - def register(linter): + from typing import TYPE_CHECKING + + import astroid + + if TYPE_CHECKING: + from pylint.lint import PyLinter + + + def register(linter: "PyLinter") -> None: + """This required method auto registers the checker during initialization. + + :param linter: The linter to register the checker to. + """ print('Hello world') def load_configuration(linter): diff --git a/doc/how_tos/transform_plugins.rst b/doc/how_tos/transform_plugins.rst index 30e057820d..031faa0f13 100644 --- a/doc/how_tos/transform_plugins.rst +++ b/doc/how_tos/transform_plugins.rst @@ -66,10 +66,19 @@ Module, Class, Function etc. In our case we need to transform a class. It can be .. sourcecode:: python + from typing import TYPE_CHECKING + import astroid - def register(linter): - # Needed for registering the plugin. + if TYPE_CHECKING: + from pylint.lint import PyLinter + + + def register(linter: "PyLinter") -> None: + """This required method auto registers the checker during initialization. + + :param linter: The linter to register the checker to. + """ pass def transform(cls): diff --git a/examples/custom.py b/examples/custom.py index c7429f629f..695f77d20d 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -1,11 +1,16 @@ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter -# This is our checker class. # Checkers should always inherit from `BaseChecker`. + + class MyAstroidChecker(BaseChecker): """Add class member attributes to the class local's dictionary.""" @@ -60,10 +65,9 @@ def visit_call(self, node: nodes.Call) -> None: in_class.locals[param.name] = node -def register(linter): - """This required method auto registers the checker. +def register(linter: "PyLinter") -> None: + """This required method auto registers the checker during initialization. :param linter: The linter to register the checker to. - :type linter: pylint.lint.PyLinter """ linter.register_checker(MyAstroidChecker(linter)) diff --git a/examples/custom_raw.py b/examples/custom_raw.py index 5f747869f8..045f22ff49 100644 --- a/examples/custom_raw.py +++ b/examples/custom_raw.py @@ -1,8 +1,13 @@ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.interfaces import IRawChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class MyRawChecker(BaseChecker): """check for line continuations with '\' instead of using triple @@ -35,6 +40,9 @@ def process_module(self, node: nodes.Module) -> None: self.add_message("backslash-line-continuation", line=lineno) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: + """This required method auto registers the checker during initialization. + + :param linter: The linter to register the checker to. + """ linter.register_checker(MyRawChecker(linter)) diff --git a/examples/deprecation_checker.py b/examples/deprecation_checker.py index 93910b724c..d3dca4e07d 100644 --- a/examples/deprecation_checker.py +++ b/examples/deprecation_checker.py @@ -38,11 +38,14 @@ def mymethod(self, arg0, arg1, deprecated1=None, arg2='foo', deprecated2='bar', ------------------------------------------------------------------ Your code has been rated at 2.00/10 (previous run: 2.00/10, +0.00) """ -from typing import Set, Tuple, Union +from typing import TYPE_CHECKING, Set, Tuple, Union from pylint.checkers import BaseChecker, DeprecatedMixin from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class DeprecationChecker(DeprecatedMixin, BaseChecker): """Class implementing deprecation checker.""" @@ -90,10 +93,5 @@ def deprecated_arguments( return () -def register(linter): - """This required method auto registers the checker. - - :param linter: The linter to register the checker to. - :type linter: pylint.lint.PyLinter - """ +def register(linter: "PyLinter") -> None: linter.register_checker(DeprecationChecker(linter)) diff --git a/pylint/checkers/async.py b/pylint/checkers/async.py index 9aaead7c4f..4a5c6abf25 100644 --- a/pylint/checkers/async.py +++ b/pylint/checkers/async.py @@ -11,6 +11,7 @@ """Checker for anything related to the async protocol (PEP 492).""" import sys +from typing import TYPE_CHECKING import astroid from astroid import nodes @@ -19,6 +20,9 @@ from pylint.checkers import utils as checker_utils from pylint.checkers.utils import decorated_with +if TYPE_CHECKING: + from pylint.lint import PyLinter + class AsyncChecker(checkers.BaseChecker): __implements__ = interfaces.IAstroidChecker @@ -94,6 +98,5 @@ def visit_asyncwith(self, node: nodes.AsyncWith) -> None: ) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(AsyncChecker(linter)) diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 652330f116..39ab5d673b 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -73,7 +73,7 @@ import itertools import re import sys -from typing import Any, Dict, Iterator, Optional, Pattern, cast +from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Pattern, cast import astroid from astroid import nodes @@ -91,6 +91,9 @@ from pylint.utils import LinterStats from pylint.utils.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + if sys.version_info >= (3, 8): from typing import Literal else: @@ -2585,8 +2588,7 @@ def _check_type_x_is_y(self, node, left, operator, right): self.add_message("unidiomatic-typecheck", node=node) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(BasicErrorChecker(linter)) linter.register_checker(BasicChecker(linter)) linter.register_checker(NameChecker(linter)) diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index e46eb583d3..2b0e595e64 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -1,12 +1,15 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +from typing import TYPE_CHECKING from pylint.checkers.classes.class_checker import ClassChecker from pylint.checkers.classes.special_methods_checker import SpecialMethodsChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter -def register(linter): - """required method to auto register this checker""" + +def register(linter: "PyLinter") -> None: linter.register_checker(ClassChecker(linter)) linter.register_checker(SpecialMethodsChecker(linter)) diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index 8a26234869..ec518e857c 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -32,7 +32,7 @@ import re from collections import defaultdict -from typing import FrozenSet, Iterator, List, Set, cast +from typing import TYPE_CHECKING, FrozenSet, Iterator, List, Set, cast import astroid from astroid import nodes @@ -42,6 +42,9 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass "R0901": ( "Too many ancestors (%s/%s)", @@ -662,6 +665,5 @@ def _inc_branch(self, node, branchesnum=1): self._branches[node.scope()] += branchesnum -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(MisdesignChecker(linter)) diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py index ea7a260ec4..e5d4c4ecd9 100644 --- a/pylint/checkers/ellipsis_checker.py +++ b/pylint/checkers/ellipsis_checker.py @@ -1,11 +1,15 @@ """Ellipsis checker for Python code """ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter + +if TYPE_CHECKING: + from pylint.lint import PyLinter class EllipsisChecker(BaseChecker): @@ -45,6 +49,5 @@ def visit_const(self, node: nodes.Const) -> None: self.add_message("unnecessary-ellipsis", node=node) -def register(linter: PyLinter) -> None: - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(EllipsisChecker(linter)) diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index b9b6ceb042..0d60b0a395 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -36,7 +36,7 @@ """Checks for various exception related errors.""" import builtins import inspect -from typing import Any, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional import astroid from astroid import nodes, objects @@ -44,6 +44,9 @@ from pylint import checkers, interfaces from pylint.checkers import utils +if TYPE_CHECKING: + from pylint.lint import PyLinter + def _builtin_exceptions(): def predicate(obj): @@ -571,6 +574,5 @@ def visit_tryexcept(self, node: nodes.TryExcept) -> None: exceptions_classes += [exc for _, exc in exceptions] -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(ExceptionsChecker(linter)) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 0cee7b90b2..17a50911cf 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -56,7 +56,7 @@ import tokenize from functools import reduce -from typing import List +from typing import TYPE_CHECKING, List from astroid import nodes @@ -71,6 +71,10 @@ from pylint.interfaces import IAstroidChecker, IRawChecker, ITokenChecker from pylint.utils.pragma_parser import OPTION_PO, PragmaParserError, parse_pragma +if TYPE_CHECKING: + from pylint.lint import PyLinter + + _ASYNC_TOKEN = "async" _KEYWORD_TOKENS = [ "assert", @@ -819,6 +823,5 @@ def check_indent_level(self, string, expected, line_num): ) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(FormatChecker(linter)) diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index ddec800e0d..528f7d80ca 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -52,7 +52,7 @@ import os import sys from distutils import sysconfig -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union import astroid from astroid import nodes @@ -69,10 +69,12 @@ from pylint.exceptions import EmptyReportError from pylint.graph import DotBackend, get_cycles from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter from pylint.reporters.ureports.nodes import Paragraph, Section, VerbatimText from pylint.utils import IsortDriver, get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + def _qualified_names(modname): """Split the names of the given module into subparts @@ -426,7 +428,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker): ) def __init__( - self, linter: Optional[PyLinter] = None + self, linter: Optional["PyLinter"] = None ): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941 BaseChecker.__init__(self, linter) self.import_graph: collections.defaultdict = collections.defaultdict(set) @@ -1018,6 +1020,5 @@ def _check_toplevel(self, node): ) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(ImportsChecker(linter)) diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index 242c5e800d..19e04c7d1b 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -26,7 +26,7 @@ """checker for use of Python logging """ import string -from typing import Set +from typing import TYPE_CHECKING, Set import astroid from astroid import nodes @@ -35,6 +35,9 @@ from pylint.checkers import utils from pylint.checkers.utils import check_messages, infer_all +if TYPE_CHECKING: + from pylint.lint import PyLinter + MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass "W1201": ( "Use %s formatting in logging functions", @@ -401,6 +404,5 @@ def _count_supplied_tokens(args): return sum(1 for arg in args if not isinstance(arg, nodes.Keyword)) -def register(linter): - """Required method to auto-register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(LoggingChecker(linter)) diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index 8090679e84..4eb52336db 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -29,7 +29,7 @@ import re import tokenize -from typing import List, Optional +from typing import TYPE_CHECKING, List, Optional from astroid import nodes @@ -38,6 +38,9 @@ from pylint.typing import ManagedMessage from pylint.utils.pragma_parser import OPTION_PO, PragmaParserError, parse_pragma +if TYPE_CHECKING: + from pylint.lint import PyLinter + class ByIdManagedMessagesChecker(BaseChecker): @@ -195,7 +198,6 @@ def process_tokens(self, tokens): ) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(EncodingChecker(linter)) linter.register_checker(ByIdManagedMessagesChecker(linter)) diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index 624f60bddf..b19a5c7ef5 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -22,6 +22,8 @@ """check for new / old style related problems """ +from typing import TYPE_CHECKING + import astroid from astroid import nodes @@ -29,6 +31,9 @@ from pylint.checkers.utils import check_messages, has_known_bases, node_frame_class from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + MSGS = { "E1003": ( "Bad first argument %r given to super()", @@ -132,6 +137,5 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: visit_asyncfunctiondef = visit_functiondef -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(NewStyleConflictChecker(linter)) diff --git a/pylint/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index dc49834ef2..379665eace 100644 --- a/pylint/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py @@ -18,7 +18,7 @@ import sys import tokenize -from typing import Any, Optional, cast +from typing import TYPE_CHECKING, Any, Optional, cast from pylint.checkers import BaseTokenChecker from pylint.interfaces import ITokenChecker @@ -30,6 +30,9 @@ else: from typing_extensions import Literal +if TYPE_CHECKING: + from pylint.lint import PyLinter + def report_raw_stats( sect, @@ -122,6 +125,5 @@ def get_type(tokens, start_index): return i, pos[0] - start[0] + 1, line_type -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(RawMetricsChecker(linter)) diff --git a/pylint/checkers/refactoring/__init__.py b/pylint/checkers/refactoring/__init__.py index c43b6c3643..8c873c6988 100644 --- a/pylint/checkers/refactoring/__init__.py +++ b/pylint/checkers/refactoring/__init__.py @@ -38,6 +38,8 @@ """Looks for code which can be refactored.""" +from typing import TYPE_CHECKING + from pylint.checkers.refactoring.implicit_booleaness_checker import ( ImplicitBooleanessChecker, ) @@ -45,6 +47,9 @@ from pylint.checkers.refactoring.recommendation_checker import RecommendationChecker from pylint.checkers.refactoring.refactoring_checker import RefactoringChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + __all__ = [ "ImplicitBooleanessChecker", "NotChecker", @@ -53,8 +58,7 @@ ] -def register(linter): - """Required method to auto register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(RefactoringChecker(linter)) linter.register_checker(NotChecker(linter)) linter.register_checker(RecommendationChecker(linter)) diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index cadee9f702..51ec27c0bb 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -52,6 +52,7 @@ from io import BufferedIOBase, BufferedReader, BytesIO from itertools import chain, groupby from typing import ( + TYPE_CHECKING, Any, Dict, FrozenSet, @@ -75,6 +76,9 @@ from pylint.reporters.ureports.nodes import Table from pylint.utils import LinterStats, decoding_stream +if TYPE_CHECKING: + from pylint.lint import PyLinter + DEFAULT_MIN_SIMILARITY_LINE = 4 REGEX_FOR_LINES_WITH_CONTENT = re.compile(r".*\w+") @@ -879,8 +883,7 @@ def reduce_map_data(self, linter, data): recombined.close() -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(SimilarChecker(linter)) diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index ad6847478c..012c396ded 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -33,7 +33,7 @@ import os import re import tokenize -from typing import Pattern +from typing import TYPE_CHECKING, Pattern from astroid import nodes @@ -41,6 +41,9 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker, ITokenChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + try: import enchant from enchant.tokenize import ( @@ -470,6 +473,5 @@ def _check_docstring(self, node): self._check_spelling("wrong-spelling-in-docstring", line, start_line + idx) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(SpellingChecker(linter)) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index f77f7a2b3a..7093bb0da3 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -39,14 +39,16 @@ import sys from collections.abc import Iterable -from typing import Any, Dict, Optional, Set +from typing import TYPE_CHECKING, Any, Dict, Optional, Set import astroid from astroid import nodes from pylint.checkers import BaseChecker, DeprecatedMixin, utils from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter + +if TYPE_CHECKING: + from pylint.lint import PyLinter OPEN_FILES_MODE = ("open", "file") OPEN_FILES_ENCODING = ("open", "read_text", "write_text") @@ -447,7 +449,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): } def __init__( - self, linter: Optional[PyLinter] = None + self, linter: Optional["PyLinter"] = None ): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941 BaseChecker.__init__(self, linter) self._deprecated_methods: Set[Any] = set() @@ -723,6 +725,5 @@ def deprecated_decorators(self) -> Iterable: return self._deprecated_decorators -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(StdlibChecker(linter)) diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 064902b5d7..e4dcfc8316 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -41,7 +41,7 @@ import numbers import re import tokenize -from typing import Counter, Iterable +from typing import TYPE_CHECKING, Counter, Iterable import astroid from astroid import nodes @@ -50,6 +50,9 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker, IRawChecker, ITokenChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + _AST_NODE_STR_TYPES = ("__builtin__.unicode", "__builtin__.str", "builtins.str") # Prefixes for both strings and bytes literals per # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals @@ -925,8 +928,7 @@ def _detect_u_string_prefix(self, node: nodes.Const): ) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(StringFormatChecker(linter)) linter.register_checker(StringConstantChecker(linter)) diff --git a/pylint/checkers/threading_checker.py b/pylint/checkers/threading_checker.py index b7ba91d419..cedf46a9ee 100644 --- a/pylint/checkers/threading_checker.py +++ b/pylint/checkers/threading_checker.py @@ -1,12 +1,17 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +from typing import TYPE_CHECKING + from astroid import nodes from pylint import interfaces from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages, safe_infer +if TYPE_CHECKING: + from pylint.lint import PyLinter + class ThreadingChecker(BaseChecker): """Checks for threading module @@ -50,6 +55,5 @@ def visit_with(self, node: nodes.With) -> None: self.add_message("useless-with-lock", node=node, args=qname) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(ThreadingChecker(linter)) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index b4f224c06c..a2239469a3 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -69,7 +69,17 @@ from collections import deque from collections.abc import Sequence from functools import singledispatch -from typing import Any, Callable, Iterator, List, Optional, Pattern, Tuple, Union +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + Optional, + Pattern, + Tuple, + Union, +) import astroid import astroid.exceptions @@ -100,6 +110,9 @@ from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + CallableObjects = Union[ bases.BoundMethod, bases.UnboundMethod, @@ -2090,7 +2103,6 @@ def _check_await_outside_coroutine(self, node: nodes.Await) -> None: self.add_message("await-outside-async", node=node) -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(TypeChecker(linter)) linter.register_checker(IterableChecker(linter)) diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index 52e0b5c757..64078aa689 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -10,6 +10,8 @@ """ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker @@ -19,9 +21,11 @@ uninferable_final_decorators, ) from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter from pylint.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + class UnsupportedVersionChecker(BaseChecker): """Checker for features that are not supported by all python versions @@ -80,6 +84,5 @@ def _check_typing_final(self, node: nodes.Decorators) -> None: ) -def register(linter: PyLinter) -> None: - """Required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(UnsupportedVersionChecker(linter)) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index a32099dfdb..8bfdc197e6 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -63,7 +63,7 @@ import sys from enum import Enum from functools import lru_cache -from typing import Any, DefaultDict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, DefaultDict, List, Optional, Set, Tuple, Union import astroid from astroid import nodes @@ -74,6 +74,9 @@ from pylint.interfaces import HIGH, INFERENCE, INFERENCE_FAILURE, IAstroidChecker from pylint.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + if sys.version_info >= (3, 8): from typing import Literal else: @@ -2527,6 +2530,5 @@ def _check_classdef_metaclasses(self, klass, parent_node): return consumed -def register(linter): - """required method to auto register this checker""" +def register(linter: "PyLinter") -> None: linter.register_checker(VariablesChecker(linter)) diff --git a/pylint/extensions/bad_builtin.py b/pylint/extensions/bad_builtin.py index b90ede1113..197bf231bc 100644 --- a/pylint/extensions/bad_builtin.py +++ b/pylint/extensions/bad_builtin.py @@ -11,12 +11,17 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE """Checker for deprecated builtins.""" +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + BAD_FUNCTIONS = ["map", "filter"] # Some hints regarding the use of bad builtins. BUILTIN_HINTS = {"map": "Using a list comprehension can be clearer."} @@ -64,10 +69,5 @@ def visit_call(self, node: nodes.Call) -> None: self.add_message("bad-builtin", node=node, args=args) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(BadBuiltinChecker(linter)) diff --git a/pylint/extensions/broad_try_clause.py b/pylint/extensions/broad_try_clause.py index b460850c23..a45dc5fe6b 100644 --- a/pylint/extensions/broad_try_clause.py +++ b/pylint/extensions/broad_try_clause.py @@ -10,12 +10,15 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE """Looks for try/except statements with too much code in the try clause.""" -from typing import Union +from typing import TYPE_CHECKING, Union from astroid import nodes from pylint import checkers, interfaces +if TYPE_CHECKING: + from pylint.lint import PyLinter + class BroadTryClauseChecker(checkers.BaseChecker): """Checks for try clauses with too many lines. @@ -72,6 +75,5 @@ def visit_tryfinally(self, node: nodes.TryFinally) -> None: self.visit_tryexcept(node) -def register(linter): - """Required method to auto register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(BroadTryClauseChecker(linter)) diff --git a/pylint/extensions/check_docs.py b/pylint/extensions/check_docs.py index 96ea4311f5..141f512987 100644 --- a/pylint/extensions/check_docs.py +++ b/pylint/extensions/check_docs.py @@ -9,16 +9,15 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import warnings +from typing import TYPE_CHECKING from pylint.extensions import docparams +if TYPE_CHECKING: + from pylint.lint import PyLinter -def register(linter): - """Required method to auto register this checker. - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: warnings.warn( "This plugin is deprecated, use pylint.extensions.docparams instead.", DeprecationWarning, diff --git a/pylint/extensions/check_elif.py b/pylint/extensions/check_elif.py index f70b436846..b4658577e0 100644 --- a/pylint/extensions/check_elif.py +++ b/pylint/extensions/check_elif.py @@ -12,12 +12,17 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseTokenChecker from pylint.checkers.utils import check_messages from pylint.interfaces import HIGH, IAstroidChecker, ITokenChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class ElseifUsedChecker(BaseTokenChecker): """Checks for use of "else if" when an "elif" could be used""" @@ -62,10 +67,5 @@ def visit_if(self, node: nodes.If) -> None: self.add_message("else-if-used", node=node, confidence=HIGH) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(ElseifUsedChecker(linter)) diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index 332ffaa92f..7082c991ce 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -1,14 +1,16 @@ import sys -from typing import List, Optional, Set, Tuple, Type, Union, cast +from typing import TYPE_CHECKING, List, Optional, Set, Tuple, Type, Union, cast from astroid import nodes from pylint.checkers import BaseChecker, utils from pylint.checkers.utils import check_messages, safe_infer from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter from pylint.utils.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + if sys.version_info >= (3, 10): from typing import TypeGuard else: @@ -72,7 +74,7 @@ class CodeStyleChecker(BaseChecker): ), ) - def __init__(self, linter: PyLinter) -> None: + def __init__(self, linter: "PyLinter") -> None: """Initialize checker instance.""" super().__init__(linter=linter) @@ -303,5 +305,5 @@ def _check_ignore_assignment_expr_suggestion( return False -def register(linter: PyLinter) -> None: +def register(linter: "PyLinter") -> None: linter.register_checker(CodeStyleChecker(linter)) diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py index e543f1ccfa..592e15a5b9 100644 --- a/pylint/extensions/comparetozero.py +++ b/pylint/extensions/comparetozero.py @@ -13,7 +13,7 @@ """Looks for comparisons to zero.""" import itertools -from typing import Any, Iterable +from typing import TYPE_CHECKING, Any, Iterable import astroid from astroid import nodes @@ -21,6 +21,9 @@ from pylint import checkers, interfaces from pylint.checkers import utils +if TYPE_CHECKING: + from pylint.lint import PyLinter + def _is_constant_zero(node): return isinstance(node, astroid.Const) and node.value == 0 @@ -77,6 +80,5 @@ def visit_compare(self, node: nodes.Compare) -> None: self.add_message("compare-to-zero", node=node) -def register(linter): - """Required method to auto register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(CompareToZeroChecker(linter)) diff --git a/pylint/extensions/comparison_placement.py b/pylint/extensions/comparison_placement.py index 64c8dee5e9..c4a5cffcb0 100644 --- a/pylint/extensions/comparison_placement.py +++ b/pylint/extensions/comparison_placement.py @@ -7,11 +7,16 @@ """ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker, utils from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + REVERSED_COMPS = {"<": ">", "<=": ">=", ">": "<", ">=": "<="} COMPARISON_OPERATORS = frozenset(("==", "!=", "<", ">", "<=", ">=")) @@ -62,6 +67,5 @@ def visit_compare(self, node: nodes.Compare) -> None: self._check_misplaced_constant(node, left, right, operator) -def register(linter): - """Required method to auto register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(MisplacedComparisonConstantChecker(linter)) diff --git a/pylint/extensions/confusing_elif.py b/pylint/extensions/confusing_elif.py index 7f81fceaae..99588d0b8d 100644 --- a/pylint/extensions/confusing_elif.py +++ b/pylint/extensions/confusing_elif.py @@ -7,12 +7,16 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter + +if TYPE_CHECKING: + from pylint.lint import PyLinter class ConfusingConsecutiveElifChecker(BaseChecker): @@ -50,10 +54,5 @@ def _has_no_else_clause(node: nodes.If): return False -def register(linter: PyLinter): - """This required method auto registers the checker. - - :param linter: The linter to register the checker to. - :type linter: pylint.lint.PyLinter - """ +def register(linter: "PyLinter") -> None: linter.register_checker(ConfusingConsecutiveElifChecker(linter)) diff --git a/pylint/extensions/consider_ternary_expression.py b/pylint/extensions/consider_ternary_expression.py index 3e3e87c6bb..6dabe3613f 100644 --- a/pylint/extensions/consider_ternary_expression.py +++ b/pylint/extensions/consider_ternary_expression.py @@ -1,10 +1,15 @@ """Check for if / assign blocks that can be rewritten with if-expressions.""" +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class ConsiderTernaryExpressionChecker(BaseChecker): @@ -44,10 +49,5 @@ def visit_if(self, node: nodes.If) -> None: self.add_message("consider-ternary-expression", node=node) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(ConsiderTernaryExpressionChecker(linter)) diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 4fadf9458d..8a29a947dc 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -27,7 +27,7 @@ """Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings """ import re -from typing import Optional +from typing import TYPE_CHECKING, Optional import astroid from astroid import nodes @@ -39,6 +39,9 @@ from pylint.interfaces import IAstroidChecker from pylint.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + class DocstringParameterChecker(BaseChecker): """Checker for Sphinx, Google, or Numpy style docstrings @@ -59,9 +62,6 @@ class DocstringParameterChecker(BaseChecker): load-plugins=pylint.extensions.docparams to the ``MASTER`` section of your ``.pylintrc``. - - :param linter: linter object - :type linter: :class:`pylint.lint.PyLinter` """ __implements__ = IAstroidChecker @@ -665,10 +665,5 @@ def _add_raise_message(self, missing_excs, node): ) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(DocstringParameterChecker(linter)) diff --git a/pylint/extensions/docstyle.py b/pylint/extensions/docstyle.py index 7866312b21..c0fed60fe9 100644 --- a/pylint/extensions/docstyle.py +++ b/pylint/extensions/docstyle.py @@ -11,6 +11,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import linecache +from typing import TYPE_CHECKING from astroid import nodes @@ -18,6 +19,9 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import HIGH, IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class DocStringStyleChecker(checkers.BaseChecker): """Checks format of docstrings based on PEP 0257""" @@ -86,10 +90,5 @@ def _check_docstring(self, node_type, node): ) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(DocStringStyleChecker(linter)) diff --git a/pylint/extensions/empty_comment.py b/pylint/extensions/empty_comment.py index 7b98416783..c525403082 100644 --- a/pylint/extensions/empty_comment.py +++ b/pylint/extensions/empty_comment.py @@ -1,8 +1,13 @@ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.interfaces import IRawChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + def is_line_commented(line): """Checks if a `# symbol that is not part of a string was found in line""" @@ -54,5 +59,5 @@ def process_module(self, node: nodes.Module) -> None: self.add_message("empty-comment", line=line_num + 1) -def register(linter): +def register(linter: "PyLinter") -> None: linter.register_checker(CommentChecker(linter)) diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py index 4e466aca4c..15bdd1e584 100644 --- a/pylint/extensions/emptystring.py +++ b/pylint/extensions/emptystring.py @@ -13,13 +13,16 @@ """Looks for comparisons to empty string.""" import itertools -from typing import Any, Iterable +from typing import TYPE_CHECKING, Any, Iterable from astroid import nodes from pylint import checkers, interfaces from pylint.checkers import utils +if TYPE_CHECKING: + from pylint.lint import PyLinter + class CompareToEmptyStringChecker(checkers.BaseChecker): """Checks for comparisons to empty string. @@ -72,6 +75,5 @@ def visit_compare(self, node: nodes.Compare) -> None: self.add_message("compare-to-empty-string", node=node) -def register(linter): - """Required method to auto register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(CompareToEmptyStringChecker(linter)) diff --git a/pylint/extensions/for_any_all.py b/pylint/extensions/for_any_all.py index 2086fa4b11..915fae8a33 100644 --- a/pylint/extensions/for_any_all.py +++ b/pylint/extensions/for_any_all.py @@ -66,8 +66,4 @@ def _build_suggested_string(node: nodes.For, final_return_bool: bool) -> str: def register(linter: "PyLinter") -> None: - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - """ linter.register_checker(ConsiderUsingAnyOrAllChecker(linter)) diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py index 27d246c1ef..d4e752f48c 100644 --- a/pylint/extensions/mccabe.py +++ b/pylint/extensions/mccabe.py @@ -13,6 +13,8 @@ """Module to add McCabe checker class for pylint. """ +from typing import TYPE_CHECKING + from astroid import nodes from mccabe import PathGraph as Mccabe_PathGraph from mccabe import PathGraphingAstVisitor as Mccabe_PathGraphingAstVisitor @@ -21,6 +23,9 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import HIGH, IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class PathGraph(Mccabe_PathGraph): def __init__(self, node): @@ -194,10 +199,5 @@ def visit_module(self, node: nodes.Module) -> None: ) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(McCabeMethodChecker(linter)) diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index 470582b68d..ef6afefea0 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -3,7 +3,7 @@ """Looks for overlapping exceptions.""" -from typing import Any, List, Tuple +from typing import TYPE_CHECKING, Any, List, Tuple import astroid from astroid import nodes @@ -12,6 +12,9 @@ from pylint.checkers import utils from pylint.checkers.exceptions import _annotated_unpack_infer +if TYPE_CHECKING: + from pylint.lint import PyLinter + class OverlappingExceptionsChecker(checkers.BaseChecker): """Checks for two or more exceptions in the same exception handler @@ -81,6 +84,5 @@ def visit_tryexcept(self, node: nodes.TryExcept) -> None: handled_in_clause += [(part, exc)] -def register(linter): - """Required method to auto register this checker.""" +def register(linter: "PyLinter") -> None: linter.register_checker(OverlappingExceptionsChecker(linter)) diff --git a/pylint/extensions/redefined_variable_type.py b/pylint/extensions/redefined_variable_type.py index a2b5f2a203..775f7adaeb 100644 --- a/pylint/extensions/redefined_variable_type.py +++ b/pylint/extensions/redefined_variable_type.py @@ -11,7 +11,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -from typing import List +from typing import TYPE_CHECKING, List from astroid import nodes @@ -19,6 +19,9 @@ from pylint.checkers.utils import check_messages, is_none, node_type from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class MultipleTypesChecker(BaseChecker): """Checks for variable type redefinitions (NoneType excepted) @@ -111,10 +114,5 @@ def visit_assign(self, node: nodes.Assign) -> None: ) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(MultipleTypesChecker(linter)) diff --git a/pylint/extensions/set_membership.py b/pylint/extensions/set_membership.py index c63e268b3c..6ba5166c5b 100644 --- a/pylint/extensions/set_membership.py +++ b/pylint/extensions/set_membership.py @@ -1,9 +1,13 @@ +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter + +if TYPE_CHECKING: + from pylint.lint import PyLinter class SetMembershipChecker(BaseChecker): @@ -21,7 +25,7 @@ class SetMembershipChecker(BaseChecker): ), } - def __init__(self, linter: PyLinter) -> None: + def __init__(self, linter: "PyLinter") -> None: """Initialize checker instance.""" super().__init__(linter=linter) @@ -43,5 +47,5 @@ def _check_in_comparison(self, comparator: nodes.NodeNG) -> None: self.add_message("use-set-for-membership", node=comparator) -def register(linter: PyLinter) -> None: +def register(linter: "PyLinter") -> None: linter.register_checker(SetMembershipChecker(linter)) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 79cb18ed3d..cc68bc35ef 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -1,4 +1,4 @@ -from typing import Dict, List, NamedTuple, Set, Union +from typing import TYPE_CHECKING, Dict, List, NamedTuple, Set, Union import astroid.bases from astroid import nodes @@ -10,9 +10,11 @@ safe_infer, ) from pylint.interfaces import IAstroidChecker -from pylint.lint import PyLinter from pylint.utils.utils import get_global_option +if TYPE_CHECKING: + from pylint.lint import PyLinter + class TypingAlias(NamedTuple): name: str @@ -132,7 +134,7 @@ class TypingChecker(BaseChecker): or Python 3.7+ with postponed evaluation. """ - def __init__(self, linter: PyLinter) -> None: + def __init__(self, linter: "PyLinter") -> None: """Initialize checker instance.""" super().__init__(linter=linter) self._alias_name_collisions: Set[str] = set() @@ -278,5 +280,5 @@ def leave_module(self, node: nodes.Module) -> None: self._consider_using_alias_msgs.clear() -def register(linter: PyLinter) -> None: +def register(linter: "PyLinter") -> None: linter.register_checker(TypingChecker(linter)) diff --git a/pylint/extensions/while_used.py b/pylint/extensions/while_used.py index 8d05ace900..dc9861bac7 100644 --- a/pylint/extensions/while_used.py +++ b/pylint/extensions/while_used.py @@ -1,10 +1,15 @@ """Check for use of while loops.""" +from typing import TYPE_CHECKING + from astroid import nodes from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker +if TYPE_CHECKING: + from pylint.lint import PyLinter + class WhileChecker(BaseChecker): @@ -23,10 +28,5 @@ def visit_while(self, node: nodes.While) -> None: self.add_message("while-used", node=node) -def register(linter): - """Required method to auto register this checker. - - :param linter: Main interface object for Pylint plugins - :type linter: Pylint object - """ +def register(linter: "PyLinter") -> None: linter.register_checker(WhileChecker(linter)) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 023455f68e..887c9357ab 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -752,10 +752,7 @@ def report_order(self): # checkers manipulation methods ############################################ def register_checker(self, checker: checkers.BaseChecker) -> None: - """register a new checker - - checker is an object implementing IRawChecker or / and IAstroidChecker - """ + """This method auto registers the checker.""" assert checker.priority <= 0, "checker priority can't be >= 0" self._checkers[checker.name].append(checker) for r_id, r_title, r_cb in checker.reports: @@ -764,7 +761,6 @@ def register_checker(self, checker: checkers.BaseChecker) -> None: if hasattr(checker, "msgs"): self.msgs_store.register_messages_from_checker(checker) checker.load_defaults() - # Register the checker, but disable all of its messages. if not getattr(checker, "enabled", True): self.disable(checker.name) diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index 84d11d24ae..8761979aa5 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -59,5 +59,4 @@ def _display(self, layout: "Section") -> None: def register(linter: "PyLinter") -> None: - """Register the reporter classes with the linter.""" linter.register_reporter(JSONReporter) diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index a71e1bc7b7..245d2ded18 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -2,21 +2,30 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import collections -from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Tuple +from typing import ( + TYPE_CHECKING, + Callable, + DefaultDict, + Dict, + List, + MutableSequence, + Optional, + Tuple, +) -from pylint import checkers from pylint.exceptions import EmptyReportError from pylint.reporters.ureports.nodes import Section from pylint.utils import LinterStats if TYPE_CHECKING: + from pylint.checkers import BaseChecker from pylint.lint.pylinter import PyLinter -ReportsDict = DefaultDict[checkers.BaseChecker, List[Tuple[str, str, Callable]]] +ReportsDict = DefaultDict["BaseChecker", List[Tuple[str, str, Callable]]] class ReportsHandlerMixIn: - """a mix-in class containing all the reports and stats manipulation + """A mix-in class containing all the reports and stats manipulation related methods for the main lint class """ @@ -24,37 +33,35 @@ def __init__(self) -> None: self._reports: ReportsDict = collections.defaultdict(list) self._reports_state: Dict[str, bool] = {} - def report_order(self) -> List[checkers.BaseChecker]: + def report_order(self) -> MutableSequence["BaseChecker"]: """Return a list of reporters""" return list(self._reports) def register_report( - self, reportid: str, r_title: str, r_cb: Callable, checker: checkers.BaseChecker + self, reportid: str, r_title: str, r_cb: Callable, checker: "BaseChecker" ) -> None: - """register a report + """Register a report - reportid is the unique identifier for the report - r_title the report's title - r_cb the method to call to make the report - checker is the checker defining the report + :param reportid: The unique identifier for the report + :param r_title: The report's title + :param r_cb: The method to call to make the report + :param checker: The checker defining the report """ reportid = reportid.upper() self._reports[checker].append((reportid, r_title, r_cb)) def enable_report(self, reportid: str) -> None: - """disable the report of the given id""" + """Enable the report of the given id""" reportid = reportid.upper() self._reports_state[reportid] = True def disable_report(self, reportid: str) -> None: - """disable the report of the given id""" + """Disable the report of the given id""" reportid = reportid.upper() self._reports_state[reportid] = False def report_is_enabled(self, reportid: str) -> bool: - """return true if the report associated to the given identifier is - enabled - """ + """Is the report associated to the given identifier enabled ?""" return self._reports_state.get(reportid, True) def make_reports( # type: ignore[misc] # ReportsHandlerMixIn is always mixed with PyLinter @@ -62,7 +69,7 @@ def make_reports( # type: ignore[misc] # ReportsHandlerMixIn is always mixed wi stats: LinterStats, old_stats: Optional[LinterStats], ) -> Section: - """render registered reports""" + """Render registered reports""" sect = Section("Report", f"{self.stats.statement} statements analysed.") for checker in self.report_order(): for reportid, r_title, r_cb in self._reports[checker]: diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 7b6e5e1a1d..b69a3a139d 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -352,7 +352,6 @@ def handle_message(self, msg: Message) -> None: def register(linter: "PyLinter") -> None: - """Register the reporter classes with the linter.""" linter.register_reporter(TextReporter) linter.register_reporter(ParseableTextReporter) linter.register_reporter(VSTextReporter) diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index a9a13b72ac..5caaac7946 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -396,10 +396,8 @@ def test_no_args() -> None: def test_get_map_data() -> None: """Tests that a SimilarChecker respects the MapReduceMixin interface""" linter = PyLinter(reporter=Reporter()) - # Add a parallel checker to ensure it can map and reduce linter.register_checker(similar.SimilarChecker(linter)) - source_streams = ( str(INPUT / "similar_lines_a.py"), str(INPUT / "similar_lines_b.py"), diff --git a/tests/regrtest_data/dummy_plugin/dummy_plugin.py b/tests/regrtest_data/dummy_plugin/dummy_plugin.py index dd0554f7e7..22b0ea391e 100644 --- a/tests/regrtest_data/dummy_plugin/dummy_plugin.py +++ b/tests/regrtest_data/dummy_plugin/dummy_plugin.py @@ -26,6 +26,6 @@ class DummyPlugin2(BaseChecker): ) -def register(linter: PyLinter) -> None: +def register(linter: "PyLinter") -> None: linter.register_checker(DummyPlugin1(linter)) linter.register_checker(DummyPlugin2(linter)) From 8a685e934bfb6b9a83f5d9a3fcfa04a397d1b3e9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Dec 2021 07:51:53 +0100 Subject: [PATCH 099/357] [pre-commit.ci] pre-commit autoupdate (#5603) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbcd90a67b..0857eaa500 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: trailing-whitespace exclude: "tests/functional/t/trailing_whitespaces.py|tests/pyreverse/data/.*.html" From 39b52acd856473ca43a829f44d1a1145a326098b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 28 Dec 2021 13:58:51 +0100 Subject: [PATCH 100/357] Refactor message disabling and enabling (#5596) Co-authored-by: Pierre Sassoulas --- pylint/lint/pylinter.py | 73 +++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 887c9357ab..e46577615a 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1617,30 +1617,18 @@ def _set_one_msg_status( else: msgs = self._msgs_state msgs[msg.msgid] = enable - # sync configuration object - self.config.enable = [ - self._message_symbol(mid) for mid, val in sorted(msgs.items()) if val - ] - self.config.disable = [ - self._message_symbol(mid) - for mid, val in sorted(msgs.items()) - if not val - ] - def _set_msg_status( - self, - msgid: str, - enable: bool, - scope: str = "package", - line: Optional[int] = None, - ignore_unknown: bool = False, - ) -> None: - """Do some tests and then iterate over message definitions to set state""" - assert scope in {"package", "module"} + def _get_messages_to_set( + self, msgid: str, enable: bool, ignore_unknown: bool = False + ) -> List[MessageDefinition]: + """Do some tests and find the actual messages of which the status should be set.""" + message_definitions = [] if msgid == "all": for _msgid in MSG_TYPES: - self._set_msg_status(_msgid, enable, scope, line, ignore_unknown) - return + message_definitions.extend( + self._get_messages_to_set(_msgid, enable, ignore_unknown) + ) + return message_definitions # msgid is a category? category_id = msgid.upper() @@ -1650,15 +1638,19 @@ def _set_msg_status( category_id_formatted = category_id if category_id_formatted is not None: for _msgid in self.msgs_store._msgs_by_category.get(category_id_formatted): - self._set_msg_status(_msgid, enable, scope, line) - return + message_definitions.extend( + self._get_messages_to_set(_msgid, enable, ignore_unknown) + ) + return message_definitions # msgid is a checker name? if msgid.lower() in self._checkers: for checker in self._checkers[msgid.lower()]: for _msgid in checker.msgs: - self._set_msg_status(_msgid, enable, scope, line) - return + message_definitions.extend( + self._get_messages_to_set(_msgid, enable, ignore_unknown) + ) + return message_definitions # msgid is report id? if msgid.lower().startswith("rp"): @@ -1666,18 +1658,41 @@ def _set_msg_status( self.enable_report(msgid) else: self.disable_report(msgid) - return + return message_definitions try: # msgid is a symbolic or numeric msgid. message_definitions = self.msgs_store.get_message_definitions(msgid) except exceptions.UnknownMessageError: - if ignore_unknown: - return - raise + if not ignore_unknown: + raise + return message_definitions + + def _set_msg_status( + self, + msgid: str, + enable: bool, + scope: str = "package", + line: Optional[int] = None, + ignore_unknown: bool = False, + ) -> None: + """Do some tests and then iterate over message definitions to set state""" + assert scope in {"package", "module"} + + message_definitions = self._get_messages_to_set(msgid, enable, ignore_unknown) + for message_definition in message_definitions: self._set_one_msg_status(scope, message_definition, line, enable) + # sync configuration object + self.config.enable = [] + self.config.disable = [] + for mid, val in self._msgs_state.items(): + if val: + self.config.enable.append(self._message_symbol(mid)) + else: + self.config.disable.append(self._message_symbol(mid)) + def _register_by_id_managed_msg( self, msgid_or_symbol: str, line: Optional[int], is_disabled: bool = True ) -> None: From 8367a571203a3cf0242394e21a24a67566de44f8 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Tue, 28 Dec 2021 20:56:53 +0530 Subject: [PATCH 101/357] Fix reported node for `unnecessary-comprehension` (#5601) --- .../refactoring/refactoring_checker.py | 6 +++-- .../unnecessary/unnecessary_comprehension.py | 6 ++--- .../unnecessary/unnecessary_comprehension.txt | 24 +++++++++---------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index acaf7191cd..d08ad8bdc6 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1589,7 +1589,9 @@ def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None: ): args = (f"{node.iter.as_string()}",) if args: - self.add_message("unnecessary-comprehension", node=node, args=args) + self.add_message( + "unnecessary-comprehension", node=node.parent, args=args + ) return if isinstance(node.parent, nodes.DictComp): @@ -1603,7 +1605,7 @@ def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None: self.add_message( "unnecessary-comprehension", - node=node, + node=node.parent, args=(f"{func}({node.iter.as_string()})",), ) diff --git a/tests/functional/u/unnecessary/unnecessary_comprehension.py b/tests/functional/u/unnecessary/unnecessary_comprehension.py index 3d76f6e66c..2647898c90 100644 --- a/tests/functional/u/unnecessary/unnecessary_comprehension.py +++ b/tests/functional/u/unnecessary/unnecessary_comprehension.py @@ -14,14 +14,14 @@ [(x, y, 1) for x, y in iterable] # exclude useful comprehensions # Test case for issue #4499 a_dict = {} -[(k, v) for k, v in a_dict.items()] # [unnecessary-comprehension] +item = [(k, v) for k, v in a_dict.items()] # [unnecessary-comprehension] # Set comprehensions {x for x in iterable} # [unnecessary-comprehension] {y for x in iterable} # expression != target_list {x for x,y,z in iterable} # expression != target_list {(x,y,z) for x,y,z in iterable} # [unnecessary-comprehension] -{(x,y,z) for (x, y, z) in iterable} # [unnecessary-comprehension] +item = {(x,y,z) for (x, y, z) in iterable} # [unnecessary-comprehension] {(x,y,z) for x in iterable} # expression != target_list {(x,y,(a,b,c)) for x in iterable} # expression != target_list {x for x, *y in iterable} # expression != target_list @@ -44,6 +44,6 @@ my_set = set() [elem for elem in my_list] # [unnecessary-comprehension] -{k: v for k, v in my_dict.items()} # [unnecessary-comprehension] +items = {k: v for k, v in my_dict.items()} # [unnecessary-comprehension] {k: my_dict[k] for k in my_dict} # [consider-using-dict-items] {elem for elem in my_set} # [unnecessary-comprehension] diff --git a/tests/functional/u/unnecessary/unnecessary_comprehension.txt b/tests/functional/u/unnecessary/unnecessary_comprehension.txt index 6093bdba8f..d316fcdc84 100644 --- a/tests/functional/u/unnecessary/unnecessary_comprehension.txt +++ b/tests/functional/u/unnecessary/unnecessary_comprehension.txt @@ -1,13 +1,13 @@ -unnecessary-comprehension:5:0:None:None::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED -unnecessary-comprehension:8:0:None:None::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED -unnecessary-comprehension:9:0:None:None::Unnecessary use of a comprehension, use list(iterable) instead.:UNDEFINED -unnecessary-comprehension:17:0:None:None::Unnecessary use of a comprehension, use list(a_dict.items()) instead.:UNDEFINED -unnecessary-comprehension:20:0:None:None::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED -unnecessary-comprehension:23:0:None:None::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED -unnecessary-comprehension:24:0:None:None::Unnecessary use of a comprehension, use set(iterable) instead.:UNDEFINED -unnecessary-comprehension:32:0:None:None::Unnecessary use of a comprehension, use dict(iterable) instead.:UNDEFINED -unnecessary-comprehension:34:0:None:None::Unnecessary use of a comprehension, use dict(iterable) instead.:UNDEFINED -unnecessary-comprehension:46:0:None:None::Unnecessary use of a comprehension, use my_list instead.:UNDEFINED -unnecessary-comprehension:47:0:None:None::Unnecessary use of a comprehension, use my_dict instead.:UNDEFINED +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:None:None::Unnecessary use of a comprehension, use my_set instead.:UNDEFINED +unnecessary-comprehension:49:0:49:25::Unnecessary use of a comprehension, use my_set instead.:UNDEFINED From efe59ca44b9ed350c9790d79a39a7343af5e7ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 28 Dec 2021 16:28:54 +0100 Subject: [PATCH 102/357] Make functional tests always go through config initialization (#5594) Co-authored-by: Pierre Sassoulas --- pylint/config/config_initialization.py | 3 ++- pylint/testutils/lint_module_test.py | 29 +++++++++++++++++--------- pylint/testutils/testing_pylintrc | 9 ++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 pylint/testutils/testing_pylintrc diff --git a/pylint/config/config_initialization.py b/pylint/config/config_initialization.py index 46031bae3a..d038220302 100644 --- a/pylint/config/config_initialization.py +++ b/pylint/config/config_initialization.py @@ -2,6 +2,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import sys +from pathlib import Path from typing import TYPE_CHECKING, List, Optional, Union from pylint import reporters @@ -15,7 +16,7 @@ def _config_initialization( linter: "PyLinter", args_list: List[str], reporter: Union[reporters.BaseReporter, reporters.MultiReporter, None] = None, - config_file: Optional[str] = None, + config_file: Union[None, str, Path] = None, verbose_mode: Optional[bool] = None, ) -> List[str]: """Parse all available options, read config files and command line arguments and diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index e89f82ace1..8dbf5e60b4 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -7,8 +7,9 @@ import sys from collections import Counter from io import StringIO +from pathlib import Path from typing import Counter as CounterType -from typing import Dict, List, Optional, TextIO, Tuple +from typing import Dict, List, Optional, TextIO, Tuple, Union import pytest from _pytest.config import Config @@ -28,6 +29,8 @@ MessageCounter = CounterType[Tuple[int, str]] +PYLINTRC = Path(__file__).parent / "testing_pylintrc" + class LintModuleTest: maxDiff = None @@ -37,21 +40,27 @@ def __init__( ) -> None: _test_reporter = FunctionalTestReporter() self._linter = PyLinter() - self._linter.set_reporter(_test_reporter) self._linter.config.persistent = 0 checkers.initialize(self._linter) - self._linter.disable("suppressed-message") - self._linter.disable("locally-disabled") - self._linter.disable("useless-suppression") + + # See if test has its own .rc file, if so we use that one + rc_file: Union[Path, str] = PYLINTRC try: - _config_initialization( - self._linter, - [test_file.source], - config_file=test_file.option_file, - ) + rc_file = test_file.option_file + self._linter.disable("suppressed-message") + self._linter.disable("locally-disabled") + self._linter.disable("useless-suppression") except NoFileError: pass + try: + args = [test_file.source] + except NoFileError: + # If we're still raising NoFileError the actual source file doesn't exist + args = [""] + _config_initialization( + self._linter, args_list=args, config_file=rc_file, reporter=_test_reporter + ) self._test_file = test_file self._config = config self._check_end_position = ( diff --git a/pylint/testutils/testing_pylintrc b/pylint/testutils/testing_pylintrc new file mode 100644 index 0000000000..ba5318b510 --- /dev/null +++ b/pylint/testutils/testing_pylintrc @@ -0,0 +1,9 @@ +# A bare minimum pylintrc used for the functional tests that don't specify +# their own settings. + +[MESSAGES CONTROL] + +disable= + suppressed-message, + locally-disabled, + useless-suppression, From f56db7d76429ddd86b3e333b60720c389fbd1025 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 28 Dec 2021 10:30:41 -0500 Subject: [PATCH 103/357] Fix #2399: Avoid negative scores by default (#5595) --- ChangeLog | 4 ++++ doc/faq.rst | 15 ++++++++------- doc/whatsnew/2.13.rst | 4 ++++ examples/pylintrc | 2 +- pylint/lint/pylinter.py | 4 ++-- pylintrc | 2 +- tests/test_self.py | 9 +++++++++ tests/unittest_reporting.py | 4 ++-- 8 files changed, 31 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index 77e77dfe56..87cf8b765a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -96,6 +96,10 @@ Release date: TBA * ``fatal`` was added to the variables permitted in score evaluation expressions. +* The default score evaluation now uses a floor of 0. + + Closes #2399 + * Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. Closes #5312 diff --git a/doc/faq.rst b/doc/faq.rst index a2c0f2252d..47dc9b501c 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -249,15 +249,16 @@ default value by changing the mixin-class-rgx option. 6.1 Pylint gave my code a negative rating out of ten. That can't be right! -------------------------------------------------------------------------- -Even though the final rating Pylint renders is nominally out of ten, there's no -lower bound on it. By default, the formula to calculate score is :: +Prior to Pylint 2.13.0, the score formula used by default had no lower +bound. The new default score formula is :: - 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -However, this option can be changed in the Pylint rc file. If having negative -values really bugs you, you can set the formula to be the maximum of 0 and the -above expression. + max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) +If your project contains a configuration file created by an earlier version of +Pylint, you can set ``evaluation`` to the above expression to get the new +behavior. Likewise, since negative values are still technically supported, +``evaluation`` can be set to a version of the above expression that does not +enforce a floor of zero. 6.2 I think I found a bug in Pylint. What should I do? ------------------------------------------------------- diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 219aec5124..bf38d767ed 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -104,6 +104,10 @@ Other Changes * ``fatal`` was added to the variables permitted in score evaluation expressions. +* The default score evaluation now uses a floor of 0. + + Closes #2399 + * Fix ``comparison-with-callable`` false positive for callables that raise, such as typing constants. diff --git a/examples/pylintrc b/examples/pylintrc index eb48b4a1fe..8fb3c179a6 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -94,7 +94,7 @@ enable=c-extension-no-member # which contain the number of messages in each category, as well as 'statement' # which is the total number of statements analyzed. This score is used by the # global evaluation report (RP0004). -evaluation=0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details. diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index e46577615a..db316caed1 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -310,8 +310,8 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]: "metavar": "", "group": "Reports", "level": 1, - "default": "0 if fatal else 10.0 - ((float(5 * error + warning + refactor + " - "convention) / statement) * 10)", + "default": "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + " + "convention) / statement) * 10))", "help": "Python expression which should return a score less " "than or equal to 10. You have access to the variables 'fatal', " "'error', 'warning', 'refactor', 'convention', and 'info' which " diff --git a/pylintrc b/pylintrc index 8dec450744..4e093a87c5 100644 --- a/pylintrc +++ b/pylintrc @@ -120,7 +120,7 @@ reports=no # and 'info', which contain the number of messages in each category, as # well as 'statement', which is the total number of statements analyzed. This # score is used by the global evaluation report (RP0004). -evaluation=0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details diff --git a/tests/test_self.py b/tests/test_self.py index 27bf17cf65..9f33d745c9 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -718,11 +718,15 @@ def test_fail_under(self) -> None: ], code=0, ) + # Need the old evaluation formula to test a negative score + # failing below a negative --fail-under threshold self._runtest( [ "--fail-under", "-9", "--enable=all", + "--evaluation", + "0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)", join(HERE, "regrtest_data", "fail_under_minus10.py"), ], code=22, @@ -732,6 +736,8 @@ def test_fail_under(self) -> None: "--fail-under", "-5", "--enable=all", + "--evaluation", + "0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)", join(HERE, "regrtest_data", "fail_under_minus10.py"), ], code=22, @@ -777,6 +783,9 @@ def test_fail_on(self, fu_score, fo_msgs, fname, out): f"--fail-on={fo_msgs}", "--enable=all", join(HERE, "regrtest_data", fname), + # Use the old form of the evaluation that can go negative + "--evaluation", + "0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)", ], code=out, ) diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index dbf2eb1fda..29aac8640a 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -332,8 +332,8 @@ def test_multi_format_output(tmp_path): "\n" "\n" "\n" - "-------------------------------------\n" - "Your code has been rated at -10.00/10\n" + "-----------------------------------\n" + "Your code has been rated at 0.00/10\n" "\n" "direct output\n" ) From e815843293adf100662fe4e183c34f3040529a15 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 28 Dec 2021 20:54:28 +0100 Subject: [PATCH 104/357] Get the msgid directly from the MessageIdStore (#5606) We don't need to create message definitions to get their msgids. The level of indirection may suggest we need to refactor a little here. Maybe we actually need message definition only when we want to display the messages. Here's the current implementation of get_message_definitions: def get_message_definitions(self, msgid_or_symbol: str) -> List[MessageDefinition]: """Returns the Message definition for either a numeric or symbolic id.""" return [ self._messages_definitions[m] for m in self.message_id_store.get_active_msgids(msgid_or_symbol) ] --- pylint/lint/pylinter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index db316caed1..ec276d42df 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1435,8 +1435,7 @@ def is_message_enabled( if confidence.name not in self.config.confidence: return False try: - message_definitions = self.msgs_store.get_message_definitions(msg_descr) - msgids = [md.msgid for md in message_definitions] + msgids = self.msgs_store.message_id_store.get_active_msgids(msg_descr) except exceptions.UnknownMessageError: # The linter checks for messages that are not registered # due to version mismatch, just treat them as message IDs From 532be9afa75bd090a389db0f172c47ba9d37b411 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 29 Dec 2021 11:13:27 +0100 Subject: [PATCH 105/357] Add caching to bottlenecks in the message store (#5605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some functions can't be cached without impacting the correctness with the current design. Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- pylint/lint/pylinter.py | 17 +++++++++++++---- pylint/message/message_definition_store.py | 9 ++++++++- pylint/message/message_id_store.py | 18 ++++++++++++------ 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index ec276d42df..1d6b5a3b1a 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1389,7 +1389,11 @@ def _get_message_state_scope( return None def _is_one_message_enabled(self, msgid: str, line: Optional[int]) -> bool: - """Checks state of a single message""" + """Checks state of a single message for the current file + + This function can't be cached as it depends on self.file_state which can + change. + """ if line is None: return self._msgs_state.get(msgid, True) try: @@ -1426,10 +1430,15 @@ def is_message_enabled( line: Optional[int] = None, confidence: Optional[interfaces.Confidence] = None, ) -> bool: - """return whether the message associated to the given message id is - enabled + """Return whether this message is enabled for the current file, line and confidence level. + + This function can't be cached right now as the line is the line of + the currently analysed file (self.file_state), if it changes, then the + result for the same msg_descr/line might need to change. - msgid may be either a numeric or symbolic message id. + :param msg_descr: Either the msgid or the symbol for a MessageDefinition + :param line: The line of the currently analysed file + :param confidence: The confidence of the message """ if self.config.confidence and confidence: if confidence.name not in self.config.confidence: diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py index c160a85bab..766cdd4465 100644 --- a/pylint/message/message_definition_store.py +++ b/pylint/message/message_definition_store.py @@ -2,6 +2,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import collections +import functools from typing import TYPE_CHECKING, Dict, List, Tuple, ValuesView from pylint.exceptions import UnknownMessageError @@ -46,8 +47,14 @@ def register_message(self, message: MessageDefinition) -> None: self._messages_definitions[message.msgid] = message self._msgs_by_category[message.msgid[0]].append(message.msgid) + @functools.lru_cache() def get_message_definitions(self, msgid_or_symbol: str) -> List[MessageDefinition]: - """Returns the Message definition for either a numeric or symbolic id.""" + """Returns the Message definition for either a numeric or symbolic id. + + The cache has no limit as its size will likely stay minimal. For each message we store + about 1000 characters, so even if we would have 1000 messages the cache would only + take up ~= 1 Mb. + """ return [ self._messages_definitions[m] for m in self.message_id_store.get_active_msgids(msgid_or_symbol) diff --git a/pylint/message/message_id_store.py b/pylint/message/message_id_store.py index 1fbe684712..a16d12bfed 100644 --- a/pylint/message/message_id_store.py +++ b/pylint/message/message_id_store.py @@ -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/main/LICENSE +import functools from typing import Dict, List, NoReturn, Optional, Tuple from pylint.exceptions import InvalidMessageError, UnknownMessageError @@ -101,18 +102,23 @@ def _raise_duplicate_msgid(symbol: str, msgid: str, other_msgid: str) -> NoRetur ) raise InvalidMessageError(error_message) + @functools.lru_cache() def get_active_msgids(self, msgid_or_symbol: str) -> List[str]: - """Return msgids but the input can be a symbol.""" - # Only msgid can have a digit as second letter - is_msgid: bool = msgid_or_symbol[1:].isdigit() - msgid = None - if is_msgid: + """Return msgids but the input can be a symbol. + + The cache has no limit as its size will likely stay minimal. For each message we store + about 1000 characters, so even if we would have 1000 messages the cache would only + take up ~= 1 Mb. + """ + msgid: Optional[str] + if msgid_or_symbol[1:].isdigit(): + # Only msgid can have a digit as second letter msgid = msgid_or_symbol.upper() symbol = self.__msgid_to_symbol.get(msgid) else: msgid = self.__symbol_to_msgid.get(msgid_or_symbol) symbol = msgid_or_symbol - if msgid is None or symbol is None or not msgid or not symbol: + if not msgid or not symbol: error_msg = f"No such message id or symbol '{msgid_or_symbol}'." raise UnknownMessageError(error_msg) return self.__old_names.get(msgid, [msgid]) From 3d3d48f80418d26ea64c73d510483b0a9e0a0698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 30 Dec 2021 14:11:23 +0100 Subject: [PATCH 106/357] Update CACHE_VERSION of primer, add comment and update paths (#5611) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .github/workflows/ci.yaml | 1 + .github/workflows/primer-test.yaml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e0eeef0793..4a2118b16e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,6 +8,7 @@ on: pull_request: ~ env: + # Also change CACHE_VERSION in the primer workflow CACHE_VERSION: 4 DEFAULT_PYTHON: 3.8 PRE_COMMIT_CACHE: ~/.cache/pre-commit diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index 08e9d125d4..54ee542821 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -10,9 +10,11 @@ on: - "pylint/**" - "tests/primer/**" - "requirements*" + - ".github/workflows/primer-test.yaml" env: - CACHE_VERSION: 3 + # Also change CACHE_VERSION in the CI workflow + CACHE_VERSION: 4 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} From 019794b808271d45f86a7014e9c91cb04458a47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 30 Dec 2021 15:14:20 +0100 Subject: [PATCH 107/357] Use ``dill`` to pickle when run in parallel mode (#5609) --- ChangeLog | 3 +++ doc/whatsnew/2.13.rst | 3 +++ pylint/lint/parallel.py | 49 +++++++++++++++++++++++++----------- setup.cfg | 4 +++ tests/test_check_parallel.py | 25 +++++++++++++++--- 5 files changed, 65 insertions(+), 19 deletions(-) diff --git a/ChangeLog b/ChangeLog index 87cf8b765a..19a3b73ea9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,9 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with + the ``dill`` package. The ``dill`` package has therefore been added as a dependency. + * ``used-before-assignment`` now considers that assignments in a try block may not have occurred when the except or finally blocks are executed. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index bf38d767ed..9510cd54a9 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -32,6 +32,9 @@ Extensions Other Changes ============= +* When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with + the ``dill`` package. The ``dill`` package has therefore been added as a dependency. + * By default, pylint does no longer take files starting with ``.#`` into account. Those are considered `emacs file locks`_. This behavior can be reverted by redefining the ``ignore-patterns`` option. diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index eaacb710d7..6dbeed9833 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -3,7 +3,18 @@ import collections import functools -from typing import Any, DefaultDict, Iterable, List, Tuple +from typing import ( + TYPE_CHECKING, + Any, + DefaultDict, + Iterable, + List, + Sequence, + Tuple, + Union, +) + +import dill from pylint import reporters from pylint.lint.utils import _patch_sys_path @@ -16,6 +27,9 @@ except ImportError: multiprocessing = None # type: ignore[assignment] +if TYPE_CHECKING: + from pylint.lint import PyLinter + # PyLinter object used by worker processes when checking files using multiprocessing # should only be used by the worker processes _worker_linter = None @@ -33,9 +47,16 @@ def _get_new_args(message): return (message.msg_id, message.symbol, location, message.msg, message.confidence) -def _worker_initialize(linter, arguments=None): +def _worker_initialize( + linter: bytes, arguments: Union[None, str, Sequence[str]] = None +) -> None: + """Function called to initialize a worker for a Process within a multiprocessing Pool + + :param linter: A linter-class (PyLinter) instance pickled with dill + :param arguments: File or module name(s) to lint and to be added to sys.path + """ global _worker_linter # pylint: disable=global-statement - _worker_linter = linter + _worker_linter = dill.loads(linter) # On the worker process side the messages are just collected and passed back to # parent process as _worker_check_file function's return value @@ -97,26 +118,24 @@ def _merge_mapreduce_data(linter, all_mapreduce_data): checker.reduce_map_data(linter, collated_map_reduce_data[checker.name]) -def check_parallel(linter, jobs, files: Iterable[FileItem], arguments=None): +def check_parallel( + linter: "PyLinter", + jobs: int, + files: Iterable[FileItem], + arguments: Union[None, str, Sequence[str]] = None, +) -> None: """Use the given linter to lint the files with given amount of workers (jobs) This splits the work filestream-by-filestream. If you need to do work across multiple files, as in the similarity-checker, then inherit from MapReduceMixin and - implement the map/reduce mixin functionality""" - # The reporter does not need to be passed to worker processes, i.e. the reporter does - original_reporter = linter.reporter - linter.reporter = None - + implement the map/reduce mixin functionality. + """ # The linter is inherited by all the pool's workers, i.e. the linter # is identical to the linter object here. This is required so that # a custom PyLinter object can be used. initializer = functools.partial(_worker_initialize, arguments=arguments) pool = multiprocessing.Pool( # pylint: disable=consider-using-with - jobs, initializer=initializer, initargs=[linter] + jobs, initializer=initializer, initargs=[dill.dumps(linter)] ) - # ...and now when the workers have inherited the linter, the actual reporter - # can be set back here on the parent process so that results get stored into - # correct reporter - linter.set_reporter(original_reporter) linter.open() try: all_stats = [] @@ -141,7 +160,7 @@ def check_parallel(linter, jobs, files: Iterable[FileItem], arguments=None): msg = Message( msg[0], msg[1], MessageLocationTuple(*msg[2]), msg[3], msg[4] ) - linter.reporter.handle_message(msg) # type: ignore[attr-defined] # linter.set_reporter() call above makes linter have a reporter attr + linter.reporter.handle_message(msg) all_stats.append(stats) all_mapreduce_data[worker_idx].append(mapreduce_data) linter.msg_status |= msg_status diff --git a/setup.cfg b/setup.cfg index 2a0bb250a1..8b438e6459 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,7 @@ project_urls = [options] packages = find: install_requires = + dill>=0.2 platformdirs>=2.2.0 astroid>=2.9.0,<2.10 # (You should also upgrade requirements_test_min.txt) isort>=4.2.5,<6 @@ -123,3 +124,6 @@ ignore_missing_imports = True [mypy-git.*] ignore_missing_imports = True + +[mypy-dill] +ignore_missing_imports = True diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 01fc352dc4..4fa49d1486 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -11,9 +11,12 @@ # pylint: disable=protected-access,missing-function-docstring,no-self-use +import argparse +import multiprocessing import os from typing import List +import dill import pytest from astroid import nodes @@ -174,8 +177,22 @@ def teardown_class(self): def test_worker_initialize(self) -> None: linter = PyLinter(reporter=Reporter()) - worker_initialize(linter=linter) - assert pylint.lint.parallel._worker_linter == linter + worker_initialize(linter=dill.dumps(linter)) + assert isinstance(pylint.lint.parallel._worker_linter, type(linter)) + + def test_worker_initialize_pickling(self) -> None: + """Test that we can pickle objects that standard pickling in multiprocessing can't. + + See: + https://stackoverflow.com/questions/8804830/python-multiprocessing-picklingerror-cant-pickle-type-function + https://github.com/PyCQA/pylint/pull/5584 + """ + linter = PyLinter(reporter=Reporter()) + linter.attribute = argparse.ArgumentParser() # type: ignore[attr-defined] + pool = multiprocessing.Pool( # pylint: disable=consider-using-with + 2, initializer=worker_initialize, initargs=[dill.dumps(linter)] + ) + pool.imap_unordered(print, [1, 2]) def test_worker_check_single_file_uninitialised(self) -> None: pylint.lint.parallel._worker_linter = None @@ -186,7 +203,7 @@ def test_worker_check_single_file_uninitialised(self) -> None: def test_worker_check_single_file_no_checkers(self) -> None: linter = PyLinter(reporter=Reporter()) - worker_initialize(linter=linter) + worker_initialize(linter=dill.dumps(linter)) ( _, # proc-id @@ -225,7 +242,7 @@ def test_worker_check_single_file_no_checkers(self) -> None: def test_worker_check_sequential_checker(self) -> None: """Same as test_worker_check_single_file_no_checkers with SequentialTestChecker""" linter = PyLinter(reporter=Reporter()) - worker_initialize(linter=linter) + worker_initialize(linter=dill.dumps(linter)) # Add the only checker we care about in this test linter.register_checker(SequentialTestChecker(linter)) From 30d1385599beefc8f65f3c71cbbb52666bf1420b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:49:02 +0100 Subject: [PATCH 108/357] Use ``with`` statement in ``parallel.py`` (#5612) --- pylint/lint/parallel.py | 10 +++------- tests/test_check_parallel.py | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index 6dbeed9833..b382c42ad8 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -133,11 +133,10 @@ def check_parallel( # is identical to the linter object here. This is required so that # a custom PyLinter object can be used. initializer = functools.partial(_worker_initialize, arguments=arguments) - pool = multiprocessing.Pool( # pylint: disable=consider-using-with + with multiprocessing.Pool( jobs, initializer=initializer, initargs=[dill.dumps(linter)] - ) - linter.open() - try: + ) as pool: + linter.open() all_stats = [] all_mapreduce_data = collections.defaultdict(list) @@ -164,9 +163,6 @@ def check_parallel( all_stats.append(stats) all_mapreduce_data[worker_idx].append(mapreduce_data) linter.msg_status |= msg_status - finally: - pool.close() - pool.join() _merge_mapreduce_data(linter, all_mapreduce_data) linter.stats = merge_stats([linter.stats] + all_stats) diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 4fa49d1486..bc27530f3b 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -189,10 +189,10 @@ def test_worker_initialize_pickling(self) -> None: """ linter = PyLinter(reporter=Reporter()) linter.attribute = argparse.ArgumentParser() # type: ignore[attr-defined] - pool = multiprocessing.Pool( # pylint: disable=consider-using-with + with multiprocessing.Pool( 2, initializer=worker_initialize, initargs=[dill.dumps(linter)] - ) - pool.imap_unordered(print, [1, 2]) + ) as pool: + pool.imap_unordered(print, [1, 2]) def test_worker_check_single_file_uninitialised(self) -> None: pylint.lint.parallel._worker_linter = None From 319ba11e45a987367816560c8aa7ecc82c8af4a0 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Thu, 30 Dec 2021 23:51:26 +0530 Subject: [PATCH 109/357] Fix false positive `consider-using-dict-comprehension` when creating a dict using a list of tuple where key AND value vary depending on the same condition (#5590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- .copyrite_aliases | 5 ++++ ChangeLog | 5 ++++ doc/whatsnew/2.13.rst | 5 ++++ .../refactoring/refactoring_checker.py | 25 ++++++++++++++++--- .../consider_using_dict_comprehension.py | 7 +++++- .../consider_using_dict_comprehension.txt | 2 ++ 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/.copyrite_aliases b/.copyrite_aliases index 3dea23b4f7..773b12d61c 100644 --- a/.copyrite_aliases +++ b/.copyrite_aliases @@ -108,5 +108,10 @@ ], "authoritative_mail": "bot@noreply.github.com", "name": "bot" + }, + { + "mails": ["tushar.sadhwani000@gmail.com", "tushar@deepsource.io"], + "authoritative_mail": "tushar.sadhwani000@gmail.com", + "name": "Tushar Sadhwani" } ] diff --git a/ChangeLog b/ChangeLog index 19a3b73ea9..1e8d79d436 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,11 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Fixed false positive ``consider-using-dict-comprehension`` when creating a dict + using a list of tuples where key AND value vary depending on the same condition. + + Closes #5588 + * When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with the ``dill`` package. The ``dill`` package has therefore been added as a dependency. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 9510cd54a9..df1a997570 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -32,6 +32,11 @@ Extensions Other Changes ============= +* Fixed false positive ``consider-using-dict-comprehension`` when creating a dict + using a list of tuples where key AND value vary depending on the same condition. + + Closes #5588 + * When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with the ``dill`` package. The ``dill`` package has therefore been added as a dependency. diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index d08ad8bdc6..40a72a53f3 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -934,9 +934,28 @@ def _check_consider_using_comprehension_constructor(self, node): and node.args and isinstance(node.args[0], nodes.ListComp) ): - if node.func.name == "dict" and not isinstance( - node.args[0].elt, nodes.Call - ): + if node.func.name == "dict": + element = node.args[0].elt + if isinstance(element, nodes.Call): + return + + # If we have an `IfExp` here where both the key AND value + # are different, then don't raise the issue. See #5588 + if ( + isinstance(element, nodes.IfExp) + and isinstance(element.body, (nodes.Tuple, nodes.List)) + and len(element.body.elts) == 2 + and isinstance(element.orelse, (nodes.Tuple, nodes.List)) + and len(element.orelse.elts) == 2 + ): + key1, value1 = element.body.elts + key2, value2 = element.orelse.elts + if ( + key1.as_string() != key2.as_string() + and value1.as_string() != value2.as_string() + ): + return + message_name = "consider-using-dict-comprehension" self.add_message(message_name, node=node) elif node.func.name == "set": diff --git a/tests/functional/c/consider/consider_using_dict_comprehension.py b/tests/functional/c/consider/consider_using_dict_comprehension.py index c9d740e18c..ef9ee3f012 100644 --- a/tests/functional/c/consider/consider_using_dict_comprehension.py +++ b/tests/functional/c/consider/consider_using_dict_comprehension.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring, invalid-name, use-dict-literal +# pylint: disable=missing-docstring, invalid-name, use-dict-literal, line-too-long numbers = [1, 2, 3, 4, 5, 6] @@ -8,5 +8,10 @@ dict([(number, number*2) for number in numbers]) # [consider-using-dict-comprehension] +stuff = {1: 10, 2: -20} +dict([(k, v) if v > 0 else (k, 0) for k, v in stuff.items()]) # [consider-using-dict-comprehension] +dict([(k, v) if v > 0 else (k*2, v) for k, v in stuff.items()]) # [consider-using-dict-comprehension] +dict([(k, v) if v > 0 else (k * 2, 0) for k, v in stuff.items()]) + # Cannot emit as this cannot be written as a comprehension dict([value.split("=") for value in ["a=b", "c=d"]]) diff --git a/tests/functional/c/consider/consider_using_dict_comprehension.txt b/tests/functional/c/consider/consider_using_dict_comprehension.txt index a241d19078..2bde7ffa23 100644 --- a/tests/functional/c/consider/consider_using_dict_comprehension.txt +++ b/tests/functional/c/consider/consider_using_dict_comprehension.txt @@ -1 +1,3 @@ consider-using-dict-comprehension:9:0:9:48::Consider using a dictionary comprehension:UNDEFINED +consider-using-dict-comprehension:12:0:12:61::Consider using a dictionary comprehension:UNDEFINED +consider-using-dict-comprehension:13:0:13:63::Consider using a dictionary comprehension:UNDEFINED From b9fe59736d1c272b2e722a352f281d6686cc40ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 30 Dec 2021 22:11:30 +0100 Subject: [PATCH 110/357] Allow passing arguments when entry points are used as functions (#5613) --- ChangeLog | 5 +++++ doc/user_guide/run.rst | 14 ++++++++++++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/__init__.py | 27 +++++++++++++++++++-------- tests/test_pylint_runners.py | 11 +++++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1e8d79d436..3dc350fb29 100644 --- a/ChangeLog +++ b/ChangeLog @@ -90,6 +90,11 @@ Release date: TBA Closes #5504 +* When invoking ``pylint``, ``symilar`` or ``pyreverse`` by importing them in a python file + you can now pass an ``arguments`` keyword besides patching ``sys.argv``. + + Closes #5320 + * The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. diff --git a/doc/user_guide/run.rst b/doc/user_guide/run.rst index 5f280a4e7c..de28a44136 100644 --- a/doc/user_guide/run.rst +++ b/doc/user_guide/run.rst @@ -45,6 +45,20 @@ thanks to the ``Run()`` function in the ``pylint.lint`` module pylint_opts = ['--disable=line-too-long', 'myfile.py'] pylint.lint.Run(pylint_opts) +Another option would be to use the ``run_pylint`` function, which is the same function +called by the command line. You can either patch ``sys.argv`` or supply arguments yourself: + +.. sourcecode:: python + + import pylint + + sys.argv = ["pylint", "your_file"] + pylint.run_pylint() + + # Or: + + pylint.run_pylint(arguments=["your_file"]) + To silently run Pylint on a ``module_name.py`` module, and get its standard output and error: diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index df1a997570..5b16243cfd 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -121,6 +121,11 @@ Other Changes Closes #5557 +* When invoking ``pylint``, ``symilar`` or ``pyreverse`` by importing them in a python file + you can now pass an ``arguments`` keyword besides patching ``sys.argv``. + + Closes #5320 + * The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. diff --git a/pylint/__init__.py b/pylint/__init__.py index ddf794f861..53ea308bd6 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -11,17 +11,22 @@ import os import sys +from typing import List, Optional from pylint.__pkginfo__ import __version__ # pylint: disable=import-outside-toplevel -def run_pylint(): +def run_pylint(*, arguments: Optional[List[str]] = None): + """Run pylint + + Arguments can be a list of strings normally supplied as arguments on the command line + """ from pylint.lint import Run as PylintRun try: - PylintRun(sys.argv[1:]) + PylintRun(arguments or sys.argv[1:]) except KeyboardInterrupt: sys.exit(1) @@ -32,18 +37,24 @@ def run_epylint(): EpylintRun() -def run_pyreverse(): - """run pyreverse""" +def run_pyreverse(*, arguments: Optional[List[str]] = None): + """Run pyreverse + + Arguments can be a list of strings normally supplied as arguments on the command line + """ from pylint.pyreverse.main import Run as PyreverseRun - PyreverseRun(sys.argv[1:]) + PyreverseRun(arguments or sys.argv[1:]) + +def run_symilar(*, arguments: Optional[List[str]] = None): + """Run symilar -def run_symilar(): - """run symilar""" + Arguments can be a list of strings normally supplied as arguments on the command line + """ from pylint.checkers.similar import Run as SimilarRun - SimilarRun(sys.argv[1:]) + SimilarRun(arguments or sys.argv[1:]) def modify_sys_path() -> None: diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py index 6c44308922..a60a3c8259 100644 --- a/tests/test_pylint_runners.py +++ b/tests/test_pylint_runners.py @@ -21,3 +21,14 @@ def test_runner(runner: Callable, tmpdir: LocalPath) -> None: with pytest.raises(SystemExit) as err: runner() assert err.value.code == 0 + + +@pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar]) +def test_runner_with_arguments(runner: Callable, tmpdir: LocalPath) -> None: + """Check the runners with arguments as parameter instead of sys.argv""" + filepath = os.path.abspath(__file__) + testargs = [filepath] + with tmpdir.as_cwd(): + with pytest.raises(SystemExit) as err: + runner(arguments=testargs) + assert err.value.code == 0 From cf17a454ffb0b5ee47babfc537bdf782a1915da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 31 Dec 2021 10:46:31 +0100 Subject: [PATCH 111/357] Rename ``arguments`` to ``argv`` in entry point functions (#5619) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- pylint/__init__.py | 20 ++++++++++---------- tests/test_pylint_runners.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pylint/__init__.py b/pylint/__init__.py index 53ea308bd6..d758a0db07 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -11,22 +11,22 @@ import os import sys -from typing import List, Optional +from typing import Optional, Sequence from pylint.__pkginfo__ import __version__ # pylint: disable=import-outside-toplevel -def run_pylint(*, arguments: Optional[List[str]] = None): +def run_pylint(argv: Optional[Sequence[str]] = None): """Run pylint - Arguments can be a list of strings normally supplied as arguments on the command line + argv can be a sequence of strings normally supplied as arguments on the command line """ from pylint.lint import Run as PylintRun try: - PylintRun(arguments or sys.argv[1:]) + PylintRun(argv or sys.argv[1:]) except KeyboardInterrupt: sys.exit(1) @@ -37,24 +37,24 @@ def run_epylint(): EpylintRun() -def run_pyreverse(*, arguments: Optional[List[str]] = None): +def run_pyreverse(argv: Optional[Sequence[str]] = None): """Run pyreverse - Arguments can be a list of strings normally supplied as arguments on the command line + argv can be a sequence of strings normally supplied as arguments on the command line """ from pylint.pyreverse.main import Run as PyreverseRun - PyreverseRun(arguments or sys.argv[1:]) + PyreverseRun(argv or sys.argv[1:]) -def run_symilar(*, arguments: Optional[List[str]] = None): +def run_symilar(argv: Optional[Sequence[str]] = None): """Run symilar - Arguments can be a list of strings normally supplied as arguments on the command line + argv can be a sequence of strings normally supplied as arguments on the command line """ from pylint.checkers.similar import Run as SimilarRun - SimilarRun(arguments or sys.argv[1:]) + SimilarRun(argv or sys.argv[1:]) def modify_sys_path() -> None: diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py index a60a3c8259..dbbea28dbc 100644 --- a/tests/test_pylint_runners.py +++ b/tests/test_pylint_runners.py @@ -30,5 +30,5 @@ def test_runner_with_arguments(runner: Callable, tmpdir: LocalPath) -> None: testargs = [filepath] with tmpdir.as_cwd(): with pytest.raises(SystemExit) as err: - runner(arguments=testargs) + runner(testargs) assert err.value.code == 0 From db0060a03a6b14f7ca7eb77ce028c6a660766866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 31 Dec 2021 13:51:30 +0100 Subject: [PATCH 112/357] Allow passing arguments if ``epylint`` entry point is used as function (#5616) --- ChangeLog | 2 +- doc/whatsnew/2.13.rst | 2 +- pylint/__init__.py | 8 ++++++-- pylint/epylint.py | 13 ++++++++----- tests/test_pylint_runners.py | 4 +++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3dc350fb29..297d2f599d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -90,7 +90,7 @@ Release date: TBA Closes #5504 -* When invoking ``pylint``, ``symilar`` or ``pyreverse`` by importing them in a python file +* When invoking ``pylint``, ``epylint``, ``symilar`` or ``pyreverse`` by importing them in a python file you can now pass an ``arguments`` keyword besides patching ``sys.argv``. Closes #5320 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 5b16243cfd..9db50635e5 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -121,7 +121,7 @@ Other Changes Closes #5557 -* When invoking ``pylint``, ``symilar`` or ``pyreverse`` by importing them in a python file +* When invoking ``pylint``, ``epylint``, ``symilar`` or ``pyreverse`` by importing them in a python file you can now pass an ``arguments`` keyword besides patching ``sys.argv``. Closes #5320 diff --git a/pylint/__init__.py b/pylint/__init__.py index d758a0db07..16d81d9036 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -31,10 +31,14 @@ def run_pylint(argv: Optional[Sequence[str]] = None): sys.exit(1) -def run_epylint(): +def run_epylint(argv: Optional[Sequence[str]] = None): + """Run epylint + + argv can be a list of strings normally supplied as arguments on the command line + """ from pylint.epylint import Run as EpylintRun - EpylintRun() + EpylintRun(argv) def run_pyreverse(argv: Optional[Sequence[str]] = None): diff --git a/pylint/epylint.py b/pylint/epylint.py index 98b11afd56..a7eec3302d 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -64,6 +64,7 @@ import sys from io import StringIO from subprocess import PIPE, Popen +from typing import Optional, Sequence def _get_env(): @@ -185,15 +186,17 @@ def py_run(command_options="", return_std=False, stdout=None, stderr=None): return None -def Run(): - if len(sys.argv) == 1: +def Run(argv: Optional[Sequence[str]] = None): + if not argv and len(sys.argv) == 1: print(f"Usage: {sys.argv[0]} [options]") sys.exit(1) - elif not os.path.exists(sys.argv[1]): - print(f"{sys.argv[1]} does not exist") + + argv = argv or sys.argv[1:] + if not os.path.exists(argv[0]): + print(f"{argv[0]} does not exist") sys.exit(1) else: - sys.exit(lint(sys.argv[1], sys.argv[2:])) + sys.exit(lint(argv[0], argv[1:])) if __name__ == "__main__": diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py index dbbea28dbc..88113cb2c6 100644 --- a/tests/test_pylint_runners.py +++ b/tests/test_pylint_runners.py @@ -23,7 +23,9 @@ def test_runner(runner: Callable, tmpdir: LocalPath) -> None: assert err.value.code == 0 -@pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar]) +@pytest.mark.parametrize( + "runner", [run_epylint, run_pylint, run_pyreverse, run_symilar] +) def test_runner_with_arguments(runner: Callable, tmpdir: LocalPath) -> None: """Check the runners with arguments as parameter instead of sys.argv""" filepath = os.path.abspath(__file__) From 1215cf34e8870dccf3225dd35e3948f2b3a7f5b9 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 19 Dec 2021 18:19:56 +0100 Subject: [PATCH 113/357] Upgrade astroid to 2.9.1 Closes #1470 Closes #3499 Closes #4302 Closes #4798 Closes #5081 --- requirements_test_min.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index a0e339897d..955b6683e9 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e . # astroid dependency is also defined in setup.cfg -astroid==2.9.0 # Pinned to a specific version for tests +astroid==2.9.1 # Pinned to a specific version for tests pytest~=6.2 pytest-benchmark~=3.4 gitpython>3 diff --git a/setup.cfg b/setup.cfg index 8b438e6459..1d1a8cc723 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ packages = find: install_requires = dill>=0.2 platformdirs>=2.2.0 - astroid>=2.9.0,<2.10 # (You should also upgrade requirements_test_min.txt) + astroid>=2.9.1,<2.10 # (You should also upgrade requirements_test_min.txt) isort>=4.2.5,<6 mccabe>=0.6,<0.7 toml>=0.9.2 From fa0a611a1a12f3db51cba59ab9258b4d8b1c6b06 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 19 Dec 2021 18:05:32 +0100 Subject: [PATCH 114/357] [regression test] Add functional test for symlinked modules This is a regression test for https://github.com/PyCQA/astroid/issues/1253 Closes #1470 --- ChangeLog | 13 +++++++++++-- doc/whatsnew/2.13.rst | 9 +++++++++ tests/functional/s/symlink/binding/__init__.py | 1 + .../s/symlink/binding/symlinked_module.py | 1 + tests/functional/s/symlink/module/__init__.py | 3 +++ .../functional/s/symlink/module/symlinked_module.py | 6 ++++++ 6 files changed, 31 insertions(+), 2 deletions(-) create mode 120000 tests/functional/s/symlink/binding/__init__.py create mode 120000 tests/functional/s/symlink/binding/symlinked_module.py create mode 100644 tests/functional/s/symlink/module/__init__.py create mode 100644 tests/functional/s/symlink/module/symlinked_module.py diff --git a/ChangeLog b/ChangeLog index 297d2f599d..07ff847160 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,8 +6,6 @@ What's New in Pylint 2.13.0? ============================ Release date: TBA -* Pyreverse - add output in mermaidjs format - .. Put new features here and also in 'doc/whatsnew/2.13.rst' @@ -19,6 +17,17 @@ Release date: TBA * When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with the ``dill`` package. The ``dill`` package has therefore been added as a dependency. +* An astroid issue where symlinks were not being taken into account + was fixed + + Closes #1470 + Closes #3499 + Closes #4302 + Closes #4798 + Closes #5081 + +* Pyreverse - add output in mermaidjs format + * ``used-before-assignment`` now considers that assignments in a try block may not have occurred when the except or finally blocks are executed. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 9db50635e5..e9054ec61e 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -48,6 +48,15 @@ Other Changes .. _`emacs file locks`: https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Locks.html +* An astroid issue where symlinks were not being taken into account + was fixed + + Closes #1470 + Closes #3499 + Closes #4302 + Closes #4798 + Closes #5081 + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/tests/functional/s/symlink/binding/__init__.py b/tests/functional/s/symlink/binding/__init__.py new file mode 120000 index 0000000000..70a3e97c5e --- /dev/null +++ b/tests/functional/s/symlink/binding/__init__.py @@ -0,0 +1 @@ +../module/__init__.py \ No newline at end of file diff --git a/tests/functional/s/symlink/binding/symlinked_module.py b/tests/functional/s/symlink/binding/symlinked_module.py new file mode 120000 index 0000000000..09382e43ee --- /dev/null +++ b/tests/functional/s/symlink/binding/symlinked_module.py @@ -0,0 +1 @@ +../module/symlinked_module.py \ No newline at end of file diff --git a/tests/functional/s/symlink/module/__init__.py b/tests/functional/s/symlink/module/__init__.py new file mode 100644 index 0000000000..cc8f6e4111 --- /dev/null +++ b/tests/functional/s/symlink/module/__init__.py @@ -0,0 +1,3 @@ +"""Example taken from issue #1470""" + +from symlinked_module import func diff --git a/tests/functional/s/symlink/module/symlinked_module.py b/tests/functional/s/symlink/module/symlinked_module.py new file mode 100644 index 0000000000..c28a97a303 --- /dev/null +++ b/tests/functional/s/symlink/module/symlinked_module.py @@ -0,0 +1,6 @@ +"""Example taken from issue #1470""" + + +def func(): + """Both module should be parsed without problem""" + return 1 From e334af21f66ed50456fab4d6b996d073cec02ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 31 Dec 2021 15:50:43 +0100 Subject: [PATCH 115/357] Add regression test for issue 5461 (#5623) --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ tests/functional/r/regression_02/regression_5461.py | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 tests/functional/r/regression_02/regression_5461.py diff --git a/ChangeLog b/ChangeLog index 07ff847160..aae2fe1572 100644 --- a/ChangeLog +++ b/ChangeLog @@ -72,6 +72,10 @@ Release date: TBA Closes #5360, #3877 +* Fixed crash on list comprehensions that used ``type`` as inner variable name. + + Closes #5461 + * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index e9054ec61e..ede1813850 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -108,6 +108,10 @@ Other Changes Closes #5360, #3877 +* Fixed crash on list comprehensions that used ``type`` as inner variable name. + + Closes #5461 + * Require Python ``3.6.2`` to run pylint. Closes #5065 diff --git a/tests/functional/r/regression_02/regression_5461.py b/tests/functional/r/regression_02/regression_5461.py new file mode 100644 index 0000000000..39daf7b9bb --- /dev/null +++ b/tests/functional/r/regression_02/regression_5461.py @@ -0,0 +1,6 @@ +"""Regression test for issue 5461 +Crash on list comprehension with it used `type` as variable name + +See: https://github.com/PyCQA/pylint/issues/5461 +""" +var = [type for type in [] if type["id"]] From ef56e761a4b66fcf8f94373d6dae0f49896169a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 2 Jan 2022 00:05:13 +0100 Subject: [PATCH 116/357] Remove unnecessary checks for node attributes in ``add_message`` (#5622) --- pylint/lint/pylinter.py | 13 +++---------- pylint/testutils/unittest_linter.py | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 1d6b5a3b1a..49ddb29ad9 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1471,18 +1471,11 @@ def _add_one_message( if node: if not line: line = node.fromlineno - # pylint: disable=fixme - # TODO: Initialize col_offset on every node (can be None) -> astroid - if not col_offset and hasattr(node, "col_offset"): + if not col_offset: col_offset = node.col_offset - # pylint: disable=fixme - # TODO: Initialize end_lineno on every node (can be None) -> astroid - # See https://github.com/PyCQA/astroid/issues/1273 - if not end_lineno and hasattr(node, "end_lineno"): + if not end_lineno: end_lineno = node.end_lineno - # pylint: disable=fixme - # TODO: Initialize end_col_offset on every node (can be None) -> astroid - if not end_col_offset and hasattr(node, "end_col_offset"): + if not end_col_offset: end_col_offset = node.end_col_offset # should this message be displayed diff --git a/pylint/testutils/unittest_linter.py b/pylint/testutils/unittest_linter.py index 0c45264f7b..c9945f52fb 100644 --- a/pylint/testutils/unittest_linter.py +++ b/pylint/testutils/unittest_linter.py @@ -43,17 +43,18 @@ def add_message( # If confidence is None we set it to UNDEFINED as well in PyLinter if confidence is None: confidence = UNDEFINED - if not line and node: - line = node.fromlineno - # pylint: disable=fixme - # TODO: Initialize col_offset, end_lineno and end_col_offset on every node -> astroid - # See https://github.com/PyCQA/astroid/issues/1273 - if col_offset is None and node and hasattr(node, "col_offset"): - col_offset = node.col_offset - if not end_lineno and node and hasattr(node, "end_lineno"): - end_lineno = node.end_lineno - if not end_col_offset and node and hasattr(node, "end_col_offset"): - end_col_offset = node.end_col_offset + + # Look up "location" data of node if not yet supplied + if node: + if not line: + line = node.fromlineno + if not col_offset: + col_offset = node.col_offset + if not end_lineno: + end_lineno = node.end_lineno + if not end_col_offset: + end_col_offset = node.end_col_offset + self._messages.append( MessageTest( msg_id, From 7d03942444d2226720126c2847a4414e11382c8a Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 22 Dec 2021 18:26:44 +0100 Subject: [PATCH 117/357] Fix a typo in pylint/typing.py --- pylint/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/typing.py b/pylint/typing.py index 5f1524180c..402a6f7162 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -57,7 +57,7 @@ class MessageLocationTuple(NamedTuple): class ManagedMessage(NamedTuple): - """Tuple with information ahout a managed message of the linter""" + """Tuple with information about a managed message of the linter""" name: Optional[str] msgid: str From 11eba03a9de2cf2293f0d952f0c8988b9bfd870d Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 31 Dec 2021 08:51:58 +0100 Subject: [PATCH 118/357] Add a regression test for pylint #73 Following change in the way distutil is stored in setuptools See https://github.com/PyCQA/astroid/pull/1321 --- .../regression_distutil_import_error_73.py | 15 +++++++++++++++ .../regression_distutil_import_error_73.txt | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 tests/functional/r/regression_02/regression_distutil_import_error_73.py create mode 100644 tests/functional/r/regression_02/regression_distutil_import_error_73.txt diff --git a/tests/functional/r/regression_02/regression_distutil_import_error_73.py b/tests/functional/r/regression_02/regression_distutil_import_error_73.py new file mode 100644 index 0000000000..751a4980f7 --- /dev/null +++ b/tests/functional/r/regression_02/regression_distutil_import_error_73.py @@ -0,0 +1,15 @@ +""" +Regression test to check that distutils can be imported +See https://github.com/PyCQA/pylint/issues/73 + +See also: +https://github.com/PyCQA/pylint/issues/2955 +https://github.com/PyCQA/astroid/pull/1321 +""" + +# pylint: disable=unused-import + +import distutils.version +from distutils.util import strtobool +from distutils import doesnottexists # [no-name-in-module] +from distutils.doesnottexists import nope # [no-name-in-module, import-error] diff --git a/tests/functional/r/regression_02/regression_distutil_import_error_73.txt b/tests/functional/r/regression_02/regression_distutil_import_error_73.txt new file mode 100644 index 0000000000..85177f1901 --- /dev/null +++ b/tests/functional/r/regression_02/regression_distutil_import_error_73.txt @@ -0,0 +1,3 @@ +no-name-in-module:14:0:14:36::No name 'doesnottexists' in module 'distutils':UNDEFINED +import-error:15:0:15:41::Unable to import 'distutils.doesnottexists':UNDEFINED +no-name-in-module:15:0:15:41::No name 'doesnottexists' in module 'distutils':UNDEFINED From 15c8825eeb4c7546f2c8e5c5a044e0f25fc9d4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 2 Jan 2022 23:21:15 +0100 Subject: [PATCH 119/357] Add ``pydocstringformatter`` to pre-commit config --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0857eaa500..37600cc60c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -95,3 +95,8 @@ repos: - id: prettier args: [--prose-wrap=always, --print-width=88] exclude: tests(/.*)*/data + - repo: https://github.com/DanielNoord/pydocstringformatter + rev: v0.2.0 + hooks: + - id: pydocstringformatter + exclude: *fixtures From fffde57ac06ace43d74a04f799fabe1724d83ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 2 Jan 2022 23:21:23 +0100 Subject: [PATCH 120/357] Format docstrings with ``pydocstringformatter`` --- examples/custom.py | 3 +- examples/deprecation_checker.py | 3 +- pylint/checkers/base.py | 15 ++++---- pylint/checkers/base_checker.py | 9 +++-- pylint/checkers/classes/class_checker.py | 10 ++--- .../classes/special_methods_checker.py | 3 +- pylint/checkers/ellipsis_checker.py | 3 +- pylint/checkers/exceptions.py | 3 +- pylint/checkers/format.py | 29 +++++--------- pylint/checkers/logging.py | 7 +--- pylint/checkers/newstyle.py | 3 +- .../implicit_booleaness_checker.py | 3 +- .../refactoring/recommendation_checker.py | 3 +- .../refactoring/refactoring_checker.py | 28 +++++++------- pylint/checkers/similar.py | 38 +++++++------------ pylint/checkers/spelling.py | 10 ++--- pylint/checkers/strings.py | 9 ++--- pylint/checkers/typecheck.py | 17 +++------ pylint/checkers/unsupported_version.py | 3 +- pylint/checkers/utils.py | 30 ++++++--------- pylint/checkers/variables.py | 29 +++++--------- pylint/config/configuration_mixin.py | 3 +- pylint/config/option_manager_mixin.py | 3 +- pylint/epylint.py | 3 +- pylint/extensions/_check_docs_utils.py | 6 +-- pylint/extensions/comparison_placement.py | 3 +- pylint/extensions/docparams.py | 6 +-- pylint/extensions/mccabe.py | 3 +- pylint/extensions/overlapping_exceptions.py | 3 +- pylint/lint/expand_modules.py | 3 +- pylint/lint/pylinter.py | 12 ++++-- pylint/message/message_id_store.py | 6 ++- pylint/pyreverse/__init__.py | 4 +- pylint/pyreverse/diadefslib.py | 3 +- pylint/pyreverse/dot_printer.py | 4 +- pylint/pyreverse/inspector.py | 3 +- pylint/pyreverse/main.py | 3 +- pylint/pyreverse/mermaidjs_printer.py | 4 +- pylint/pyreverse/plantuml_printer.py | 4 +- pylint/pyreverse/printer.py | 4 +- pylint/pyreverse/utils.py | 6 ++- pylint/reporters/multi_reporter.py | 3 +- pylint/reporters/text.py | 3 +- pylint/testutils/lint_module_test.py | 3 +- pylint/testutils/output_line.py | 3 +- pylint/testutils/pyreverse.py | 3 +- pylint/utils/pragma_parser.py | 15 ++------ script/bump_changelog.py | 4 +- tests/benchmark/test_baseline_benchmarks.py | 33 ++++++++++------ tests/checkers/unittest_design.py | 3 +- tests/checkers/unittest_format.py | 6 +-- tests/checkers/unittest_stdlib.py | 3 +- tests/checkers/unittest_variables.py | 3 +- .../config/test_functional_config_loading.py | 3 +- tests/config/unittest_config.py | 6 ++- tests/pyreverse/test_diadefs.py | 3 +- tests/pyreverse/test_inspector.py | 4 +- tests/pyreverse/test_printer_factory.py | 4 +- tests/pyreverse/test_utils.py | 3 +- tests/pyreverse/test_writer.py | 4 +- tests/test_check_parallel.py | 6 ++- tests/test_func.py | 3 +- tests/test_self.py | 7 +--- tests/unittest_reporting.py | 3 +- 64 files changed, 208 insertions(+), 264 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index 695f77d20d..d4376fc4e3 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -52,7 +52,8 @@ class MyAstroidChecker(BaseChecker): def visit_call(self, node: nodes.Call) -> None: """Called when a :class:`.nodes.Call` node is visited. - See :mod:`astroid` for the description of available nodes.""" + See :mod:`astroid` for the description of available nodes. + """ if not ( isinstance(node.func, nodes.Attribute) and isinstance(node.func.expr, nodes.Name) diff --git a/examples/deprecation_checker.py b/examples/deprecation_checker.py index d3dca4e07d..79a7285379 100644 --- a/examples/deprecation_checker.py +++ b/examples/deprecation_checker.py @@ -1,5 +1,4 @@ -""" -Example checker detecting deprecated functions/methods. Following example searches for usages of +"""Example checker detecting deprecated functions/methods. Following example searches for usages of deprecated function `deprecated_function` and deprecated method `MyClass.deprecated_method` from module mymodule: diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 39ab5d673b..c1eefab921 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -103,7 +103,8 @@ class NamingStyle: """It may seem counterintuitive that single naming style has multiple "accepted" forms of regular expressions, but we need to special-case stuff like dunder names - in method names.""" + in method names. + """ ANY: Pattern[str] = re.compile(".*") CLASS_NAME_RGX: Pattern[str] = ANY @@ -278,8 +279,7 @@ def in_nested_list(nested_list, obj): def _get_break_loop_node(break_node): - """ - Returns the loop node that holds the break node in arguments. + """Returns the loop node that holds the break node in arguments. Args: break_node (astroid.Break): the break node of interest. @@ -300,8 +300,7 @@ def _get_break_loop_node(break_node): def _loop_exits_early(loop): - """ - Returns true if a loop may end with a break statement. + """Returns true if a loop may end with a break statement. Args: loop (astroid.For, astroid.While): the loop node inspected. @@ -389,8 +388,7 @@ def _determine_function_name_type(node: nodes.FunctionDef, config=None): def _has_abstract_methods(node): - """ - Determine if the given `node` has abstract methods. + """Determine if the given `node` has abstract methods. The methods should be made abstract by decorating them with `abc` decorators. @@ -1496,7 +1494,8 @@ def _check_not_in_finally(self, node, node_name, breaker_classes=()): """check that a node is not inside a 'finally' clause of a 'try...finally' statement. If we find a parent which type is in breaker_classes before - a 'try...finally' block we skip the whole check.""" + a 'try...finally' block we skip the whole check. + """ # if self._tryfinallys is empty, we're not an in try...finally block if not self._tryfinallys: return diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index 8fe44a2e23..5a6efd20c2 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -50,7 +50,8 @@ class BaseChecker(OptionsProviderMixIn): def __init__(self, linter=None): """checker instances should have the linter as argument - :param ILinter linter: is an object implementing ILinter.""" + :param ILinter linter: is an object implementing ILinter. + """ if self.name is not None: self.name = self.name.lower() super().__init__() @@ -67,7 +68,8 @@ def __repr__(self): def __str__(self): """This might be incomplete because multiple class inheriting BaseChecker - can have the same name. Cf MessageHandlerMixIn.get_full_documentation()""" + can have the same name. Cf MessageHandlerMixIn.get_full_documentation() + """ return self.get_full_documentation( msgs=self.msgs, options=self.options_and_values(), reports=self.reports ) @@ -132,7 +134,8 @@ def check_consistency(self): checker. :raises InvalidMessageError: If the checker id in the messages are not - always the same.""" + always the same. + """ checker_id = None existing_ids = [] for message in self.messages: diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index b76e10e43a..fce2f7025f 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -186,8 +186,7 @@ class _DefaultMissing: def _has_different_parameters_default_value(original, overridden): - """ - Check if original and overridden methods arguments have different default values + """Check if original and overridden methods arguments have different default values Return True if one of the overridden arguments has a default value different from the default value of the original argument @@ -821,8 +820,7 @@ def _check_consistent_mro(self, node): pass def _check_proper_bases(self, node): - """ - Detect that a class inherits something which is not + """Detect that a class inherits something which is not a class or a type. """ for base in node.bases: @@ -1655,9 +1653,7 @@ def _check_protected_attribute_access(self, node: nodes.Attribute): @staticmethod def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: - """ - Returns true if the node is located inside a special (aka dunder) method - """ + """Returns true if the node is located inside a special (aka dunder) method""" try: frame_name = node.frame().name except AttributeError: diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py index 51c52b3ae4..f268230072 100644 --- a/pylint/checkers/classes/special_methods_checker.py +++ b/pylint/checkers/classes/special_methods_checker.py @@ -21,8 +21,7 @@ def _safe_infer_call_result(node, caller, context=None): - """ - Safely infer the return value of a function. + """Safely infer the return value of a function. Returns None if inference failed or if there is some ambiguity (more than one node has been inferred). Otherwise, returns inferred value. diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py index e5d4c4ecd9..af8715ed55 100644 --- a/pylint/checkers/ellipsis_checker.py +++ b/pylint/checkers/ellipsis_checker.py @@ -1,5 +1,4 @@ -"""Ellipsis checker for Python code -""" +"""Ellipsis checker for Python code""" from typing import TYPE_CHECKING from astroid import nodes diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 0d60b0a395..eafbafa2b9 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -57,8 +57,7 @@ def predicate(obj): def _annotated_unpack_infer(stmt, context=None): - """ - Recursively generate nodes inferred by the given statement. + """Recursively generate nodes inferred by the given statement. If the inferred value is a list or a tuple, recurse on the elements. Returns an iterator which yields tuples in the format ('original node', 'inferred node'). diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 17a50911cf..f38944ded0 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -669,9 +669,7 @@ def _check_multi_statement_line(self, node, line): self._visited_lines[line] = 2 def check_line_ending(self, line: str, i: int) -> None: - """ - Check that the final newline is not missing and that there is no trailing whitespace. - """ + """Check that the final newline is not missing and that there is no trailing whitespace.""" if not line.endswith("\n"): self.add_message("missing-final-newline", line=i) return @@ -683,9 +681,7 @@ def check_line_ending(self, line: str, i: int) -> None: ) def check_line_length(self, line: str, i: int, checker_off: bool) -> None: - """ - Check that the line length is less than the authorized value - """ + """Check that the line length is less than the authorized value""" max_chars = self.config.max_line_length ignore_long_line = self.config.ignore_long_lines line = line.rstrip() @@ -697,9 +693,7 @@ def check_line_length(self, line: str, i: int, checker_off: bool) -> None: @staticmethod def remove_pylint_option_from_lines(options_pattern_obj) -> str: - """ - Remove the `# pylint ...` pattern from lines - """ + """Remove the `# pylint ...` pattern from lines""" lines = options_pattern_obj.string purged_lines = ( lines[: options_pattern_obj.start(1)].rstrip() @@ -709,9 +703,7 @@ def remove_pylint_option_from_lines(options_pattern_obj) -> str: @staticmethod def is_line_length_check_activated(pylint_pattern_match_object) -> bool: - """ - Return true if the line length check is activated - """ + """Return true if the line length check is activated""" try: for pragma in parse_pragma(pylint_pattern_match_object.group(2)): if pragma.action == "disable" and "line-too-long" in pragma.messages: @@ -723,9 +715,7 @@ def is_line_length_check_activated(pylint_pattern_match_object) -> bool: @staticmethod def specific_splitlines(lines: str) -> List[str]: - """ - Split lines according to universal newlines except those in a specific sets - """ + """Split lines according to universal newlines except those in a specific sets""" unsplit_ends = { "\v", "\x0b", @@ -749,11 +739,10 @@ def specific_splitlines(lines: str) -> List[str]: return res def check_lines(self, lines: str, lineno: int) -> None: - """ - Check lines have : - - a final newline - - no trailing whitespace - - less than a maximum number of characters + """Check lines have : + - a final newline + - no trailing whitespace + - less than a maximum number of characters """ # we're first going to do a rough check whether any lines in this set # go over the line limit. If none of them do, then we don't need to diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index 19e04c7d1b..4c44017419 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -23,8 +23,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""checker for use of Python logging -""" +"""checker for use of Python logging""" import string from typing import TYPE_CHECKING, Set @@ -295,9 +294,7 @@ def _helper_string(self, node): @staticmethod def _is_operand_literal_str(operand): - """ - Return True if the operand in argument is a literal string - """ + """Return True if the operand in argument is a literal string""" return isinstance(operand, nodes.Const) and operand.name == "str" def _check_call_func(self, node: nodes.Call): diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index b19a5c7ef5..45b4409d87 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -20,8 +20,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""check for new / old style related problems -""" +"""check for new / old style related problems""" from typing import TYPE_CHECKING import astroid diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index 29277e3a23..6167e3e88f 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -129,7 +129,8 @@ def instance_has_bool(class_def: nodes.ClassDef) -> bool: def visit_unaryop(self, node: nodes.UnaryOp) -> None: """`not len(S)` must become `not S` regardless if the parent block is a test condition or something else (boolean expression) - e.g. `if not len(S):`""" + e.g. `if not len(S):` + """ if ( isinstance(node, nodes.UnaryOp) and node.op == "not" diff --git a/pylint/checkers/refactoring/recommendation_checker.py b/pylint/checkers/refactoring/recommendation_checker.py index 3094ffd07e..785e37fddf 100644 --- a/pylint/checkers/refactoring/recommendation_checker.py +++ b/pylint/checkers/refactoring/recommendation_checker.py @@ -337,7 +337,8 @@ def visit_const(self, node: nodes.Const) -> None: def _detect_replacable_format_call(self, node: nodes.Const) -> None: """Check whether a string is used in a call to format() or '%' and whether it - can be replaced by an f-string""" + can be replaced by an f-string + """ if ( isinstance(node.parent, nodes.Attribute) and node.parent.attrname == "format" diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 40a72a53f3..a182574561 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -118,8 +118,7 @@ def _is_a_return_statement(node: nodes.Call) -> bool: def _is_part_of_with_items(node: nodes.Call) -> bool: - """ - Checks if one of the node's parents is a ``nodes.With`` node and that the node itself is located + """Checks if one of the node's parents is a ``nodes.With`` node and that the node itself is located somewhere under its ``items``. """ frame = node.frame() @@ -135,7 +134,8 @@ def _is_part_of_with_items(node: nodes.Call) -> bool: def _will_be_released_automatically(node: nodes.Call) -> bool: """Checks if a call that could be used in a ``with`` statement is used in an alternative - construct which would ensure that its __exit__ method is called.""" + construct which would ensure that its __exit__ method is called. + """ callables_taking_care_of_exit = frozenset( ( "contextlib._BaseExitStack.enter_context", @@ -152,7 +152,8 @@ def _will_be_released_automatically(node: nodes.Call) -> bool: class ConsiderUsingWithStack(NamedTuple): """Stack for objects that may potentially trigger a R1732 message - if they are not used in a ``with`` block later on.""" + if they are not used in a ``with`` block later on. + """ module_scope: Dict[str, nodes.NodeNG] = {} class_scope: Dict[str, nodes.NodeNG] = {} @@ -1278,7 +1279,8 @@ def _apply_boolean_simplification_rules(operator, values): reverse for AND 2) False values in OR expressions are only relevant if all values are - false, and the reverse for AND""" + false, and the reverse for AND + """ simplified_values = [] for subnode in values: @@ -1299,7 +1301,8 @@ def _simplify_boolean_operation(self, bool_op): """Attempts to simplify a boolean operation Recursively applies simplification on the operator terms, - and keeps track of whether reductions have been made.""" + and keeps track of whether reductions have been made. + """ children = list(bool_op.get_children()) intermediate = [ self._simplify_boolean_operation(child) @@ -1320,7 +1323,8 @@ def _check_simplifiable_condition(self, node): """Check if a boolean condition can be simplified. Variables will not be simplified, even in the value can be inferred, - and expressions like '3 + 4' will remain expanded.""" + and expressions like '3 + 4' will remain expanded. + """ if not utils.is_test_condition(node): return @@ -1506,8 +1510,7 @@ def _check_use_list_or_dict_literal(self, node: nodes.Call) -> None: self.add_message("use-dict-literal", node=node) def _check_consider_using_join(self, aug_assign): - """ - We start with the augmented assignment and work our way upwards. + """We start with the augmented assignment and work our way upwards. Names of variables for nodes if match successful: result = '' # assign for number in ['1', '2', '3'] # for_loop @@ -1630,8 +1633,7 @@ def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None: @staticmethod def _is_and_or_ternary(node): - """ - Returns true if node is 'condition and true_value or false_value' form. + """Returns true if node is 'condition and true_value or false_value' form. All of: condition, true_value and false_value should not be a complex boolean expression """ @@ -1793,9 +1795,7 @@ def _is_node_return_ended(self, node: nodes.NodeNG) -> bool: @staticmethod def _has_return_in_siblings(node: nodes.NodeNG) -> bool: - """ - Returns True if there is at least one return in the node's siblings - """ + """Returns True if there is at least one return in the node's siblings""" next_sibling = node.next_sibling() while next_sibling: if isinstance(next_sibling, nodes.Return): diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 51ec27c0bb..ab72631355 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -108,8 +108,7 @@ class LineSpecifs(NamedTuple): class CplSuccessiveLinesLimits: - """ - This class holds a couple of SuccessiveLinesLimits objects, one for each file compared, + """This class holds a couple of SuccessiveLinesLimits objects, one for each file compared, and a counter on the number of common lines between both stripped lines collections extracted from both files """ @@ -133,9 +132,7 @@ def __init__( class LinesChunk: - """ - The LinesChunk object computes and stores the hash of some consecutive stripped lines of a lineset. - """ + """The LinesChunk object computes and stores the hash of some consecutive stripped lines of a lineset.""" __slots__ = ("_fileid", "_index", "_hash") @@ -170,8 +167,7 @@ def __str__(self) -> str: class SuccessiveLinesLimits: - """ - A class to handle the numbering of begin and end of successive lines. + """A class to handle the numbering of begin and end of successive lines. :note: Only the end line number can be updated. """ @@ -199,9 +195,7 @@ def __repr__(self) -> str: class LineSetStartCouple(NamedTuple): - """ - Indices in both linesets that mark the beginning of successive lines - """ + """Indices in both linesets that mark the beginning of successive lines""" fst_lineset_index: Index snd_lineset_index: Index @@ -235,8 +229,7 @@ def increment(self, value: Index) -> "LineSetStartCouple": def hash_lineset( lineset: "LineSet", min_common_lines: int = DEFAULT_MIN_SIMILARITY_LINE ) -> Tuple[HashToIndex_T, IndexToLines_T]: - """ - Return two dicts. The first associates the hash of successive stripped lines of a lineset + """Return two dicts. The first associates the hash of successive stripped lines of a lineset to the indices of the starting lines. The second dict, associates the index of the starting line in the lineset's stripped lines to the couple [start, end] lines number in the corresponding file. @@ -275,8 +268,7 @@ def hash_lineset( def remove_successives(all_couples: CplIndexToCplLines_T) -> None: - """ - Removes all successive entries in the dictionary in argument + """Removes all successive entries in the dictionary in argument :param all_couples: collection that has to be cleaned up from successives entries. The keys are couples of indices that mark the beginning of common entries @@ -325,8 +317,7 @@ def filter_noncode_lines( stindex_2: Index, common_lines_nb: int, ) -> int: - """ - Return the effective number of common lines between lineset1 and lineset2 filtered from non code lines, that is to say the number of + """Return the effective number of common lines between lineset1 and lineset2 filtered from non code lines, that is to say the number of common successive stripped lines except those that do not contain code (for example a ligne with only an ending parathensis) @@ -477,8 +468,7 @@ def _get_similarity_report( def _find_common( self, lineset1: "LineSet", lineset2: "LineSet" ) -> Generator[Commonality, None, None]: - """ - Find similarities in the two given linesets. + """Find similarities in the two given linesets. This the core of the algorithm. The idea is to compute the hashes of a minimal number of successive lines of each lineset and then compare the hashes. @@ -562,7 +552,8 @@ def get_map_data(self): def combine_mapreduce_data(self, linesets_collection): """Reduces and recombines data into a format that we can report on - The partner function of get_map_data()""" + The partner function of get_map_data() + """ self.linesets = [line for lineset in linesets_collection for line in lineset] @@ -573,8 +564,7 @@ def stripped_lines( ignore_imports: bool, ignore_signatures: bool, ) -> List[LineSpecifs]: - """ - Return tuples of line/line number/line type with leading/trailing whitespace and any ignored code features removed + """Return tuples of line/line number/line type with leading/trailing whitespace and any ignored code features removed :param lines: a collection of lines :param ignore_comments: if true, any comment in the lines collection is removed from the result @@ -664,8 +654,7 @@ def _get_functions( @functools.total_ordering class LineSet: - """ - Holds and indexes all the lines of a single source file. + """Holds and indexes all the lines of a single source file. Allows for correspondence between real lines of the source file and stripped ones, which are the real ones from which undesired patterns have been removed. """ @@ -871,7 +860,8 @@ def get_map_data(self): def reduce_map_data(self, linter, data): """Reduces and recombines data into a format that we can report on - The partner function of get_map_data()""" + The partner function of get_map_data() + """ recombined = SimilarChecker(linter) recombined.min_lines = self.min_lines recombined.ignore_comments = self.ignore_comments diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index 012c396ded..752eb83e3c 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -28,8 +28,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Checker for spelling errors in comments and docstrings. -""" +"""Checker for spelling errors in comments and docstrings.""" import os import re import tokenize @@ -147,9 +146,7 @@ class SphinxDirectives(RegExFilter): class ForwardSlashChunker(Chunker): - """ - This chunker allows splitting words like 'before/after' into 'before' and 'after' - """ + """This chunker allows splitting words like 'before/after' into 'before' and 'after'""" def next(self): while True: @@ -194,7 +191,8 @@ def _strip_code_flanked_in_backticks(line: str) -> str: """Alter line so code flanked in backticks is ignored. Pyenchant automatically strips backticks when parsing tokens, - so this cannot be done at the individual filter level.""" + so this cannot be done at the individual filter level. + """ def replace_code_but_leave_surrounding_characters(match_obj) -> str: return match_obj.group(1) + match_obj.group(5) diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index e4dcfc8316..458c96fe64 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -34,8 +34,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Checker for string formatting operations. -""" +"""Checker for string formatting operations.""" import collections import numbers @@ -540,8 +539,7 @@ def _check_new_format(self, node, func): self._check_new_format_specifiers(node, fields, named_arguments) def _check_new_format_specifiers(self, node, fields, named): - """ - Check attribute and index access in the format + """Check attribute and index access in the format string ("{0.a}" and "{0[a]}"). """ for key, specifiers in fields: @@ -934,8 +932,7 @@ def register(linter: "PyLinter") -> None: def str_eval(token): - """ - Mostly replicate `ast.literal_eval(token)` manually to avoid any performance hit. + """Mostly replicate `ast.literal_eval(token)` manually to avoid any performance hit. This supports f-strings, contrary to `ast.literal_eval`. We have to support all string literal notations: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index a2239469a3..c0a7725f93 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -55,8 +55,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""try to find more bugs in the code using astroid inference capabilities -""" +"""try to find more bugs in the code using astroid inference capabilities""" import fnmatch import heapq @@ -1137,9 +1136,7 @@ def _get_nomember_msgid_hint(self, node, owner): "non-str-assignment-to-dunder-name", ) def visit_assign(self, node: nodes.Assign) -> None: - """ - Process assignments in the AST. - """ + """Process assignments in the AST.""" self._check_assignment_from_function_call(node) self._check_dundername_is_string(node) @@ -1193,9 +1190,7 @@ def _check_assignment_from_function_call(self, node): self.add_message("assignment-from-none", node=node) def _check_dundername_is_string(self, node): - """ - Check a string is assigned to self.__name__ - """ + """Check a string is assigned to self.__name__""" # Check the left-hand side of the assignment is .__name__ lhs = node.targets[0] @@ -1216,8 +1211,7 @@ def _check_dundername_is_string(self, node): self.add_message("non-str-assignment-to-dunder-name", node=node) def _check_uninferable_call(self, node): - """ - Check that the given uninferable Call node does not + """Check that the given uninferable Call node does not call an actual function. """ if not isinstance(node.func, nodes.Attribute): @@ -1979,8 +1973,7 @@ def visit_for(self, node: nodes.For) -> None: class IterableChecker(BaseChecker): - """ - Checks for non-iterables used in an iterable context. + """Checks for non-iterables used in an iterable context. Contexts include: - for-statement - starargs in function call diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index 64078aa689..ada104ab90 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -68,7 +68,8 @@ def visit_decorators(self, node: nodes.Decorators) -> None: def _check_typing_final(self, node: nodes.Decorators) -> None: """Add a message when the `typing.final` decorator is used and the - py-version is lower than 3.8""" + py-version is lower than 3.8 + """ if self._py38_plus: return diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index cd72e38ee3..c20342c82e 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -57,8 +57,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""some functions that may be useful for various checkers -""" +"""some functions that may be useful for various checkers""" import builtins import itertools import numbers @@ -476,7 +475,8 @@ def assign_parent(node: nodes.NodeNG) -> nodes.NodeNG: def overrides_a_method(class_node: nodes.ClassDef, name: str) -> bool: """return True if is a method overridden from an ancestor - which is not the base object class""" + which is not the base object class + """ for ancestor in class_node.ancestors(): if ancestor.name == "object": continue @@ -501,7 +501,8 @@ class IncompleteFormatString(Exception): class UnsupportedFormatCharacter(Exception): """A format character in a format string is not one of the supported - format characters.""" + format characters. + """ def __init__(self, index): super().__init__(index) @@ -625,8 +626,7 @@ def collect_string_fields(format_string) -> Iterable[Optional[str]]: def parse_format_method_string( format_string: str, ) -> Tuple[List[Tuple[str, List[Tuple[bool, str]]]], int, int]: - """ - Parses a PEP 3101 format string, returning a tuple of + """Parses a PEP 3101 format string, returning a tuple of (keyword_arguments, implicit_pos_args_cnt, explicit_pos_args), where keyword_arguments is the set of mapping keys in the format string, implicit_pos_args_cnt is the number of arguments required by the format string and @@ -733,8 +733,7 @@ def get_argument_from_call( def inherit_from_std_ex(node: nodes.NodeNG) -> bool: - """ - Return whether the given class node is subclass of + """Return whether the given class node is subclass of exceptions.Exception. """ ancestors = node.ancestors() if hasattr(node, "ancestors") else [] @@ -746,8 +745,7 @@ def inherit_from_std_ex(node: nodes.NodeNG) -> bool: def error_of_type(handler: nodes.ExceptHandler, error_type) -> bool: - """ - Check if the given exception handler catches + """Check if the given exception handler catches the given error_type. The *handler* parameter is a node, representing an ExceptHandler node. @@ -908,8 +906,7 @@ def uninferable_final_decorators( def unimplemented_abstract_methods( node: nodes.ClassDef, is_abstract_cb: nodes.FunctionDef = None ) -> Dict[str, nodes.NodeNG]: - """ - Get the unimplemented abstract methods for the given *node*. + """Get the unimplemented abstract methods for the given *node*. A method can be considered abstract if the callback *is_abstract_cb* returns a ``True`` value. The check defaults to verifying that @@ -1368,8 +1365,7 @@ def is_registered_in_singledispatch_function(node: nodes.FunctionDef) -> bool: def get_node_last_lineno(node: nodes.NodeNG) -> int: - """ - Get the last lineno of the given node. For a simple statement this will just be node.lineno, + """Get the last lineno of the given node. For a simple statement this will just be node.lineno, but for a node that has child statements (e.g. a method) this will be the lineno of the last child statement recursively. """ @@ -1440,8 +1436,7 @@ def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool: def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool: - """ - Check if first node is a subclass of second node. + """Check if first node is a subclass of second node. :param child: Node to check for subclass. :param parent: Node to check for superclass. :returns: True if child is derived from parent. False otherwise. @@ -1588,8 +1583,7 @@ def get_iterating_dictionary_name( def get_subscript_const_value(node: nodes.Subscript) -> nodes.Const: - """ - Returns the value 'subscript.slice' of a Subscript node. + """Returns the value 'subscript.slice' of a Subscript node. :param node: Subscript Node to extract value from :returns: Const Node containing subscript value diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 8bfdc197e6..44d54cfd27 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -333,8 +333,7 @@ def _fix_dot_imports(not_consumed): def _find_frame_imports(name, frame): - """ - Detect imports in the frame, with the required + """Detect imports in the frame, with the required *name*. Such imports can be considered assignments. Returns True if an import for the given name was found. """ @@ -369,9 +368,7 @@ def _flattened_scope_names(iterator): def _assigned_locally(name_node): - """ - Checks if name_node has corresponding assign statement in same scope - """ + """Checks if name_node has corresponding assign statement in same scope""" assign_stmts = name_node.scope().nodes_of_class(nodes.AssignName) return any(a.name == name_node.name for a in assign_stmts) @@ -545,9 +542,7 @@ def _has_locals_call_after_node(stmt, scope): class NamesConsumer: - """ - A simple class to handle consumed, to consume and scope type info of node locals - """ + """A simple class to handle consumed, to consume and scope type info of node locals""" def __init__(self, node, scope_type): self._atomic = ScopeConsumer( @@ -584,8 +579,7 @@ def consumed(self): @property def consumed_uncertain(self) -> DefaultDict[str, List[nodes.NodeNG]]: - """ - Retrieves nodes filtered out by get_next_to_consume() that may not + """Retrieves nodes filtered out by get_next_to_consume() that may not have executed, such as statements in except blocks, or statements in try blocks (when evaluating their corresponding except and finally blocks). Checkers that want to treat the statements as executed @@ -598,8 +592,7 @@ def scope_type(self): return self._atomic.scope_type def mark_as_consumed(self, name, consumed_nodes): - """ - Mark the given nodes as consumed for the name. + """Mark the given nodes as consumed for the name. If all of the nodes for the name were consumed, delete the name from the to_consume dictionary """ @@ -612,8 +605,7 @@ def mark_as_consumed(self, name, consumed_nodes): del self.to_consume[name] def get_next_to_consume(self, node): - """ - Return a list of the nodes that define `node` from this scope. If it is + """Return a list of the nodes that define `node` from this scope. If it is uncertain whether a node will be consumed, such as for statements in except blocks, add it to self.consumed_uncertain instead of returning it. Return None to indicate a special case that needs to be handled by the caller. @@ -700,8 +692,7 @@ def get_next_to_consume(self, node): @staticmethod def _uncertain_nodes_in_except_blocks(found_nodes, node, node_statement): - """ - Return any nodes in ``found_nodes`` that should be treated as uncertain + """Return any nodes in ``found_nodes`` that should be treated as uncertain because they are in an except block. """ uncertain_nodes = [] @@ -1846,8 +1837,7 @@ def _is_never_evaluated( return False def _ignore_class_scope(self, node): - """ - Return True if the node is in a local class scope, as an assignment. + """Return True if the node is in a local class scope, as an assignment. :param node: Node considered :type node: astroid.Node @@ -2169,8 +2159,7 @@ def _allowed_redefined_builtin(self, name): def _has_homonym_in_upper_function_scope( self, node: nodes.Name, index: int ) -> bool: - """ - Return whether there is a node with the same name in the + """Return whether there is a node with the same name in the to_consume dict of an upper scope and if that scope is a function diff --git a/pylint/config/configuration_mixin.py b/pylint/config/configuration_mixin.py index 2d34933f08..a2abcb7528 100644 --- a/pylint/config/configuration_mixin.py +++ b/pylint/config/configuration_mixin.py @@ -7,7 +7,8 @@ class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): """Basic mixin for simple configurations which don't need the - manager / providers model""" + manager / providers model + """ def __init__(self, *args, **kwargs): if not args: diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index e6e031e7df..67740d3342 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -332,7 +332,8 @@ def _parse_toml( def load_config_file(self): """Dispatch values previously read from a configuration file to each - option's provider""" + option's provider + """ parser = self.cfgfile_parser for section in parser.sections(): for option, value in parser.items(section): diff --git a/pylint/epylint.py b/pylint/epylint.py index a7eec3302d..5517543a4d 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -69,7 +69,8 @@ def _get_env(): """Extracts the environment PYTHONPATH and appends the current 'sys.path' - to it.""" + to it. + """ env = dict(os.environ) env["PYTHONPATH"] = os.pathsep.join(sys.path) return env diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 611a04aa25..2ff86b00b8 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -121,8 +121,7 @@ def _split_multiple_exc_types(target: str) -> List[str]: def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: - """ - Gets all the possible raised exception types for the given raise node. + """Gets all the possible raised exception types for the given raise node. .. note:: @@ -401,8 +400,7 @@ def match_param_docs(self): class EpytextDocstring(SphinxDocstring): - """ - Epytext is similar to Sphinx. See the docs: + """Epytext is similar to Sphinx. See the docs: http://epydoc.sourceforge.net/epytext.html http://epydoc.sourceforge.net/fields.html#fields diff --git a/pylint/extensions/comparison_placement.py b/pylint/extensions/comparison_placement.py index c4a5cffcb0..e045ebdd01 100644 --- a/pylint/extensions/comparison_placement.py +++ b/pylint/extensions/comparison_placement.py @@ -1,8 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Checks for yoda comparisons (variable before constant) +"""Checks for yoda comparisons (variable before constant) See https://en.wikipedia.org/wiki/Yoda_conditions """ diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 8a29a947dc..8d9472fbfa 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -24,8 +24,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings -""" +"""Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings""" import re from typing import TYPE_CHECKING, Optional @@ -642,8 +641,7 @@ def _handle_no_raise_doc(self, excs, node): self._add_raise_message(excs, node) def _add_raise_message(self, missing_excs, node): - """ - Adds a message on :param:`node` for the missing exception type. + """Adds a message on :param:`node` for the missing exception type. :param missing_excs: A list of missing exception types. :type missing_excs: set(str) diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py index d4e752f48c..d675087fe1 100644 --- a/pylint/extensions/mccabe.py +++ b/pylint/extensions/mccabe.py @@ -181,7 +181,8 @@ class McCabeMethodChecker(checkers.BaseChecker): @check_messages("too-complex") def visit_module(self, node: nodes.Module) -> None: """visit an astroid.Module node to check too complex rating and - add message if is greater than max_complexity stored from options""" + add message if is greater than max_complexity stored from options + """ visitor = PathGraphingAstVisitor() for child in node.body: visitor.preorder(child, visitor) diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index ef6afefea0..9729fd112e 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -19,7 +19,8 @@ class OverlappingExceptionsChecker(checkers.BaseChecker): """Checks for two or more exceptions in the same exception handler clause that are identical or parts of the same inheritance hierarchy - (i.e. overlapping).""" + (i.e. overlapping). + """ __implements__ = interfaces.IAstroidChecker diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 7a0dc7004f..4d3600a000 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -18,7 +18,8 @@ def _is_package_cb(inner_path, parts): def get_python_path(filepath: str) -> str: """TODO This get the python path with the (bad) assumption that there is always - an __init__.py. This is not true since python 3.3 and is causing problem.""" + an __init__.py. This is not true since python 3.3 and is causing problem. + """ dirname = os.path.realpath(os.path.expanduser(filepath)) if not os.path.isdir(dirname): dirname = os.path.dirname(dirname) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 49ddb29ad9..8b1993b4ea 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -542,7 +542,8 @@ def __init__( pylintrc=None, ): """Some stuff has to be done before ancestors initialization... - messages store / checkers / reporter / astroid manager""" + messages store / checkers / reporter / astroid manager + """ # Attributes for reporters self.reporter: Union[reporters.BaseReporter, reporters.MultiReporter] if reporter: @@ -851,7 +852,8 @@ def list_messages_enabled(self): def process_tokens(self, tokens): """Process tokens from the current module to search for module/block level - options.""" + options. + """ control_pragmas = {"disable", "disable-next", "enable"} prev_line = None saw_newline = True @@ -1464,7 +1466,8 @@ def _add_one_message( end_col_offset: Optional[int], ) -> None: """After various checks have passed a single Message is - passed to the reporter and added to stats""" + passed to the reporter and added to stats + """ message_definition.check_message_definition(line, node) # Look up "location" data of node if not yet supplied @@ -1698,7 +1701,8 @@ def _register_by_id_managed_msg( self, msgid_or_symbol: str, line: Optional[int], is_disabled: bool = True ) -> None: """If the msgid is a numeric one, then register it to inform the user - it could furnish instead a symbolic msgid.""" + it could furnish instead a symbolic msgid. + """ if msgid_or_symbol[1:].isdigit(): try: symbol = self.msgs_store.message_id_store.get_symbol( diff --git a/pylint/message/message_id_store.py b/pylint/message/message_id_store.py index a16d12bfed..c4d1f4e8f7 100644 --- a/pylint/message/message_id_store.py +++ b/pylint/message/message_id_store.py @@ -52,7 +52,8 @@ def add_msgid_and_symbol(self, msgid: str, symbol: str) -> None: """Add valid message id. There is a little duplication with add_legacy_msgid_and_symbol to avoid a function call, - this is called a lot at initialization.""" + this is called a lot at initialization. + """ self.__msgid_to_symbol[msgid] = symbol self.__symbol_to_msgid[symbol] = msgid @@ -62,7 +63,8 @@ def add_legacy_msgid_and_symbol( """Add valid legacy message id. There is a little duplication with add_msgid_and_symbol to avoid a function call, - this is called a lot at initialization.""" + this is called a lot at initialization. + """ self.__msgid_to_symbol[msgid] = symbol self.__symbol_to_msgid[symbol] = msgid existing_old_names = self.__old_names.get(msgid, []) diff --git a/pylint/pyreverse/__init__.py b/pylint/pyreverse/__init__.py index fdbbe1e832..3902445224 100644 --- a/pylint/pyreverse/__init__.py +++ b/pylint/pyreverse/__init__.py @@ -1,8 +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/main/LICENSE -""" -pyreverse.extensions -""" +"""pyreverse.extensions""" __revision__ = "$Id $" diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index aee686da52..1e059dea31 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -19,8 +19,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""handle diagram generation options for class diagram or default diagrams -""" +"""handle diagram generation options for class diagram or default diagrams""" from typing import Any, Optional diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index c2fd62fb9b..fb7f3d8bc2 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -8,9 +8,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Class to generate files in dot format and image formats supported by Graphviz. -""" +"""Class to generate files in dot format and image formats supported by Graphviz.""" import os import subprocess import sys diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 25819f5aa4..2c7162d7c1 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -14,8 +14,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Visitor doing some postprocessing on the astroid tree. +"""Visitor doing some postprocessing on the astroid tree. Try to resolve definitions (namespace) dictionary, relationship... """ import collections diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index c48b9f3c30..f96c07a596 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -20,8 +20,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" - %prog [options] +"""%prog [options] create UML diagrams for classes and modules in """ diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index d215b559d1..bf12fa2ec3 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -3,9 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Class to generate files in mermaidjs format -""" +"""Class to generate files in mermaidjs format""" from typing import Dict, Optional from pylint.pyreverse.printer import EdgeType, NodeProperties, NodeType, Printer diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index c73cddd5e1..be49c51eba 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -3,9 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Class to generate files in dot format and image formats supported by Graphviz. -""" +"""Class to generate files in dot format and image formats supported by Graphviz.""" from typing import Dict, Optional from pylint.pyreverse.printer import EdgeType, Layout, NodeProperties, NodeType, Printer diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index 0a8715011e..0fdee79fd4 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -7,9 +7,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Base class defining the interface for a printer. -""" +"""Base class defining the interface for a printer.""" from abc import ABC, abstractmethod from enum import Enum from typing import List, NamedTuple, Optional diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index e220617816..454b9dfc9e 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -270,7 +270,8 @@ def get_annotation( def infer_node(node: Union[nodes.AssignAttr, nodes.AssignName]) -> set: """Return a set containing the node annotation if it exists - otherwise return a set of the inferred types using the NodeNG.infer method""" + otherwise return a set of the inferred types using the NodeNG.infer method + """ ann = get_annotation(node) try: @@ -286,7 +287,8 @@ def infer_node(node: Union[nodes.AssignAttr, nodes.AssignName]) -> set: def check_graphviz_availability(): """Check if the ``dot`` command is available on the machine. This is needed if image output is desired and ``dot`` is used to convert - from *.dot or *.gv into the final output format.""" + from *.dot or *.gv into the final output format. + """ if shutil.which("dot") is None: print( "The requested output format is currently not available.\n" diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index 3eb75181e9..61740908dd 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -49,8 +49,7 @@ def out(self): @out.setter def out(self, output: Optional[AnyFile] = None): - """ - MultiReporter doesn't have its own output. This method is only + """MultiReporter doesn't have its own output. This method is only provided for API parity with BaseReporter and should not be called with non-None values for 'output'. """ diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index b69a3a139d..b0db63f11a 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -60,8 +60,7 @@ class MessageStyle(NamedTuple): or the color number when 256 colors are available """ style: Tuple[str, ...] = () - """Tuple of style strings (see `ANSI_COLORS` for available values). - """ + """Tuple of style strings (see `ANSI_COLORS` for available values).""" ColorMappingDict = Dict[str, MessageStyle] diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 8dbf5e60b4..9bbf00d2fa 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -142,7 +142,8 @@ def multiset_difference( ) -> Tuple[MessageCounter, Dict[Tuple[int, str], int]]: """Takes two multisets and compares them. - A multiset is a dict with the cardinality of the key as the value.""" + A multiset is a dict with the cardinality of the key as the value. + """ missing = expected_entries.copy() missing.subtract(actual_entries) unexpected = {} diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index 9c0c762395..fe80247957 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -104,7 +104,8 @@ def _get_column(column: str) -> int: @staticmethod def _get_py38_none_value(value: T, check_endline: bool) -> Optional[T]: """Used to make end_line and end_column None as indicated by our version compared to - `min_pyver_end_position`.""" + `min_pyver_end_position`. + """ if not check_endline: return None # pragma: no cover return value diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index f70fe540f7..ac72def41c 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -8,7 +8,8 @@ # A NamedTuple is not possible as some tests need to modify attributes during the test. class PyreverseConfig: # pylint: disable=too-many-instance-attributes, too-many-arguments """Holds the configuration options for Pyreverse. - The default values correspond to the defaults of the options' parser.""" + The default values correspond to the defaults of the options' parser. + """ def __init__( self, diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index 1cd1213a7b..df506465db 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -61,13 +61,10 @@ def emit_pragma_representer(action: str, messages: List[str]) -> PragmaRepresent class PragmaParserError(Exception): - """ - A class for exceptions thrown by pragma_parser module - """ + """A class for exceptions thrown by pragma_parser module""" def __init__(self, message: str, token: str) -> None: - """ - :args message: explain the reason why the exception has been thrown + """:args message: explain the reason why the exception has been thrown :args token: token concerned by the exception """ self.message = message @@ -76,15 +73,11 @@ def __init__(self, message: str, token: str) -> None: class UnRecognizedOptionError(PragmaParserError): - """ - Thrown in case the of a valid but unrecognized option - """ + """Thrown in case the of a valid but unrecognized option""" class InvalidPragmaError(PragmaParserError): - """ - Thrown in case the pragma is invalid - """ + """Thrown in case the pragma is invalid""" def parse_pragma(pylint_pragma: str) -> Generator[PragmaRepresenter, None, None]: diff --git a/script/bump_changelog.py b/script/bump_changelog.py index 6b8e4abbff..6e25719d4c 100644 --- a/script/bump_changelog.py +++ b/script/bump_changelog.py @@ -1,9 +1,7 @@ # ORIGINAL here: https://github.com/PyCQA/astroid/blob/main/script/bump_changelog.py # DO NOT MODIFY DIRECTLY -""" -This script permits to upgrade the changelog in astroid or pylint when releasing a version. -""" +"""This script permits to upgrade the changelog in astroid or pylint when releasing a version.""" # pylint: disable=logging-fstring-interpolation import argparse import enum diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index 58939fd4db..5227ca5ae1 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -40,7 +40,8 @@ class SleepingChecker(BaseChecker): """A checker that sleeps, the wall-clock time should reduce as we add workers As we apply a roughly constant amount of "work" in this checker any variance is - likely to be caused by the pylint system.""" + likely to be caused by the pylint system. + """ __implements__ = (pylint.interfaces.IRawChecker,) @@ -57,7 +58,8 @@ class SleepingChecker(BaseChecker): def process_module(self, _node: nodes.Module) -> None: """Sleeps for `sleep_duration` on each call - This effectively means each file costs ~`sleep_duration`+framework overhead""" + This effectively means each file costs ~`sleep_duration`+framework overhead + """ time.sleep(self.sleep_duration) @@ -65,7 +67,8 @@ class SleepingCheckerLong(BaseChecker): """A checker that sleeps, the wall-clock time should reduce as we add workers As we apply a roughly constant amount of "work" in this checker any variance is - likely to be caused by the pylint system.""" + likely to be caused by the pylint system. + """ __implements__ = (pylint.interfaces.IRawChecker,) @@ -82,7 +85,8 @@ class SleepingCheckerLong(BaseChecker): def process_module(self, _node: nodes.Module) -> None: """Sleeps for `sleep_duration` on each call - This effectively means each file costs ~`sleep_duration`+framework overhead""" + This effectively means each file costs ~`sleep_duration`+framework overhead + """ time.sleep(self.sleep_duration) @@ -111,7 +115,8 @@ class TestEstablishBaselineBenchmarks: """Naive benchmarks for the high-level pylint framework Because this benchmarks the fundamental and common parts and changes seen here will - impact everything else""" + impact everything else + """ empty_filepath = _empty_filepath() empty_file_info = FileItem( @@ -176,7 +181,8 @@ def test_baseline_lots_of_files_j1(self, benchmark): """Establish a baseline with only 'master' checker being run in -j1 We do not register any checkers except the default 'master', so the cost is just - that of the system with a lot of files registered""" + that of the system with a lot of files registered + """ if benchmark.disabled: benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling @@ -195,7 +201,8 @@ def test_baseline_lots_of_files_j10(self, benchmark): As with the -j1 variant above `test_baseline_lots_of_files_j1`, we do not register any checkers except the default 'master', so the cost is just that of - the check_parallel system across 10 workers, plus the overhead of PyLinter""" + the check_parallel system across 10 workers, plus the overhead of PyLinter + """ if benchmark.disabled: benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling @@ -213,7 +220,8 @@ def test_baseline_lots_of_files_j1_empty_checker(self, benchmark): """Baselines pylint for a single extra checker being run in -j1, for N-files We use a checker that does no work, so the cost is just that of the system at - scale""" + scale + """ if benchmark.disabled: benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling @@ -232,7 +240,8 @@ def test_baseline_lots_of_files_j10_empty_checker(self, benchmark): """Baselines pylint for a single extra checker being run in -j10, for N-files We use a checker that does no work, so the cost is just that of the system at - scale, across workers""" + scale, across workers + """ if benchmark.disabled: benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling @@ -254,7 +263,8 @@ def test_baseline_benchmark_j1_single_working_checker(self, benchmark): impact of running a simple system with -j1 against the same system with -j10. We expect this benchmark to take very close to - `numfiles*SleepingChecker.sleep_duration`""" + `numfiles*SleepingChecker.sleep_duration` + """ if benchmark.disabled: benchmark(print, "skipping, do not want to sleep in main tests") return # _only_ run this test is profiling @@ -279,7 +289,8 @@ def test_baseline_benchmark_j10_single_working_checker(self, benchmark): `error_margin*(1/J)*(numfiles*SleepingChecker.sleep_duration)` Because of the cost of the framework and system the performance difference will - *not* be 1/10 of -j1 versions.""" + *not* be 1/10 of -j1 versions. + """ if benchmark.disabled: benchmark(print, "skipping, do not want to sleep in main tests") return # _only_ run this test is profiling diff --git a/tests/checkers/unittest_design.py b/tests/checkers/unittest_design.py index fd9c78f0e2..faf8f6e9e0 100644 --- a/tests/checkers/unittest_design.py +++ b/tests/checkers/unittest_design.py @@ -56,7 +56,8 @@ def test_exclude_too_few_methods_with_value(self) -> None: def test_ignore_paths_with_no_value(self) -> None: """Test exclude-too-few-public-methods option with no value. - Compare against actual list to see if validator works.""" + Compare against actual list to see if validator works. + """ options = get_global_option(self.checker, "exclude-too-few-public-methods") assert options == [] diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 1fdb0f8bce..8aa7ec0c78 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -109,7 +109,8 @@ def testCheckKeywordParensHandlesUnnecessaryParens(self) -> None: def testNoSuperfluousParensWalrusOperatorIf(self) -> None: """Parenthesis change the meaning of assignment in the walrus operator - and so are not always superfluous:""" + and so are not always superfluous: + """ cases = [ ("if (odd := is_odd(i))\n"), ("not (foo := 5)\n"), @@ -177,8 +178,7 @@ def test_encoding_token(self) -> None: def test_disable_global_option_end_of_line() -> None: - """ - Test for issue with disabling tokenizer messages + """Test for issue with disabling tokenizer messages that extend beyond the scope of the ast tokens """ file_ = tempfile.NamedTemporaryFile("w", delete=False) diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py index 9529af3ada..88f5c79229 100644 --- a/tests/checkers/unittest_stdlib.py +++ b/tests/checkers/unittest_stdlib.py @@ -45,7 +45,8 @@ def test_deprecated_no_qname_on_unexpected_nodes(self) -> None: While this test might seem weird since it uses a transform, it's actually testing a crash that happened in production, but there was no way to retrieve the code for which this - occurred (how an AssignAttr got to be the result of a function inference beats me...)""" + occurred (how an AssignAttr got to be the result of a function inference beats me...) + """ def infer_func( node: Name, context: Optional[Any] = None diff --git a/tests/checkers/unittest_variables.py b/tests/checkers/unittest_variables.py index a59278bb99..f42c3fbae6 100644 --- a/tests/checkers/unittest_variables.py +++ b/tests/checkers/unittest_variables.py @@ -198,7 +198,8 @@ def test_nested_lambda(self) -> None: @set_config(ignored_argument_names=re.compile("arg")) def test_ignored_argument_names_no_message(self) -> None: """Make sure is_ignored_argument_names properly ignores - function arguments""" + function arguments + """ node = astroid.parse( """ def fooby(arg): diff --git a/tests/config/test_functional_config_loading.py b/tests/config/test_functional_config_loading.py index 0dacb69739..1937435f73 100644 --- a/tests/config/test_functional_config_loading.py +++ b/tests/config/test_functional_config_loading.py @@ -1,8 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -This launches the configuration functional tests. This permits to test configuration +"""This launches the configuration functional tests. This permits to test configuration files by providing a file with the appropriate extension in the ``tests/config/functional`` directory. diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py index d98b2a7a1b..72474abe3f 100644 --- a/tests/config/unittest_config.py +++ b/tests/config/unittest_config.py @@ -77,7 +77,8 @@ def test__regexp_csv_validator_invalid() -> None: class TestPyLinterOptionSetters(CheckerTestCase): """Class to check the set_config decorator and get_global_option util - for options declared in PyLinter.""" + for options declared in PyLinter. + """ class Checker(BaseChecker): name = "checker" @@ -98,7 +99,8 @@ def test_ignore_paths_with_value(self) -> None: def test_ignore_paths_with_no_value(self) -> None: """Test ignore-paths option with no value. - Compare against actual list to see if validator works.""" + Compare against actual list to see if validator works. + """ options = get_global_option(self.checker, "ignore-paths") assert options == [] diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index b172792808..5fe73bf36f 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -128,7 +128,8 @@ def test_functional_relation_extraction( self, default_config: PyreverseConfig, get_project: Callable ) -> None: """functional test of relations extraction; - different classes possibly in different modules""" + different classes possibly in different modules + """ # XXX should be catching pyreverse environment problem but doesn't # pyreverse doesn't extract the relations but this test ok project = get_project("data") diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py index dec38f541c..6fff4e5fc0 100644 --- a/tests/pyreverse/test_inspector.py +++ b/tests/pyreverse/test_inspector.py @@ -13,9 +13,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" - for the visitors.diadefs module -""" +"""for the visitors.diadefs module""" # pylint: disable=redefined-outer-name import os diff --git a/tests/pyreverse/test_printer_factory.py b/tests/pyreverse/test_printer_factory.py index f2ad7106aa..f39b90dade 100644 --- a/tests/pyreverse/test_printer_factory.py +++ b/tests/pyreverse/test_printer_factory.py @@ -3,9 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Unit tests for pylint.pyreverse.printer_factory -""" +"""Unit tests for pylint.pyreverse.printer_factory""" import pytest diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index c631917d9e..94ee858e09 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -109,7 +109,8 @@ def test_infer_node_2(mock_infer: Any, mock_get_annotation: Any) -> None: def test_infer_node_3() -> None: """Return a set containing a nodes.ClassDef object when the attribute - has a type annotation""" + has a type annotation + """ node = astroid.extract_node( """ class Component: diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index b436b65e9b..426cef68e1 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -17,9 +17,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" -Unit test for ``DiagramWriter`` -""" +"""Unit test for ``DiagramWriter``""" import codecs diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index bc27530f3b..738b3061c2 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -359,7 +359,8 @@ def test_sequential_checkers_work(self) -> None: def test_invoke_single_job(self) -> None: """Tests basic checkers functionality using just a single workderdo - This is *not* the same -j1 and does not happen under normal operation""" + This is *not* the same -j1 and does not happen under normal operation + """ linter = PyLinter(reporter=Reporter()) linter.register_checker(SequentialTestChecker(linter)) @@ -429,7 +430,8 @@ def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers) number of checkers applied. This test becomes more important if we want to change how we parametrise the - checkers, for example if we aim to batch the files across jobs.""" + checkers, for example if we aim to batch the files across jobs. + """ # define the stats we expect to get back from the runs, these should only vary # with the number of files. diff --git a/tests/test_func.py b/tests/test_func.py index 575773bb9d..3e6bf36ce1 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -39,7 +39,8 @@ def exception_str(self, ex) -> str: # pylint: disable=unused-argument """function used to replace default __str__ method of exception instances - This function is not typed because it is legacy code""" + This function is not typed because it is legacy code + """ return f"in {ex.file}\n:: {', '.join(ex.args)}" diff --git a/tests/test_self.py b/tests/test_self.py index 9f33d745c9..99bf56280d 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -183,8 +183,7 @@ def _test_output(self, args: List[str], expected_output: str) -> None: def _test_output_file( self, args: List[str], filename: LocalPath, expected_output: str ) -> None: - """ - Run Pylint with the ``output`` option set (must be included in + """Run Pylint with the ``output`` option set (must be included in the ``args`` passed to this method!) and check the file content afterwards. """ out = StringIO() @@ -1164,9 +1163,7 @@ def test_fail_on_exit_code(self, args, expected): self._runtest([path, "--fail-under=-10"] + args, code=expected) def test_one_module_fatal_error(self): - """ - Fatal errors in one of several modules linted still exits non-zero. - """ + """Fatal errors in one of several modules linted still exits non-zero.""" valid_path = join(HERE, "conftest.py") invalid_path = join(HERE, "garbagePath.py") self._runtest([valid_path, invalid_path], code=1) diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 29aac8640a..1ee9477593 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -94,7 +94,8 @@ def test_template_option_end_line(linter) -> None: def test_template_option_non_existing(linter) -> None: """Test the msg-template option with non-existent options. This makes sure that this option remains backwards compatible as new - parameters do not break on previous versions""" + parameters do not break on previous versions + """ output = StringIO() linter.reporter.out = output linter.set_option( From 619d8534d338f8093c01a6fbc195792b28f1e19e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 3 Jan 2022 14:38:50 -0500 Subject: [PATCH 121/357] Fix #5370: Emit `redefined-outer-name` when a nested except handler shadows an outer one (#5630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add regression test for #4434. Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 5 ++ doc/whatsnew/2.13.rst | 5 ++ pylint/checkers/variables.py | 26 +++++++ .../functional/r/redefined_except_handler.py | 72 +++++++++++++++++++ .../functional/r/redefined_except_handler.txt | 4 ++ 5 files changed, 112 insertions(+) create mode 100644 tests/functional/r/redefined_except_handler.py create mode 100644 tests/functional/r/redefined_except_handler.txt diff --git a/ChangeLog b/ChangeLog index aae2fe1572..2be3c8c949 100644 --- a/ChangeLog +++ b/ChangeLog @@ -115,6 +115,11 @@ Release date: TBA Partially closes #1730 +* Emit ``redefined-outer-name`` when a nested except handler shadows an outer one. + + Closes #4434 + Closes #5370 + * Fatal errors now emit a score of 0.0 regardless of whether the linted module contained any statements diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index ede1813850..787ae57b3c 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -118,6 +118,11 @@ Other Changes * Fixed crash on uninferable decorators on Python 3.6 and 3.7 +* Emit ``redefined-outer-name`` when a nested except handler shadows an outer one. + + Closes #4434 + Closes #5370 + * Fatal errors now emit a score of 0.0 regardless of whether the linted module contained any statements diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 44d54cfd27..e79f545cc3 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -846,6 +846,10 @@ def __init__(self, linter=None): self._checking_mod_attr = None self._loop_variables = [] self._type_annotation_names = [] + self._except_handler_names_queue: List[ + Tuple[nodes.ExceptHandler, nodes.AssignName] + ] = [] + """This is a queue, last in first out""" self._postponed_evaluation_enabled = False def open(self) -> None: @@ -1130,6 +1134,28 @@ def visit_name(self, node: nodes.Name) -> None: if self._is_undefined_loop_variable_enabled: self._loopvar_name(node) + @utils.check_messages("redefined-outer-name") + def visit_excepthandler(self, node: nodes.ExceptHandler) -> None: + if not node.name or not isinstance(node.name, nodes.AssignName): + return + + for outer_except, outer_except_assign_name in self._except_handler_names_queue: + if node.name.name == outer_except_assign_name.name: + self.add_message( + "redefined-outer-name", + args=(outer_except_assign_name.name, outer_except.fromlineno), + node=node, + ) + break + + self._except_handler_names_queue.append((node, node.name)) + + @utils.check_messages("redefined-outer-name") + def leave_excepthandler(self, node: nodes.ExceptHandler) -> None: + if not node.name or not isinstance(node.name, nodes.AssignName): + return + self._except_handler_names_queue.pop() + def _undefined_and_used_before_checker( self, node: nodes.Name, stmt: nodes.NodeNG ) -> None: diff --git a/tests/functional/r/redefined_except_handler.py b/tests/functional/r/redefined_except_handler.py new file mode 100644 index 0000000000..47591034a2 --- /dev/null +++ b/tests/functional/r/redefined_except_handler.py @@ -0,0 +1,72 @@ +"""Tests for except handlers that shadow outer except handlers or exceptions. + +See: https://github.com/PyCQA/pylint/issues/5370 +""" + +try: + pass +except ImportError as err: + try: + pass + except ImportError as err: # [redefined-outer-name] + pass + print(err) + +try: + pass +except ImportError as err: + try: + pass + except ImportError as err2: + pass + print(err) + +try: + try: + pass + except ImportError as err: + pass +except ImportError: + try: + pass + except ImportError as err: + pass + print(err) + + +try: + try: + pass + except ImportError as err: + pass +except ImportError as err: + try: + pass + except ImportError: + pass + print(err) + +try: + pass +except ImportError as err: + try: + pass + except ImportError as err2: + try: + pass + except ImportError as err: # [redefined-outer-name] + pass + print(err) + + +class CustomException(Exception): + """https://github.com/PyCQA/pylint/issues/4434""" + + +def func(): + """Override CustomException by except .. as ..""" + try: + raise CustomException('Test') # [used-before-assignment] + # pylint:disable-next=invalid-name, unused-variable + except IOError as CustomException: # [redefined-outer-name] + pass diff --git a/tests/functional/r/redefined_except_handler.txt b/tests/functional/r/redefined_except_handler.txt new file mode 100644 index 0000000000..93338d09b5 --- /dev/null +++ b/tests/functional/r/redefined_except_handler.txt @@ -0,0 +1,4 @@ +redefined-outer-name:11:4:12:12::Redefining name 'err' from outer scope (line 8):UNDEFINED +redefined-outer-name:57:8:58:16::Redefining name 'err' from outer scope (line 51):UNDEFINED +used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:UNDEFINED +redefined-outer-name:71:4:72:12:func:Redefining name 'CustomException' from outer scope (line 62):UNDEFINED From 84f755cd7f001e114fa4c06cafea38d8b13bc23a Mon Sep 17 00:00:00 2001 From: orSolocate <38433858+orSolocate@users.noreply.github.com> Date: Mon, 3 Jan 2022 23:13:38 +0200 Subject: [PATCH 122/357] Hotfix mypy pre-commit check under Windows (#5632) --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 1d1a8cc723..d78790d523 100644 --- a/setup.cfg +++ b/setup.cfg @@ -127,3 +127,6 @@ ignore_missing_imports = True [mypy-dill] ignore_missing_imports = True + +[mypy-colorama] +ignore_missing_imports = True From 8a45d9364b50fca2912f130fdbf0a558d1d2e114 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jan 2022 00:01:13 +0100 Subject: [PATCH 123/357] [pre-commit.ci] pre-commit autoupdate (#5633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.29.1 → v2.31.0](https://github.com/asottile/pyupgrade/compare/v2.29.1...v2.31.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37600cc60c..964df4fa48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - --remove-duplicate-keys - --remove-unused-variables - repo: https://github.com/asottile/pyupgrade - rev: v2.29.1 + rev: v2.31.0 hooks: - id: pyupgrade args: [--py36-plus] From fb4259f9dea0b9b53c2f376daa36c93f9bfc1e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 4 Jan 2022 20:27:33 +0100 Subject: [PATCH 124/357] Update astroid to ``2.9.2`` (#5634) --- requirements_test_min.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 955b6683e9..605fcf2552 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e . # astroid dependency is also defined in setup.cfg -astroid==2.9.1 # Pinned to a specific version for tests +astroid==2.9.2 # Pinned to a specific version for tests pytest~=6.2 pytest-benchmark~=3.4 gitpython>3 diff --git a/setup.cfg b/setup.cfg index d78790d523..71f9a1bf9e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ packages = find: install_requires = dill>=0.2 platformdirs>=2.2.0 - astroid>=2.9.1,<2.10 # (You should also upgrade requirements_test_min.txt) + astroid>=2.9.2,<2.10 # (You should also upgrade requirements_test_min.txt) isort>=4.2.5,<6 mccabe>=0.6,<0.7 toml>=0.9.2 From 60f60d10132bbbca7e1fda8116aa95adb11b3841 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 5 Jan 2022 09:45:57 -0500 Subject: [PATCH 125/357] Fix typos in pylint/checkers/variables.py (#5639) --- pylint/checkers/variables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index e79f545cc3..ae01a65d18 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -726,7 +726,7 @@ def _uncertain_nodes_in_except_blocks(found_nodes, node, node_statement): ): uncertain_nodes.append(other_node) else: - # Assume the except blocks execute. Possiblility for a false negative + # Assume the except blocks execute. Possibility for a false negative # if one of the except blocks does not define the name in question, # raise, or return. See: https://github.com/PyCQA/pylint/issues/5524. continue @@ -1806,7 +1806,7 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool if defstmt_frame == node_frame and not ref_node.lineno < node.lineno: break - # If the parent of the local reference is anything but a AnnAssign + # If the parent of the local reference is anything but an AnnAssign # Or if the AnnAssign adds a value the variable will now have a value # var = 1 # OR # var: int = 1 From 29997f81b05b60b6c9749f50e445de2b94e25ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 6 Jan 2022 00:03:41 +0100 Subject: [PATCH 126/357] Move ``HUMAN_READABLE_TYPES`` to ``constants`` (#5642) Ref #5311 Co-authored-by: Carli* Freudenberg --- pylint/checkers/base.py | 20 +++----------------- pylint/constants.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index c1eefab921..2fe6123f1d 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -78,7 +78,7 @@ import astroid from astroid import nodes -from pylint import checkers, interfaces +from pylint import checkers, constants, interfaces from pylint import utils as lint_utils from pylint.checkers import utils from pylint.checkers.utils import ( @@ -1672,20 +1672,6 @@ def visit_for(self, node: nodes.For) -> None: "inlinevar", } -HUMAN_READABLE_TYPES = { - "module": "module", - "const": "constant", - "class": "class", - "function": "function", - "method": "method", - "attr": "attribute", - "argument": "argument", - "variable": "variable", - "class_attribute": "class attribute", - "class_const": "class constant", - "inlinevar": "inline iteration", -} - DEFAULT_NAMING_STYLES = { "module": "snake_case", "const": "UPPER_CASE", @@ -1704,7 +1690,7 @@ def visit_for(self, node: nodes.For) -> None: def _create_naming_options(): name_options = [] for name_type in sorted(KNOWN_NAME_TYPES): - human_readable_name = HUMAN_READABLE_TYPES[name_type] + human_readable_name = constants.HUMAN_READABLE_TYPES[name_type] default_style = DEFAULT_NAMING_STYLES[name_type] name_type = name_type.replace("_", "-") name_options.append( @@ -2025,7 +2011,7 @@ def _raise_name_warning( confidence, warning: str = "invalid-name", ) -> None: - type_label = HUMAN_READABLE_TYPES[node_type] + type_label = constants.HUMAN_READABLE_TYPES[node_type] hint = self._name_hints[node_type] if prevalent_group: # This happens in the multi naming match case. The expected diff --git a/pylint/constants.py b/pylint/constants.py index d30b9dd56b..eb5b1118a5 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -55,3 +55,17 @@ class WarningScope: full_version = f"""pylint {__version__} astroid {astroid.__version__} Python {sys.version}""" + +HUMAN_READABLE_TYPES = { + "module": "module", + "const": "constant", + "class": "class", + "function": "function", + "method": "method", + "attr": "attribute", + "argument": "argument", + "variable": "variable", + "class_attribute": "class attribute", + "class_const": "class constant", + "inlinevar": "inline iteration", +} From 374d9de081d24dd04098b9b7da9ee4a2af30e0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 6 Jan 2022 10:36:27 +0100 Subject: [PATCH 127/357] Add test for exclusivity of id names (#5615) Co-authored-by: Pierre Sassoulas --- tests/message/unittest_message_id_store.py | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/message/unittest_message_id_store.py b/tests/message/unittest_message_id_store.py index 340b23d326..d1c140e9f7 100644 --- a/tests/message/unittest_message_id_store.py +++ b/tests/message/unittest_message_id_store.py @@ -1,14 +1,18 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +from pathlib import Path from typing import Dict, ValuesView import pytest +from pylint import lint from pylint.exceptions import InvalidMessageError, UnknownMessageError from pylint.message.message_definition import MessageDefinition from pylint.message.message_id_store import MessageIdStore +EMPTY_FILE = str(Path(__file__).parent.parent.resolve() / "regrtest_data" / "empty.py") + def test_len_str(msgid_store: MessageIdStore, msgids: Dict[str, str]) -> None: assert len(msgid_store) == len(msgids) @@ -90,3 +94,47 @@ def test_duplicate_msgid(msgid_store: MessageIdStore) -> None: "Message symbol 'warning-symbol' cannot be used for 'W1234' and 'W1235'" in str(error.value) ) + + +def test_exclusivity_of_msgids() -> None: + """Test to see if all checkers have an exclusive message id prefix""" + err_msg = ( + "{} has the same prefix ('{}') as the '{}' checker. Please make sure the prefix " + "is unique for each checker. You can use 'script/get_unused_message_id_category.py' " + "to get an unique id." + ) + + runner = lint.Run( + ["--enable-all-extensions", EMPTY_FILE], + exit=False, + ) + + # Some pairs are hard-coded as they are pre-existing and non-exclusive + # and we don't want to rename them for backwards compatibility + checker_id_pairs = { + "00": ("master", "miscellaneous"), + "01": ( + "basic", + "refactoring", + "consider_ternary_expression", + "while_used", + "docstyle", + "deprecated_builtins", + ), + "02": ("classes", "refactoring", "multiple_types"), + "03": ("classes", "format"), + "04": ("imports", "spelling"), + "05": ("consider-using-any-or-all", "miscellaneous"), + "07": ("exceptions", "broad_try_clause", "overlap-except"), + "12": ("design", "logging"), + "17": ("async", "refactoring"), + "20": ("compare-to-zero", "refactoring"), + } + + for msgid, definition in runner.linter.msgs_store._messages_definitions.items(): + if msgid[1:3] in checker_id_pairs: + assert ( + definition.checker_name in checker_id_pairs[msgid[1:3]] + ), err_msg.format(msgid, msgid[1:3], checker_id_pairs[msgid[1:3]][0]) + else: + checker_id_pairs[msgid[1:3]] = (definition.checker_name,) From 1e677a7b1f449b935155970a61bd5e4f3e169993 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 6 Jan 2022 05:00:06 -0500 Subject: [PATCH 128/357] Fix #5638: Allow for encoding to be supplied as a positional argument (#5641) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/stdlib.py | 19 ++++++++++++++----- .../functional/u/unspecified_encoding_py38.py | 13 +++++++++++++ .../u/unspecified_encoding_py38.txt | 4 ++++ 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2be3c8c949..95fa398374 100644 --- a/ChangeLog +++ b/ChangeLog @@ -120,6 +120,11 @@ Release date: TBA Closes #4434 Closes #5370 +* ``encoding`` can now be supplied as a positional argument to calls that open + files without triggering ``unspecified-encoding``. + + Closes #5638 + * Fatal errors now emit a score of 0.0 regardless of whether the linted module contained any statements diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 787ae57b3c..89796bd5da 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -123,6 +123,11 @@ Other Changes Closes #4434 Closes #5370 +* ``encoding`` can now be supplied as a positional argument to calls that open + files without triggering ``unspecified-encoding``. + + Closes #5638 + * Fatal errors now emit a score of 0.0 regardless of whether the linted module contained any statements diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 7093bb0da3..638b7f3c93 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -636,13 +636,22 @@ def _check_open_encoded(self, node: nodes.Call, open_module: str) -> None: ): encoding_arg = None try: - if open_module == "pathlib" and node.func.attrname == "read_text": - encoding_arg = utils.get_argument_from_call( - node, position=0, keyword="encoding" - ) + if open_module == "pathlib": + if node.func.attrname == "read_text": + encoding_arg = utils.get_argument_from_call( + node, position=0, keyword="encoding" + ) + elif node.func.attrname == "write_text": + encoding_arg = utils.get_argument_from_call( + node, position=1, keyword="encoding" + ) + else: + encoding_arg = utils.get_argument_from_call( + node, position=2, keyword="encoding" + ) else: encoding_arg = utils.get_argument_from_call( - node, position=None, keyword="encoding" + node, position=3, keyword="encoding" ) except utils.NoSuchArgumentError: self.add_message("unspecified-encoding", node=node) diff --git a/tests/functional/u/unspecified_encoding_py38.py b/tests/functional/u/unspecified_encoding_py38.py index ce5f3e8167..306f94e698 100644 --- a/tests/functional/u/unspecified_encoding_py38.py +++ b/tests/functional/u/unspecified_encoding_py38.py @@ -143,3 +143,16 @@ class IOArgs: # Test for crash reported in https://github.com/PyCQA/pylint/issues/5321 open(FILENAME, args_good_one.mode, encoding=args_good_one.encoding) + +# Positional arguments +open(FILENAME, "w", -1, "utf-8") +open(FILENAME, "w", -1) # [unspecified-encoding] + +Path(FILENAME).open("w", -1, "utf-8") +Path(FILENAME).open("w", -1) # [unspecified-encoding] + +Path(FILENAME).read_text("utf-8") +Path(FILENAME).read_text() # [unspecified-encoding] + +Path(FILENAME).write_text("string", "utf-8") +Path(FILENAME).write_text("string") # [unspecified-encoding] diff --git a/tests/functional/u/unspecified_encoding_py38.txt b/tests/functional/u/unspecified_encoding_py38.txt index 60a811ecae..59a648a40a 100644 --- a/tests/functional/u/unspecified_encoding_py38.txt +++ b/tests/functional/u/unspecified_encoding_py38.txt @@ -23,3 +23,7 @@ unspecified-encoding:81:0:81:21::Using open without explicitly specifying an enc unspecified-encoding:82:0:82:25::Using open without explicitly specifying an encoding:UNDEFINED unspecified-encoding:83:0:83:25::Using open without explicitly specifying an encoding:UNDEFINED unspecified-encoding:84:0:84:39::Using open without explicitly specifying an encoding:UNDEFINED +unspecified-encoding:149:0:149:23::Using open without explicitly specifying an encoding:UNDEFINED +unspecified-encoding:152:0:152:28::Using open without explicitly specifying an encoding:UNDEFINED +unspecified-encoding:155:0:155:26::Using open without explicitly specifying an encoding:UNDEFINED +unspecified-encoding:158:0:158:35::Using open without explicitly specifying an encoding:UNDEFINED From 1d5b3f154a590436b2538d065e41c8d1e1ded0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 6 Jan 2022 14:04:07 +0100 Subject: [PATCH 129/357] Rename CI job names (#5618) Co-authored-by: Pierre Sassoulas --- .github/workflows/ci.yaml | 20 ++++++++++---------- .github/workflows/primer-test.yaml | 14 +++++--------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4a2118b16e..56ff5a1c21 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -74,7 +74,7 @@ jobs: pre-commit install --install-hooks formatting: - name: Run pre-commit checks + name: checks / pre-commit runs-on: ubuntu-latest needs: prepare-base steps: @@ -116,7 +116,7 @@ jobs: pre-commit run pylint --all-files spelling: - name: Run spelling checks + name: checks / spelling runs-on: ubuntu-latest needs: prepare-base steps: @@ -146,7 +146,7 @@ jobs: pytest tests/ -k unittest_spelling prepare-tests-linux: - name: Prepare tests for Python ${{ matrix.python-version }} (Linux) + name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest strategy: matrix: @@ -188,7 +188,7 @@ jobs: pip install -U -r requirements_test.txt pytest-linux: - name: Run tests Python ${{ matrix.python-version }} (Linux) + name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest needs: prepare-tests-linux strategy: @@ -227,7 +227,7 @@ jobs: path: .coverage coverage: - name: Process test coverage + name: tests / process / coverage runs-on: ubuntu-latest needs: ["prepare-tests-linux", "pytest-linux"] strategy: @@ -271,7 +271,7 @@ jobs: coveralls --rcfile=${{ env.COVERAGERC_FILE }} --service=github benchmark-linux: - name: Run benchmark tests Python ${{ matrix.python-version }} (Linux) + name: tests / run benchmark / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest needs: prepare-tests-linux strategy: @@ -322,7 +322,7 @@ jobs: path: .benchmarks/ prepare-tests-windows: - name: Prepare tests for Python ${{ matrix.python-version }} (Windows) + name: tests / prepare / ${{ matrix.python-version }} / Windows runs-on: windows-latest strategy: matrix: @@ -364,7 +364,7 @@ jobs: pip install -U -r requirements_test_min.txt pytest-windows: - name: Run tests Python ${{ matrix.python-version }} (Windows) + name: tests / run / ${{ matrix.python-version }} / Windows runs-on: windows-latest needs: prepare-tests-windows strategy: @@ -402,7 +402,7 @@ jobs: pytest --benchmark-disable tests/ prepare-tests-pypy: - name: Prepare tests for Python ${{ matrix.python-version }} + name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest strategy: matrix: @@ -444,7 +444,7 @@ jobs: pip install -U -r requirements_test_min.txt pytest-pypy: - name: Run tests Python ${{ matrix.python-version }} + name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest needs: prepare-tests-pypy strategy: diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index 54ee542821..44ee898b94 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -1,4 +1,4 @@ -name: Primer tests +name: Primer on: push: @@ -22,7 +22,7 @@ concurrency: jobs: prepare-tests-linux: - name: Prepare tests for Python ${{ matrix.python-version }} (Linux) + name: prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest strategy: matrix: @@ -64,7 +64,7 @@ jobs: pip install -U -r requirements_test.txt pytest-primer-stdlib: - name: Run primer tests on stdlib Python ${{ matrix.python-version }} (Linux) + name: run on stdlib / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest needs: prepare-tests-linux strategy: @@ -98,9 +98,7 @@ jobs: pytest -m primer_stdlib --primer-stdlib -n auto pytest-primer-external-batch-one: - name: - Run primer tests batch one on external libs Python ${{ matrix.python-version }} - (Linux) + name: run on batch one / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest needs: prepare-tests-linux strategy: @@ -134,9 +132,7 @@ jobs: pytest -m primer_external_batch_one --primer-external -n auto pytest-primer-external-batch-two: - name: - Run primer tests batch two on external libs Python ${{ matrix.python-version }} - (Linux) + name: run on batch two / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest needs: prepare-tests-linux strategy: From 3fc855f9d0fa8e6410be5a23cf954ffd5471b4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 6 Jan 2022 16:43:02 +0100 Subject: [PATCH 130/357] Fix crash with slots and annotated assignments (#5494) Co-authored-by: Pierre Sassoulas --- ChangeLog | 4 +++ doc/whatsnew/2.13.rst | 4 +++ pylint/checkers/classes/class_checker.py | 16 +++++++--- .../r/regression_02/regression_5479.py | 31 +++++++++++++++++++ .../r/regression_02/regression_5479.rc | 3 ++ .../r/regression_02/regression_5479.txt | 1 + 6 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 tests/functional/r/regression_02/regression_5479.py create mode 100644 tests/functional/r/regression_02/regression_5479.rc create mode 100644 tests/functional/r/regression_02/regression_5479.txt diff --git a/ChangeLog b/ChangeLog index 95fa398374..2bf37a089a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -72,6 +72,10 @@ Release date: TBA Closes #5360, #3877 +* Fixed crash with slots assignments and annotated assignments. + + Closes #5479 + * Fixed crash on list comprehensions that used ``type`` as inner variable name. Closes #5461 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 89796bd5da..c1b5922378 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -70,6 +70,10 @@ Other Changes Closes #5504 +* Fixed crash with slots assignments and annotated assignments. + + Closes #5479 + * Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables or ``not in`` checks diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index fce2f7025f..452d57c1b6 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1495,11 +1495,17 @@ def _check_in_slots(self, node): # Properties circumvent the slots mechanism, # so we should not emit a warning for them. return - if node.attrname in klass.locals and _has_data_descriptor( - klass, node.attrname - ): - # Descriptors circumvent the slots mechanism as well. - return + if node.attrname in klass.locals: + for local_name in klass.locals.get(node.attrname): + statement = local_name.statement(future=True) + if ( + isinstance(statement, nodes.AnnAssign) + and not statement.value + ): + return + if _has_data_descriptor(klass, node.attrname): + # Descriptors circumvent the slots mechanism as well. + return if node.attrname == "__class__" and _has_same_layout_slots( slots, node.parent.value ): diff --git a/tests/functional/r/regression_02/regression_5479.py b/tests/functional/r/regression_02/regression_5479.py new file mode 100644 index 0000000000..adc20f26ca --- /dev/null +++ b/tests/functional/r/regression_02/regression_5479.py @@ -0,0 +1,31 @@ +"""Test for a regression on slots and annotated assignments. +Reported in https://github.com/PyCQA/pylint/issues/5479 +""" +# pylint: disable=too-few-public-methods, unused-private-member, missing-class-docstring, missing-function-docstring + +from __future__ import annotations + +import asyncio + + +class Connector: + __slots__ = ("_Connector__reader", "_Connector__writer") + + __reader: asyncio.StreamReader + __writer: asyncio.StreamWriter + + def __init__(self) -> None: + raise TypeError("Use connect() instead") + + @classmethod + async def connect(cls, socket: str) -> Connector: + self = cls.__new__(cls) + self.__reader, self.__writer = await asyncio.open_unix_connection(socket) + return self + + +async def main(): + conn = await Connector.connect("/tmp/mysocket") # [unused-variable] + + +asyncio.run(main()) diff --git a/tests/functional/r/regression_02/regression_5479.rc b/tests/functional/r/regression_02/regression_5479.rc new file mode 100644 index 0000000000..5793d19de2 --- /dev/null +++ b/tests/functional/r/regression_02/regression_5479.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.7 +except_implementations=PyPy diff --git a/tests/functional/r/regression_02/regression_5479.txt b/tests/functional/r/regression_02/regression_5479.txt new file mode 100644 index 0000000000..b7771bd930 --- /dev/null +++ b/tests/functional/r/regression_02/regression_5479.txt @@ -0,0 +1 @@ +unused-variable:28:4:28:8:main:Unused variable 'conn':UNDEFINED From 0df7cd8fd0bb7cb1c2be040e4b63e26b014d1d84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 14:31:09 +0100 Subject: [PATCH 131/357] Bump types-toml from 0.10.1 to 0.10.3 (#5657) Bumps [types-toml](https://github.com/python/typeshed) from 0.10.1 to 0.10.3. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-toml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1fd84ce7d4..0bad61018d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,4 +10,4 @@ pytest-profiling~=1.7 pytest-xdist~=2.5 # Type packages for mypy types-pkg_resources==0.1.3 -types-toml==0.10.1 +types-toml==0.10.3 From 8168a6b3862376c28664743b83eb5a844b1ad20c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 14:48:56 +0100 Subject: [PATCH 132/357] Bump mypy from 0.930 to 0.931 (#5659) Bumps [mypy](https://github.com/python/mypy) from 0.930 to 0.931. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.930...v0.931) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test_pre_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 007e59b4fc..37ad883f5d 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,4 @@ black==21.12b0 flake8==4.0.1 flake8-typing-imports==1.12.0 isort==5.10.1 -mypy==0.930 +mypy==0.931 From 32f47f04990e85c4872b94ece0e028838b22e35d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 15:01:55 +0100 Subject: [PATCH 133/357] Bump astroid from 2.9.2 to 2.9.3 (#5658) Bumps [astroid](https://github.com/PyCQA/astroid) from 2.9.2 to 2.9.3. - [Release notes](https://github.com/PyCQA/astroid/releases) - [Changelog](https://github.com/PyCQA/astroid/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/astroid/compare/v2.9.2...v2.9.3) --- updated-dependencies: - dependency-name: astroid dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: Pierre Sassoulas Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- requirements_test_min.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 605fcf2552..4b71e937a1 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e . # astroid dependency is also defined in setup.cfg -astroid==2.9.2 # Pinned to a specific version for tests +astroid==2.9.3 # Pinned to a specific version for tests pytest~=6.2 pytest-benchmark~=3.4 gitpython>3 From 901d3cab84163b136e8aadd68a45822daca3248c Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 10 Jan 2022 12:07:55 -0500 Subject: [PATCH 134/357] Diff generated by python test_functional.py --update-functional-output (#5660) --- tests/functional/e/.#emacs_file_lock_redefined_conf.txt | 2 +- .../raise/missing_raises_doc_required_exc_inheritance.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/e/.#emacs_file_lock_redefined_conf.txt b/tests/functional/e/.#emacs_file_lock_redefined_conf.txt index 52050dcc84..41b31279f7 100644 --- a/tests/functional/e/.#emacs_file_lock_redefined_conf.txt +++ b/tests/functional/e/.#emacs_file_lock_redefined_conf.txt @@ -1 +1 @@ -invalid-name:1:0:None:None::Module name "#emacs_file_lock_redefined_conf" doesn't conform to snake_case naming style:HIGH +invalid-name:1:0:None:None::"Module name ""#emacs_file_lock_redefined_conf"" doesn't conform to snake_case naming style":HIGH diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt index 411cb77d5c..e566c8a856 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt @@ -1 +1 @@ -missing-raises-doc:12:0:18:25:test_find_missing_raise_for_parent:"""NameError""" not documented as being raised:UNDEFINED +missing-raises-doc:12:0:18:25:test_find_missing_raise_for_parent:"""NameError"" not documented as being raised":UNDEFINED From 83a4b8bdfcf31bc1f690de77794d9268a3c2d5ca Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 10 Jan 2022 14:48:14 -0500 Subject: [PATCH 135/357] Don't assume direct parentage when emitting `used-before-assignment` (#5582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix #4045: Don't assume try ancestors are immediate parents when emitting `used-before-assignment` Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- ChangeLog | 6 ++ doc/whatsnew/2.13.rst | 6 ++ pylint/checkers/utils.py | 16 ++++ pylint/checkers/variables.py | 75 +++++++++++++++---- .../u/use/used_before_assignment_issue2615.py | 51 +++++++++++++ .../use/used_before_assignment_issue2615.txt | 4 +- 6 files changed, 142 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2bf37a089a..df0a7313ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -33,6 +33,12 @@ Release date: TBA Closes #85, #2615 +* Fixed false negative for ``used-before-assignment`` when a conditional + or context manager intervened before the try statement that suggested + it might fail. + + Closes #4045 + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index c1b5922378..10970a1dd0 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -84,6 +84,12 @@ Other Changes Closes #85, #2615 +* Fixed false negative for ``used-before-assignment`` when a conditional + or context manager intervened before the try statement that suggested + it might fail. + + Closes #4045 + * Fix a false positive for ``assigning-non-slot`` when the slotted class defined ``__setattr__``. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index c20342c82e..f421230482 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1713,3 +1713,19 @@ def get_node_first_ancestor_of_type( if isinstance(ancestor, ancestor_type): return ancestor return None + + +def get_node_first_ancestor_of_type_and_its_child( + node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node]]] +) -> Union[Tuple[None, None], Tuple[T_Node, nodes.NodeNG]]: + """Modified version of get_node_first_ancestor_of_type to also return the + descendant visited directly before reaching the sought ancestor. Useful + for extracting whether a statement is guarded by a try, except, or finally + when searching for a TryFinally ancestor. + """ + child = node + for ancestor in node.node_ancestors(): + if isinstance(ancestor, ancestor_type): + return (ancestor, child) + child = ancestor + return None, None diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index ae01a65d18..f01866276b 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -672,26 +672,24 @@ def get_next_to_consume(self, node): # If this node is in an ExceptHandler, # filter out assignments in the try portion, assuming they may fail - if found_nodes and isinstance(node_statement.parent, nodes.ExceptHandler): - filtered_nodes = [ - n - for n in found_nodes - if not ( - isinstance(n.statement(future=True).parent, nodes.TryExcept) - and n.statement(future=True) in n.statement(future=True).parent.body - and node_statement.parent - in n.statement(future=True).parent.handlers + if found_nodes: + uncertain_nodes = ( + self._uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( + found_nodes, node_statement ) - ] - filtered_nodes_set = set(filtered_nodes) - difference = [n for n in found_nodes if n not in filtered_nodes_set] - self.consumed_uncertain[node.name] += difference - found_nodes = filtered_nodes + ) + self.consumed_uncertain[node.name] += uncertain_nodes + uncertain_nodes_set = set(uncertain_nodes) + found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set] return found_nodes @staticmethod - def _uncertain_nodes_in_except_blocks(found_nodes, node, node_statement): + def _uncertain_nodes_in_except_blocks( + found_nodes: List[nodes.NodeNG], + node: nodes.NodeNG, + node_statement: nodes.Statement, + ) -> List[nodes.NodeNG]: """Return any nodes in ``found_nodes`` that should be treated as uncertain because they are in an except block. """ @@ -734,6 +732,53 @@ def _uncertain_nodes_in_except_blocks(found_nodes, node, node_statement): uncertain_nodes.append(other_node) return uncertain_nodes + @staticmethod + def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( + found_nodes: List[nodes.NodeNG], node_statement: nodes.Statement + ) -> List[nodes.NodeNG]: + """Return any nodes in ``found_nodes`` that should be treated as uncertain + because they are in a try block and the ``node_statement`` being evaluated + is in one of its except handlers. + """ + uncertain_nodes: List[nodes.NodeNG] = [] + closest_except_handler = utils.get_node_first_ancestor_of_type( + node_statement, nodes.ExceptHandler + ) + if closest_except_handler is None: + return uncertain_nodes + for other_node in found_nodes: + other_node_statement = other_node.statement(future=True) + # If the other statement is the except handler guarding `node`, it executes + if other_node_statement is closest_except_handler: + continue + # Ensure other_node is in a try block + ( + other_node_try_ancestor, + other_node_try_ancestor_visited_child, + ) = utils.get_node_first_ancestor_of_type_and_its_child( + other_node_statement, nodes.TryExcept + ) + if other_node_try_ancestor is None: + continue + if ( + other_node_try_ancestor_visited_child + not in other_node_try_ancestor.body + ): + continue + # Make sure nesting is correct -- there should be at least one + # except handler that is a sibling attached to the try ancestor, + # or is an ancestor of the try ancestor. + if not any( + closest_except_handler in other_node_try_ancestor.handlers + or other_node_try_ancestor_except_handler + in closest_except_handler.node_ancestors() + for other_node_try_ancestor_except_handler in other_node_try_ancestor.handlers + ): + continue + # Passed all tests for uncertain execution + uncertain_nodes.append(other_node) + return uncertain_nodes + # pylint: disable=too-many-public-methods class VariablesChecker(BaseChecker): diff --git a/tests/functional/u/use/used_before_assignment_issue2615.py b/tests/functional/u/use/used_before_assignment_issue2615.py index 912c713878..bce073bf3c 100644 --- a/tests/functional/u/use/used_before_assignment_issue2615.py +++ b/tests/functional/u/use/used_before_assignment_issue2615.py @@ -4,6 +4,57 @@ def main(): try: res = 1 / 0 res = 42 + if main(): + res = None + with open(__file__, encoding="utf-8") as opened_file: + res = opened_file.readlines() except ZeroDivisionError: print(res) # [used-before-assignment] print(res) + + +def nested_except_blocks(): + """Assignments in an except are tested against possibly failing + assignments in try blocks at two different nesting levels.""" + try: + res = 1 / 0 + res = 42 + if main(): + res = None + with open(__file__, encoding="utf-8") as opened_file: + res = opened_file.readlines() + except ZeroDivisionError: + try: + more_bad_division = 1 / 0 + except ZeroDivisionError: + print(more_bad_division) # [used-before-assignment] + print(res) # [used-before-assignment] + print(res) + + +def consecutive_except_blocks(): + """An assignment assumed to execute in one TryExcept should continue to be + assumed to execute in a consecutive TryExcept. + """ + try: + res = 100 + except ZeroDivisionError: + pass + try: + pass + except ValueError: + print(res) + + +def name_earlier_in_except_block(): + """Permit the name that might not have been assigned during the try block + to be defined inside a conditional inside the except block. + """ + try: + res = 1 / 0 + except ZeroDivisionError: + if main(): + res = 10 + else: + res = 11 + print(res) diff --git a/tests/functional/u/use/used_before_assignment_issue2615.txt b/tests/functional/u/use/used_before_assignment_issue2615.txt index ce6e4b9d06..0da3892c96 100644 --- a/tests/functional/u/use/used_before_assignment_issue2615.txt +++ b/tests/functional/u/use/used_before_assignment_issue2615.txt @@ -1 +1,3 @@ -used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED +used-before-assignment:12:14:12:17:main:Using variable 'res' before assignment:UNDEFINED +used-before-assignment:30:18:30:35:nested_except_blocks:Using variable 'more_bad_division' before assignment:UNDEFINED +used-before-assignment:31:18:31:21:nested_except_blocks:Using variable 'res' before assignment:UNDEFINED From d2475b42e3f4b17ca39eedd1ac65a929f70ca02e Mon Sep 17 00:00:00 2001 From: Kound Date: Mon, 10 Jan 2022 22:48:46 +0100 Subject: [PATCH 136/357] Improve non ascii checker (#5643) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * split ``non-ascii-name`` into 3 different msgs - non-ascii-identifier (replaces non-ascii-name) - non-ascii-file-name (a warning) - non-ascii-module-import (only considering the namespace the import is imported in) Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- CONTRIBUTORS.txt | 3 + ChangeLog | 9 + doc/whatsnew/2.13.rst | 9 + pylint/checkers/__init__.py | 9 +- pylint/checkers/base.py | 27 +- pylint/checkers/non_ascii_names.py | 201 +++++++++++++ pylint/constants.py | 1 + pylint/testutils/checker_test_case.py | 15 +- tests/checkers/unittest_non_ascii_name.py | 279 ++++++++++++++++++ tests/functional/n/non/non_ascii_name.rc | 3 - tests/functional/n/non/non_ascii_name.txt | 4 +- .../non/non_ascii_name_backward_test_code.py | 7 + .../n/non/non_ascii_name_backward_test_msg.py | 7 + .../functional/n/non_ascii_import/__init__.py | 0 .../n/non_ascii_import/non_ascii_import.py | 13 + .../non_ascii_import_as_bad.py | 6 + .../non_ascii_import_as_bad.txt | 1 + .../non_ascii_import_as_okay.py | 12 + .../non_ascii_import_from_as.py | 6 + .../non_ascii_import_from_as.txt | 1 + .../non_ascii_name_lo\305\202.py" | 1 + .../non_ascii_name_lo\305\202.txt" | 1 + tests/functional/n/non_ascii_name/__init__.py | 0 .../non_ascii_name_assignment_expressions.py | 4 + .../non_ascii_name_assignment_expressions.rc | 2 + .../non_ascii_name_assignment_expressions.txt | 1 + .../non_ascii_name_decorator.py | 19 ++ .../non_ascii_name_decorator.rc | 2 + .../non_ascii_name_decorator.txt | 1 + .../non_ascii_name_dict_kwargs.py | 13 + .../non_ascii_name/non_ascii_name_for_loop.py | 14 + .../non_ascii_name_for_loop.txt | 1 + .../non_ascii_name/non_ascii_name_function.py | 19 ++ .../non_ascii_name_function.txt | 1 + .../non_ascii_name_function_argument_py38.py | 24 ++ .../non_ascii_name_function_argument_py38.rc | 3 + .../non_ascii_name_function_argument_py38.txt | 2 + ...n_ascii_name_function_argument_py39plus.py | 25 ++ ...n_ascii_name_function_argument_py39plus.rc | 2 + ..._ascii_name_function_argument_py39plus.txt | 2 + .../non_ascii_name_inline_var.py | 8 + .../non_ascii_name_inline_var.txt | 1 + .../non_ascii_name_kwargs_py38.py | 17 ++ .../non_ascii_name_kwargs_py38.rc | 3 + .../non_ascii_name_kwargs_py38.txt | 1 + .../non_ascii_name_kwargs_py39plus.py | 18 ++ .../non_ascii_name_kwargs_py39plus.rc | 2 + .../non_ascii_name_kwargs_py39plus.txt | 1 + .../n/non_ascii_name/non_ascii_name_local.py | 9 + .../n/non_ascii_name/non_ascii_name_local.txt | 1 + .../non_ascii_name_pos_and_kwonly_function.py | 24 ++ .../non_ascii_name_pos_and_kwonly_function.rc | 2 + ...non_ascii_name_pos_and_kwonly_function.txt | 4 + .../non_ascii_name_staticmethod.py | 17 ++ .../non_ascii_name_staticmethod.txt | 1 + .../non_ascii_name_try_except.py | 11 + .../non_ascii_name_try_except.txt | 1 + .../non_ascii_name/non_ascii_name_variable.py | 9 + .../non_ascii_name_variable.txt | 2 + .../n/non_ascii_name_class/__init__.py | 0 .../non_ascii_name_class.py | 23 ++ .../non_ascii_name_class.txt | 1 + .../non_ascii_name_class_attribute.py | 25 ++ .../non_ascii_name_class_attribute.txt | 1 + .../non_ascii_name_class_constant.py | 23 ++ .../non_ascii_name_class_constant.txt | 1 + .../non_ascii_name_class_method.py | 18 ++ .../non_ascii_name_class_method.txt | 1 + 68 files changed, 945 insertions(+), 30 deletions(-) create mode 100644 pylint/checkers/non_ascii_names.py create mode 100644 tests/checkers/unittest_non_ascii_name.py delete mode 100644 tests/functional/n/non/non_ascii_name.rc create mode 100644 tests/functional/n/non/non_ascii_name_backward_test_code.py create mode 100644 tests/functional/n/non/non_ascii_name_backward_test_msg.py create mode 100644 tests/functional/n/non_ascii_import/__init__.py create mode 100644 tests/functional/n/non_ascii_import/non_ascii_import.py create mode 100644 tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py create mode 100644 tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt create mode 100644 tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py create mode 100644 tests/functional/n/non_ascii_import/non_ascii_import_from_as.py create mode 100644 tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt create mode 100644 "tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" create mode 100644 "tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" create mode 100644 tests/functional/n/non_ascii_name/__init__.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_decorator.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_local.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_local.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_try_except.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_variable.py create mode 100644 tests/functional/n/non_ascii_name/non_ascii_name_variable.txt create mode 100644 tests/functional/n/non_ascii_name_class/__init__.py create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class.py create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py create mode 100644 tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index eee4da9a43..aa4fb2e245 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -595,3 +595,6 @@ contributors: * Eero Vuojolahti: contributor * Kian-Meng, Ang: contributor + +* Carli* Freudenberg (CarliJoy): contributor + - Improve non-ascii-name checker diff --git a/ChangeLog b/ChangeLog index df0a7313ba..424d8b064f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,15 @@ Release date: TBA Closes #5588 +* Rewrote checker for ``non-ascii-name``. + It now ensures __all__ Python names are ASCII and also properly + checks the names of imports (``non-ascii-module-import``) as + well as file names (``non-ascii-file-name``) and emits their respective new warnings. + + Non ASCII characters could be homoglyphs (look alike characters) and hard to + enter on a non specialized keyboard. + See `Confusable Characters in PEP 672 `_ + * When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with the ``dill`` package. The ``dill`` package has therefore been added as a dependency. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 10970a1dd0..854fef618d 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -14,6 +14,15 @@ New checkers Closes #5460 +* Rewrote Checker of ``non-ascii-name``. + It now ensures __all__ Python names are ASCII and also properly + checks the names of imports (``non-ascii-module-import``) as + well as file names (``non-ascii-file-name``) and emits their respective new warnings. + + Non ASCII characters could be homoglyphs (look alike characters) and hard to + enter on a non specialized keyboard. + See `Confusable Characters in PEP 672 `_ + Removed checkers ================ diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 273b085a05..914425313e 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -39,7 +39,14 @@ 15: stdlib 16: python3 17: refactoring -18-50: not yet used: reserved for future internal checkers. +. +. +. +24: non-ascii-names +25-50: not yet used: reserved for future internal checkers. +This file is not updated. Use + script/get_unused_message_id_category.py +to get the next free checker id. 51-99: perhaps used: reserved for external checkers The raw_metrics checker has no number associated since it doesn't emit any diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 2fe6123f1d..2f79f852e0 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -1737,11 +1737,6 @@ class NameChecker(_BasicChecker): ] }, ), - "C0144": ( - '%s name "%s" contains a non-ASCII unicode character', - "non-ascii-name", - "Used when the name contains at least one non-ASCII unicode character.", - ), "W0111": ( "Name %s will become a keyword in Python %s", "assign-to-new-keyword", @@ -1838,7 +1833,6 @@ def __init__(self, linter): self._name_hints = {} self._good_names_rgxs_compiled = [] self._bad_names_rgxs_compiled = [] - self._non_ascii_rgx_compiled = re.compile("[^\u0000-\u007F]") def open(self): self.linter.stats.reset_bad_names() @@ -1878,7 +1872,7 @@ def _create_naming_rules(self): return regexps, hints - @utils.check_messages("disallowed-name", "invalid-name", "non-ascii-name") + @utils.check_messages("disallowed-name", "invalid-name") def visit_module(self, node: nodes.Module) -> None: self._check_name("module", node.name.split(".")[-1], node) self._bad_names = {} @@ -1904,9 +1898,7 @@ def leave_module(self, _: nodes.Module) -> None: for args in warnings: self._raise_name_warning(prevalent_group, *args) - @utils.check_messages( - "disallowed-name", "invalid-name", "assign-to-new-keyword", "non-ascii-name" - ) + @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword") def visit_classdef(self, node: nodes.ClassDef) -> None: self._check_assign_to_new_keyword_violation(node.name, node) self._check_name("class", node.name, node) @@ -1914,9 +1906,7 @@ def visit_classdef(self, node: nodes.ClassDef) -> None: if not any(node.instance_attr_ancestors(attr)): self._check_name("attr", attr, anodes[0]) - @utils.check_messages( - "disallowed-name", "invalid-name", "assign-to-new-keyword", "non-ascii-name" - ) + @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword") def visit_functiondef(self, node: nodes.FunctionDef) -> None: # Do not emit any warnings if the method is just an implementation # of a base class method. @@ -1944,14 +1934,12 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: visit_asyncfunctiondef = visit_functiondef - @utils.check_messages("disallowed-name", "invalid-name", "non-ascii-name") + @utils.check_messages("disallowed-name", "invalid-name") def visit_global(self, node: nodes.Global) -> None: for name in node.names: self._check_name("const", name, node) - @utils.check_messages( - "disallowed-name", "invalid-name", "assign-to-new-keyword", "non-ascii-name" - ) + @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword") def visit_assignname(self, node: nodes.AssignName) -> None: """check module level assigned names""" self._check_assign_to_new_keyword_violation(node.name, node) @@ -2041,11 +2029,6 @@ def _name_disallowed_by_regex(self, name: str) -> bool: def _check_name(self, node_type, name, node, confidence=interfaces.HIGH): """check for a name using the type's regexp""" - non_ascii_match = self._non_ascii_rgx_compiled.match(name) - if non_ascii_match is not None: - self._raise_name_warning( - None, node, node_type, name, confidence, warning="non-ascii-name" - ) def _should_exempt_from_invalid_name(node): if node_type == "variable": diff --git a/pylint/checkers/non_ascii_names.py b/pylint/checkers/non_ascii_names.py new file mode 100644 index 0000000000..6ad0df556b --- /dev/null +++ b/pylint/checkers/non_ascii_names.py @@ -0,0 +1,201 @@ +# Copyright (c) 2021-2022 Carli Freudenberg + +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +"""All alphanumeric unicode character are allowed in Python but due +to similarities in how they look they can be confused. + +See: https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers + +The following checkers are intended to make users are aware of these issues. +""" + +import sys +from typing import Optional, Union + +from astroid import nodes + +from pylint import constants, interfaces, lint +from pylint.checkers import base_checker, utils + +if sys.version_info[:2] >= (3, 7): + # pylint: disable-next=fixme + # TODO: Remove after 3.6 has been deprecated + Py37Str = str +else: + + class Py37Str(str): + # Allow Python 3.6 compatibility + def isascii(self: str) -> bool: + return all("\u0000" <= x <= "\u007F" for x in self) + + +NON_ASCII_HELP = ( + "Used when the name contains at least one non-ASCII unicode character. " + "See https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers" + " for a background why this could be bad. \n" + "If your programming guideline defines that you are programming in " + "English, then there should be no need for non ASCII characters in " + "Python Names. If not you can simply disable this check." +) + + +class NonAsciiNameChecker(base_checker.BaseChecker): + """A strict name checker only allowing ASCII + + Note: This check only checks Names, so it ignores the content of + docstrings and comments! + """ + + __implements__ = interfaces.IAstroidChecker + priority = -1 + + msgs = { + "C2401": ( + '%s name "%s" contains a non-ASCII character, consider renaming it.', + "non-ascii-name", + NON_ASCII_HELP, + {"old_names": [("C0144", "old-non-ascii-name")]}, + ), + # First %s will always be "file" + "W2402": ( + ( + '%s name "%s" contains a non-ASCII character. PEP 3131 only allows ' + "non-ascii identifiers, not file names." + ), + "non-ascii-file-name", + ( + # Some = PyCharm at the time of writing didn't display the non_ascii_name_loł + # files and had big troubles with git. + # Probably only a bug shows the problem quite good. + # That's also why this is a warning and not only a convention! + "Some editors don't support non-ASCII file names properly. " + "Even though Python supports UTF-8 files since Python 3.5 this isn't " + "recommended for interoperability. Further reading:\n" + "- https://www.python.org/dev/peps/pep-0489/#export-hook-name\n" + "- https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers\n" + "- https://bugs.python.org/issue20485\n" + ), + ), + # First %s will always be "module" + "C2403": ( + '%s name "%s" contains a non-ASCII character, use an ASCII-only alias for import.', + "non-ascii-module-import", + NON_ASCII_HELP, + ), + } + + name = "NonASCII-Checker" + + def _check_name( + self, node_type: str, name: Optional[str], node: nodes.NodeNG + ) -> None: + """Check whether a name is using non-ASCII characters.""" + + if name is None: + # For some nodes i.e. *kwargs from a dict, the name will be empty + return + + if not (Py37Str(name).isascii()): + type_label = constants.HUMAN_READABLE_TYPES[node_type] + args = (type_label.capitalize(), name) + + msg = "non-ascii-name" + + # Some node types have customized messages + if node_type == "file": + msg = "non-ascii-file-name" + elif node_type == "module": + msg = "non-ascii-module-import" + + self.add_message(msg, node=node, args=args, confidence=interfaces.HIGH) + + @utils.check_messages("non-ascii-name") + def visit_module(self, node: nodes.Module) -> None: + self._check_name("file", node.name.split(".")[-1], node) + + @utils.check_messages("non-ascii-name") + def visit_functiondef( + self, node: Union[nodes.FunctionDef, nodes.AsyncFunctionDef] + ) -> None: + self._check_name("function", node.name, node) + + # Check argument names + arguments = node.args + + # Check position only arguments + if arguments.posonlyargs: + for pos_only_arg in arguments.posonlyargs: + self._check_name("argument", pos_only_arg.name, pos_only_arg) + + # Check "normal" arguments + if arguments.args: + for arg in arguments.args: + self._check_name("argument", arg.name, arg) + + # Check key word only arguments + if arguments.kwonlyargs: + for kwarg in arguments.kwonlyargs: + self._check_name("argument", kwarg.name, kwarg) + + visit_asyncfunctiondef = visit_functiondef + + @utils.check_messages("non-ascii-name") + def visit_global(self, node: nodes.Global) -> None: + for name in node.names: + self._check_name("const", name, node) + + @utils.check_messages("non-ascii-name") + def visit_assignname(self, node: nodes.AssignName) -> None: + """check module level assigned names""" + # The NameChecker from which this Checker originates knows a lot of different + # versions of variables, i.e. constants, inline variables etc. + # To simplify we use only `variable` here, as we don't need to apply different + # rules to different types of variables. + frame = node.frame() + + if isinstance(frame, nodes.FunctionDef): + if node.parent in frame.body: + # Only perform the check if the assignment was done in within the body + # of the function (and not the function parameter definition + # (will be handled in visit_functiondef) + # or within a decorator (handled in visit_call) + self._check_name("variable", node.name, node) + elif isinstance(frame, nodes.ClassDef): + self._check_name("attr", node.name, node) + else: + # Possibilities here: + # - isinstance(node.assign_type(), nodes.Comprehension) == inlinevar + # - isinstance(frame, nodes.Module) == variable (constant?) + # - some other kind of assigment missed but still most likely a variable + self._check_name("variable", node.name, node) + + @utils.check_messages("non-ascii-name") + def visit_classdef(self, node: nodes.ClassDef) -> None: + self._check_name("class", node.name, node) + for attr, anodes in node.instance_attrs.items(): + if not any(node.instance_attr_ancestors(attr)): + self._check_name("attr", attr, anodes[0]) + + def _check_module_import(self, node: Union[nodes.ImportFrom, nodes.Import]) -> None: + for module_name, alias in node.names: + name = alias or module_name + self._check_name("module", name, node) + + @utils.check_messages("non-ascii-name") + def visit_import(self, node: nodes.Import) -> None: + self._check_module_import(node) + + @utils.check_messages("non-ascii-name") + def visit_importfrom(self, node: nodes.ImportFrom) -> None: + self._check_module_import(node) + + @utils.check_messages("non-ascii-name") + def visit_call(self, node: nodes.Call) -> None: + """Check if the used keyword args are correct.""" + for keyword in node.keywords: + self._check_name("argument", keyword.arg, keyword) + + +def register(linter: lint.PyLinter) -> None: + linter.register_checker(NonAsciiNameChecker(linter)) diff --git a/pylint/constants.py b/pylint/constants.py index eb5b1118a5..db54113ee4 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -57,6 +57,7 @@ class WarningScope: Python {sys.version}""" HUMAN_READABLE_TYPES = { + "file": "file", "module": "module", "const": "constant", "class": "class", diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index af72136a9d..5fabf97c5b 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -32,12 +32,18 @@ def assertNoMessages(self): yield @contextlib.contextmanager - def assertAddsMessages(self, *messages: MessageTest) -> Generator[None, None, None]: + def assertAddsMessages( + self, *messages: MessageTest, ignore_position: bool = False + ) -> Generator[None, None, None]: """Assert that exactly the given method adds the given messages. The list of messages must exactly match *all* the messages added by the method. Additionally, we check to see whether the args in each message can actually be substituted into the message string. + + Using the keyword argument `ignore_position`, all checks for position + arguments (line, col_offset, ...) will be skipped. This can be used to + just test messages for the correct node. """ yield got = self.linter.release_messages() @@ -53,10 +59,15 @@ def assertAddsMessages(self, *messages: MessageTest) -> Generator[None, None, No for expected_msg, gotten_msg in zip(messages, got): assert expected_msg.msg_id == gotten_msg.msg_id, msg - assert expected_msg.line == gotten_msg.line, msg assert expected_msg.node == gotten_msg.node, msg assert expected_msg.args == gotten_msg.args, msg assert expected_msg.confidence == gotten_msg.confidence, msg + + if ignore_position: + # Do not check for line, col_offset etc... + continue + + assert expected_msg.line == gotten_msg.line, msg assert expected_msg.col_offset == gotten_msg.col_offset, msg if PY38_PLUS: # pylint: disable=fixme diff --git a/tests/checkers/unittest_non_ascii_name.py b/tests/checkers/unittest_non_ascii_name.py new file mode 100644 index 0000000000..d0e3a85ab7 --- /dev/null +++ b/tests/checkers/unittest_non_ascii_name.py @@ -0,0 +1,279 @@ +import sys +from typing import Iterable, Optional + +import astroid +import pytest +from astroid import nodes + +import pylint.checkers.non_ascii_names +import pylint.interfaces +import pylint.testutils + + +class TestNonAsciiChecker(pylint.testutils.CheckerTestCase): + CHECKER_CLASS = pylint.checkers.non_ascii_names.NonAsciiNameChecker + checker: pylint.checkers.non_ascii_names.NonAsciiNameChecker + + @pytest.mark.skipif( + sys.version_info < (3, 8), reason="requires python3.8 or higher" + ) + def test_kwargs_and_position_only(self): + """Even the new position only and keyword only should be found""" + node = astroid.extract_node( + """ + def name( + ok, + not_økay, + not_okay_defaułt=None, + /, + p_or_kw_okay=None, + p_or_kw_not_økay=None, + *, + kw_arg_okay, + kw_arg_not_økay, + ): + ... + """ + ) + assert isinstance(node, nodes.FunctionDef) + arguments = node.args + + posargs = list(arguments.posonlyargs) + args = list(arguments.args) + kwargs = list(arguments.kwonlyargs) + + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id="non-ascii-name", + node=posargs[1], + args=("Argument", "not_økay"), + confidence=pylint.interfaces.HIGH, + ), + pylint.testutils.MessageTest( + msg_id="non-ascii-name", + node=posargs[2], + args=("Argument", "not_okay_defaułt"), + confidence=pylint.interfaces.HIGH, + ), + pylint.testutils.MessageTest( + msg_id="non-ascii-name", + node=args[1], + args=("Argument", "p_or_kw_not_økay"), + confidence=pylint.interfaces.HIGH, + ), + pylint.testutils.MessageTest( + msg_id="non-ascii-name", + node=kwargs[1], + args=("Argument", "kw_arg_not_økay"), + confidence=pylint.interfaces.HIGH, + ), + ignore_position=True, + ): + self.checker.visit_functiondef(node) + + @pytest.mark.parametrize( + "code, assign_type", + [ + pytest.param( + """ + try: + ... + except ValueError as łol: #@ + ... + """, + "Variable", + id="try-except", + ), + pytest.param( + """ + class FooBar: + łol = "test" #@ + """, + "Attribute", + id="class_attribute", + ), + pytest.param( + """ + łol = "test" #@ + """, + "Variable", + id="global_assign", + ), + pytest.param( + """ + def foobar(): + łol="test" #@ + """, + "Variable", + id="function_variable", + ), + pytest.param( + """ + for łol in os.listdir("."): #@ + ... + """, + "Variable", + id="for_loop_variable", + ), + pytest.param( + """ + [łoł + for łol in os.listdir(".") #@ + ] + """, + "Variable", + id="inline_for_loop_variable", + ), + ], + ) + def test_assignname( + self, + code: str, + assign_type: str, + ): + """Variables defined no matter where, should be checked for non ascii""" + assign_node = astroid.extract_node(code) + + if not isinstance(assign_node, nodes.AssignName): + # For some elements we can't directly extract the assign + # node, so we have to manually look in the children for it + for child in assign_node.get_children(): + if isinstance(child, nodes.AssignName): + assign_node = child + break + + # Just to make sure we found the correct node + assert isinstance(assign_node, nodes.AssignName) + + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id="non-ascii-name", + node=assign_node, + args=(assign_type, "łol"), + confidence=pylint.interfaces.HIGH, + ), + ignore_position=True, + ): + self.checker.visit_assignname(assign_node) + + @pytest.mark.parametrize( + "import_statement, wrong_name", + [ + pytest.param("import fürimma", "fürimma", id="bad_single_main_module"), + pytest.param( + "import fürimma as okay", + None, + id="bad_single_main_module_with_okay_alias", + ), + pytest.param( + "import fürimma, pathlib", + "fürimma", + id="bad_single_main_module_with_stdlib_import", + ), + pytest.param( + "import pathlib, os, foobar, fürimma", + "fürimma", + id="stdlib_with_bad_single_main_module", + ), + pytest.param( + "import pathlib, os, foobar, sys as systëm", + "systëm", + id="stdlib_with_bad_alias", + ), + pytest.param( + "import fürimma as okay, pathlib", + None, + id="bad_single_main_module_with_okay_alias_with_stdlib_import", + ), + pytest.param( + "import fürimma.submodule", "fürimma.submodule", id="bad_main_module" + ), + pytest.param( + "import fürimma.submodule as submodule", + None, + id="bad_main_module_with_okay_alias", + ), + pytest.param( + "import main_module.fürimma", "main_module.fürimma", id="bad_submodule" + ), + pytest.param( + "import main_module.fürimma as okay", + None, + id="bad_submodule_with_okay_alias", + ), + pytest.param( + "import main_module.fürimma as not_økay", + "not_økay", + id="bad_submodule_with_bad_alias", + ), + pytest.param( + "from foo.bar import function", None, id="from_okay_module_import_okay" + ), + pytest.param( + "from foo.bär import function", None, id="from_bad_module_import_okay" + ), + pytest.param( + "from foo.bar import functiøn", + "functiøn", + id="from_okay_module_import_bad", + ), + pytest.param( + "from foo.bar import functiøn as function", + None, + id="from_okay_module_import_bad_as_good", + ), + pytest.param( + "from foo.bär import functiøn as function", + None, + id="from_bad_module_import_bad_as_good", + ), + pytest.param( + "from foo.bar import functiøn as føl", + "føl", + id="from_okay_module_import_bad_as_bad", + ), + pytest.param( + "from foo.bar import functiøn as good, bäd", + "bäd", + id="from_okay_module_import_bad_as_good_and_bad", + ), + pytest.param( + "from foo.bar import functiøn as good, bäd", + "bäd", + id="from_okay_module_import_bad_as_good_and_bad", + ), + pytest.param( + "from foo.bar import functiøn as good, *", + # We still have functiøn within our namespace and could detect this + # But to do this properly we would need to check all `*` imports + # -> Too much effort! + "functiøn", + id="from_okay_module_import_bad_as_good_and_star", + marks=pytest.mark.xfail( + reason="We don't know what is imported when using star" + ), + ), + ], + ) + def test_check_import(self, import_statement: str, wrong_name: Optional[str]): + """We expect that for everything that user can change there is a message""" + node = astroid.extract_node(f"{import_statement} #@") + + expected_msgs: Iterable[pylint.testutils.MessageTest] = tuple() + + if wrong_name: + expected_msgs = ( + pylint.testutils.MessageTest( + msg_id="non-ascii-module-import", + node=node, + args=("Module", wrong_name), + confidence=pylint.interfaces.HIGH, + ), + ) + with self.assertAddsMessages(*expected_msgs, ignore_position=True): + if import_statement.startswith("from"): + assert isinstance(node, nodes.ImportFrom) + self.checker.visit_importfrom(node) + else: + assert isinstance(node, nodes.Import) + self.checker.visit_import(node) diff --git a/tests/functional/n/non/non_ascii_name.rc b/tests/functional/n/non/non_ascii_name.rc deleted file mode 100644 index ff09ea1f60..0000000000 --- a/tests/functional/n/non/non_ascii_name.rc +++ /dev/null @@ -1,3 +0,0 @@ -[testoptions] -# This test cannot run on Windows due to Unicode error formatting. -exclude_platforms=win32 diff --git a/tests/functional/n/non/non_ascii_name.txt b/tests/functional/n/non/non_ascii_name.txt index ba5338bb96..2cda6962ef 100644 --- a/tests/functional/n/non/non_ascii_name.txt +++ b/tests/functional/n/non/non_ascii_name.txt @@ -1,2 +1,2 @@ -non-ascii-name:3:0:3:10::"Constant name ""áéíóú"" contains a non-ASCII unicode character":HIGH -non-ascii-name:5:0:6:12:úóíéá:"Function name ""úóíéá"" contains a non-ASCII unicode character":HIGH +non-ascii-name:3:0:3:10::"Variable name ""áéíóú"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:5:0:6:12:úóíéá:"Function name ""úóíéá"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non/non_ascii_name_backward_test_code.py b/tests/functional/n/non/non_ascii_name_backward_test_code.py new file mode 100644 index 0000000000..36a22ca521 --- /dev/null +++ b/tests/functional/n/non/non_ascii_name_backward_test_code.py @@ -0,0 +1,7 @@ +""" Tests for non-ascii-name checker working as expected after a major refactor """ +# pylint: disable=C0144, use-symbolic-message-instead + +áéíóú = 4444 + +def úóíéá(): + """yo""" diff --git a/tests/functional/n/non/non_ascii_name_backward_test_msg.py b/tests/functional/n/non/non_ascii_name_backward_test_msg.py new file mode 100644 index 0000000000..e63f3a4db6 --- /dev/null +++ b/tests/functional/n/non/non_ascii_name_backward_test_msg.py @@ -0,0 +1,7 @@ +""" Tests for non-ascii-name checker working as expected after a major refactor """ +# pylint: disable=non-ascii-name + +áéíóú = 4444 + +def úóíéá(): + """yo""" diff --git a/tests/functional/n/non_ascii_import/__init__.py b/tests/functional/n/non_ascii_import/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/n/non_ascii_import/non_ascii_import.py b/tests/functional/n/non_ascii_import/non_ascii_import.py new file mode 100644 index 0000000000..76163b2862 --- /dev/null +++ b/tests/functional/n/non_ascii_import/non_ascii_import.py @@ -0,0 +1,13 @@ +"""Test that invalid module name imports causes correct error""" +# pylint: disable=import-error, wrong-import-position, unused-wildcard-import, wildcard-import, wrong-import-order +import sys +import os + +# allow module imports to test that this is indeed a valid python file +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.dirname(SCRIPT_DIR)) + +import non_ascii_name_loł as ok +from pathlib import * # test that star imports work correctly and give no error + +print(ok) diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py new file mode 100644 index 0000000000..df392961f4 --- /dev/null +++ b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py @@ -0,0 +1,6 @@ +"""import non ascii alias""" +import os.path as łos # [non-ascii-module-import] + + +# Usage should not raise a second error +foo = łos.join("a", "b") diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt new file mode 100644 index 0000000000..595d387f71 --- /dev/null +++ b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt @@ -0,0 +1 @@ +non-ascii-module-import:2:0:2:22::"Module name ""łos"" contains a non-ASCII character, use an ASCII-only alias for import.":HIGH diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py b/tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py new file mode 100644 index 0000000000..35f8513fa9 --- /dev/null +++ b/tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py @@ -0,0 +1,12 @@ +"""Test that invalid module name do not cause errors when using an alias""" +# pylint: disable=import-error, wrong-import-position +import sys +import os + +# allow module imports to test that this is indeed a valid python file +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.dirname(SCRIPT_DIR)) + +import non_ascii_name_loł as foobar + +print(foobar) diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_from_as.py b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.py new file mode 100644 index 0000000000..68beba0ec6 --- /dev/null +++ b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.py @@ -0,0 +1,6 @@ +"""import as non ascii alias""" +from os.path import join as łos # [non-ascii-module-import] + + +# Usage should not raise a second error +foo = łos("a", "b") diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt new file mode 100644 index 0000000000..58daa481ba --- /dev/null +++ b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt @@ -0,0 +1 @@ +non-ascii-module-import:2:0:2:32::"Module name ""łos"" contains a non-ASCII character, use an ASCII-only alias for import.":HIGH diff --git "a/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" "b/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" new file mode 100644 index 0000000000..bc59418e1e --- /dev/null +++ "b/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" @@ -0,0 +1 @@ +# [non-ascii-file-name] diff --git "a/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" "b/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" new file mode 100644 index 0000000000..eefaa197a9 --- /dev/null +++ "b/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" @@ -0,0 +1 @@ +non-ascii-file-name:1:0:None:None::"File name ""non_ascii_name_loł"" contains a non-ASCII character. PEP 3131 only allows non-ascii identifiers, not file names.":HIGH diff --git a/tests/functional/n/non_ascii_name/__init__.py b/tests/functional/n/non_ascii_name/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py new file mode 100644 index 0000000000..ac51b9c4dc --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py @@ -0,0 +1,4 @@ +"""Assigment Expression as defined in https://www.python.org/dev/peps/pep-0572/""" + +if (loł := __name__) == "__main__": # [non-ascii-name] + print(loł) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt new file mode 100644 index 0000000000..8a5acceb86 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt @@ -0,0 +1 @@ +non-ascii-name:3:4:3:8::"Variable name ""loł"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.py b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.py new file mode 100644 index 0000000000..06900ac7ca --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.py @@ -0,0 +1,19 @@ +""" +Ensure that also decorator calls caught correctly +Column and line not correctly detected with Python 3.8, so we +skip it and only test versions after that. +""" + + +def decoractor_func(*args, **kwargs): + """A docstring""" + return lambda x: f"Foobar {args} {kwargs}" + + +@decoractor_func( + aaaaaaaaaaaaaaaaaaalllllllooooooooooooonnngggggggggglllline=1, + normal=2, + fåling=3, # [non-ascii-name] +) +def a_function(): + """A docstring""" diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc new file mode 100644 index 0000000000..16b75eea75 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt new file mode 100644 index 0000000000..6c4944009d --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt @@ -0,0 +1 @@ +non-ascii-name:16:4:16:13:a_function:"Argument name ""fåling"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py b/tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py new file mode 100644 index 0000000000..5d7b4c7f37 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py @@ -0,0 +1,13 @@ +""" +We don't expect this to give any errors! +""" + + +def okay(**kwargs): + """print kwargs""" + print(kwargs) + + +keyword_args = {"łol": "this would be hard to check against"} + +okay(**keyword_args) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py new file mode 100644 index 0000000000..59585645a7 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py @@ -0,0 +1,14 @@ +"""invalid ascii char in a for loop""" + +import os + + +def main(): + """main func""" + # +2: [non-ascii-name] + a_variable = "" + for łol in os.listdir("."): + # Usage should not raise a second error + a_variable += ( + f"{łol} " + ) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt new file mode 100644 index 0000000000..8995dc0eb3 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt @@ -0,0 +1 @@ +non-ascii-name:10:8:10:12:main:"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function.py b/tests/functional/n/non_ascii_name/non_ascii_name_function.py new file mode 100644 index 0000000000..e3c558ea65 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function.py @@ -0,0 +1,19 @@ +"""invalid ASCII char in a function definition""" +# pylint: disable=invalid-name + + +def sayHello(): + """Greetings""" + print("Hello, World!") + + +# +3: [non-ascii-name] + + +def sayНello(): + """From Russia with Love""" + print("Goodbye, World!") + + +# Usage should not raise a second error +sayНello() diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function.txt new file mode 100644 index 0000000000..949059da9e --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function.txt @@ -0,0 +1 @@ +non-ascii-name:13:0:15:28:sayНello:"Function name ""sayНello"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py new file mode 100644 index 0000000000..a75e1c6b78 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py @@ -0,0 +1,24 @@ +""" +non ascii variable defined in a function + +This test is only for 3.8 as the starting column is incorrect +""" + + +def okay( + just_some_thing_long_again: str, + lol_very_long_argument: str, + łol: str, # [non-ascii-name] +) -> bool: + """Be okay, yeah?""" + # Usage should not raise a second error + print(just_some_thing_long_again, lol_very_long_argument, łol) + return True + + +# Usage should raise a second error +okay( + "A VVVVVVVEEEERRRRRRRRRRYYYYYYYYYY LONG TIME ", + lol_very_long_argument="a", + łol="b", # [non-ascii-name] +) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc new file mode 100644 index 0000000000..35b185fdbc --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.8 +max_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt new file mode 100644 index 0000000000..7813364747 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt @@ -0,0 +1,2 @@ +non-ascii-name:11:4:11:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:23:0:None:None::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py new file mode 100644 index 0000000000..a2d87ac4d7 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py @@ -0,0 +1,25 @@ +""" +non ascii variable defined in a function + +This test is 3.9+ and not using 'min_pyver_end_position' +as the starting column is also incorrect on < 3.9 +""" + + +def okay( + just_some_thing_long_again: str, + lol_very_long_argument: str, + łol: str, # [non-ascii-name] +) -> bool: + """Be okay, yeah?""" + # Usage should not raise a second error + print(just_some_thing_long_again, lol_very_long_argument, łol) + return True + + +# Usage should raise a second error +okay( + "A VVVVVVVEEEERRRRRRRRRRYYYYYYYYYY LONG TIME ", + lol_very_long_argument="a", + łol="b", # [non-ascii-name] +) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc new file mode 100644 index 0000000000..16b75eea75 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt new file mode 100644 index 0000000000..0222308af6 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt @@ -0,0 +1,2 @@ +non-ascii-name:12:4:12:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:24:4:24:12::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py new file mode 100644 index 0000000000..2d47e58c2d --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py @@ -0,0 +1,8 @@ +"""inline loop non ascii variable definition""" +import os + + +foo = [ + f"{łol} " + for łol in os.listdir(".") # [non-ascii-name] +] diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt new file mode 100644 index 0000000000..64515296b6 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt @@ -0,0 +1 @@ +non-ascii-name:7:8:7:12::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py new file mode 100644 index 0000000000..3c7b8c6835 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py @@ -0,0 +1,17 @@ +""" +Defining non ASCII variables in a function call + +This test is only for 3.8 as the starting column is incorrect +""" + + +def okay(**kwargs): + """Print kwargs""" + print(kwargs) + + +okay( + a_long_attribute_that_is_very_okay=1, + b_belongs_to_yet_another_okay_attributed=2, + łol=3, # [non-ascii-name] +) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc new file mode 100644 index 0000000000..35b185fdbc --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.8 +max_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt new file mode 100644 index 0000000000..b43189658f --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt @@ -0,0 +1 @@ +non-ascii-name:16:0:None:None::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py new file mode 100644 index 0000000000..0dfceb38f4 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py @@ -0,0 +1,18 @@ +""" +Defining non ASCII variables in a function call + +This test is 3.9+ and not using 'min_pyver_end_position' +as the starting column is also incorrect on < 3.9 +""" + + +def okay(**kwargs): + """Print kwargs""" + print(kwargs) + + +okay( + a_long_attribute_that_is_very_okay=1, + b_belongs_to_yet_another_okay_attributed=2, + łol=3, # [non-ascii-name] +) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc new file mode 100644 index 0000000000..16b75eea75 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt new file mode 100644 index 0000000000..7595a9d082 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt @@ -0,0 +1 @@ +non-ascii-name:17:4:17:10::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_local.py b/tests/functional/n/non_ascii_name/non_ascii_name_local.py new file mode 100644 index 0000000000..99423fdc8f --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_local.py @@ -0,0 +1,9 @@ +"""Using non ascii variables in local""" + + +def okay(): + """docstring""" + łol = "foo" # [non-ascii-name] + # Usage should not raise a second error + baring = łol + print(baring) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_local.txt b/tests/functional/n/non_ascii_name/non_ascii_name_local.txt new file mode 100644 index 0000000000..8e5e684fa9 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_local.txt @@ -0,0 +1 @@ +non-ascii-name:6:4:6:8:okay:"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py new file mode 100644 index 0000000000..651d78f894 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py @@ -0,0 +1,24 @@ +""" +Test for names within keyword and position only function + +This test is 3.8+ as the columns are not correctly identified +by the ast parser < 3.8 +""" +# pylint: disable=unused-argument + + +def name( + some_thing_long_but_okay, + not_okay_łol, # [non-ascii-name] + not_okay_defaułt=None, # [non-ascii-name] + /, + p_or_kw_okay=None, + p_or_kw_not_økay=None, # [non-ascii-name] + *, + kw_arg_okay, + kw_arg_not_økay, # [non-ascii-name] +): + """ + Do something! + """ + return "Foobar" diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt new file mode 100644 index 0000000000..b13a1af3b2 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt @@ -0,0 +1,4 @@ +non-ascii-name:12:4:12:17:name:"Argument name ""not_okay_łol"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:13:4:13:21:name:"Argument name ""not_okay_defaułt"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:16:4:16:21:name:"Argument name ""p_or_kw_not_økay"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:19:4:19:20:name:"Argument name ""kw_arg_not_økay"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py new file mode 100644 index 0000000000..9b10f1cd46 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py @@ -0,0 +1,17 @@ +"""static method with non ascii characters""" + + +class OkayClass: + """Class Docstring""" + + def public(self): + """Say it load""" + + @staticmethod + def umlaut_ä(): # [non-ascii-name] + """Say ä""" + return "ä" + + +# Usage should not raise a second error +OkayClass.umlaut_ä() diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt new file mode 100644 index 0000000000..8bc3e8ce69 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt @@ -0,0 +1 @@ +non-ascii-name:11:4:13:19:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_try_except.py b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.py new file mode 100644 index 0000000000..beccf4b9a9 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.py @@ -0,0 +1,11 @@ +""" +Also variables defined in except can't contain non ascii chars +""" + + +try: + raise AttributeError("Test") + # +1: [non-ascii-name] +except AttributeError as łol: + # Usage should not raise a second error + foo = łol diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt new file mode 100644 index 0000000000..f6b0d6c6c4 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt @@ -0,0 +1 @@ +non-ascii-name:9:0:11:14::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_variable.py b/tests/functional/n/non_ascii_name/non_ascii_name_variable.py new file mode 100644 index 0000000000..2994dfa475 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_variable.py @@ -0,0 +1,9 @@ +""" +Simply variable test +""" +# pylint: disable=invalid-name + +# Test invalid variable name +łol = "Foobar" # [non-ascii-name] +# Usage should not raise a second error +łol += "-" # [non-ascii-name] diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_variable.txt b/tests/functional/n/non_ascii_name/non_ascii_name_variable.txt new file mode 100644 index 0000000000..5c142ee7e4 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_variable.txt @@ -0,0 +1,2 @@ +non-ascii-name:7:0:7:4::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:9:0:9:4::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/__init__.py b/tests/functional/n/non_ascii_name_class/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py new file mode 100644 index 0000000000..edcdae6435 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py @@ -0,0 +1,23 @@ +""" Non ASCII char in class name + +Note that the end line parameter here seems to be off. +We would expect it to be the same as the start, as we only refer +to the class Name and not the complete class definition. +But this is not possible atm with pylint. +""" +# pylint: disable=too-few-public-methods + + +class НoldIt: # [non-ascii-name] + """nice classs""" + + def public(self): + """do something""" + print(self) + + +def main(): + """Main function""" + # Usage should not raise a second error + foobar = НoldIt() + print(foobar) diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt new file mode 100644 index 0000000000..92c4430b31 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt @@ -0,0 +1 @@ +non-ascii-name:11:0:16:19:НoldIt:"Class name ""НoldIt"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py new file mode 100644 index 0000000000..07d95bba56 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py @@ -0,0 +1,25 @@ +"""Non ASCII name in class variable""" + + +class OkayIsh: + """Class docstring""" + + def public(self): + """Be public!""" + print(self) + + def __init__(self): + self.łoopback = "invalid" # [non-ascii-name] + + def foobar(self): + """do something""" + # Usage should not raise a second error + return self.łoopback + +def main(): + """main function""" + # Usage should not raise a second error + barrrr = OkayIsh() + barrrr.foobar() + test = barrrr.łoopback + print(test) diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt new file mode 100644 index 0000000000..7e3f5327c2 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt @@ -0,0 +1 @@ +non-ascii-name:12:8:12:22:OkayIsh.__init__:"Attribute name ""łoopback"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py new file mode 100644 index 0000000000..c3f96cadd9 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py @@ -0,0 +1,23 @@ +"""non ASCII name in global class variable/class constant""" + + +class OkayIsh: + """Class docstring""" + ŁOOPBACK = "invalid" # [non-ascii-name] + + + def more_public(self): + """yet another public method""" + print(self) + + def public(self): + """something public""" + print(self) + + + +def main(): + """Main func""" + # Usage should not raise a second error + foobar = OkayIsh.ŁOOPBACK + print(foobar) diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt new file mode 100644 index 0000000000..26cf27ef78 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt @@ -0,0 +1 @@ +non-ascii-name:6:4:6:13:OkayIsh:"Attribute name ""ŁOOPBACK"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py new file mode 100644 index 0000000000..ec05b4cf96 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py @@ -0,0 +1,18 @@ +"""Non ASCII char in classmethod""" + + +class OkayClass: + """We need a class docstring?""" + + def public(self): + """Say something""" + print(self) + + @classmethod + def umlaut_ä(cls): # [non-ascii-name] + """do something""" + return "ä" + + +# Usage should not raise a second error +OkayClass.umlaut_ä() diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt new file mode 100644 index 0000000000..8d7606e679 --- /dev/null +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt @@ -0,0 +1 @@ +non-ascii-name:12:4:14:19:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH From 3ad42a05634d6913f57c7d65741bdbdb7b1ffc48 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:05:48 +0100 Subject: [PATCH 137/357] Fixed false positive for ``global-variable-not-assigned`` when the `del` statement is used (#5656) Closes #5333 --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/utils.py | 9 +++++++++ pylint/checkers/variables.py | 1 + tests/functional/g/globals.py | 7 +++++++ tests/functional/g/globals.txt | 9 +++++---- 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 424d8b064f..44f4bfa367 100644 --- a/ChangeLog +++ b/ChangeLog @@ -62,6 +62,10 @@ Release date: TBA Closes #5499 +* Fixed false positive for ``global-variable-not-assigned`` when the ``del`` statement is used + + Closes #5333 + * By default, pylint does no longer take files starting with ``.#`` into account. Those are considered `emacs file locks`. See https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Locks.html. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 854fef618d..ab88087b34 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -195,3 +195,7 @@ Other Changes raise ValueError Closes #4955 + +* Fixed false positive for ``global-variable-not-assigned`` when the ``del`` statement is used + + Closes #5333 diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index f421230482..b0b15e3618 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1672,6 +1672,15 @@ def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: ) +def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool: + """Check if the given variable name is deleted in the same scope after the current node""" + return any( + getattr(target, "name", None) == varname and target.lineno > node.lineno + for del_node in node.scope().nodes_of_class(nodes.Delete) + for target in del_node.targets + ) + + def is_function_body_ellipsis(node: nodes.FunctionDef) -> bool: """Checks whether a function body only consists of a single Ellipsis""" return ( diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index f01866276b..5ce58df140 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1128,6 +1128,7 @@ def visit_global(self, node: nodes.Global) -> None: ) if ( not utils.is_reassigned_after_current(node, name) + and not utils.is_deleted_after_current(node, name) and not_defined_locally_by_import ): self.add_message("global-variable-not-assigned", args=name, node=node) diff --git a/tests/functional/g/globals.py b/tests/functional/g/globals.py index a5e4b4bffc..56c852d8a6 100644 --- a/tests/functional/g/globals.py +++ b/tests/functional/g/globals.py @@ -40,6 +40,13 @@ def global_no_assign(): print(CONSTANT) +def global_del(): + """Deleting the global name prevents `global-variable-not-assigned`""" + global CONSTANT # [global-statement] + print(CONSTANT) + del CONSTANT + + def global_operator_assign(): """Operator assigns should only throw a global statement error""" global CONSTANT # [global-statement] diff --git a/tests/functional/g/globals.txt b/tests/functional/g/globals.txt index 0559eea213..4e3f6dca43 100644 --- a/tests/functional/g/globals.txt +++ b/tests/functional/g/globals.txt @@ -6,7 +6,8 @@ undefined-variable:22:10:22:13:other:Undefined variable 'HOP':UNDEFINED global-variable-undefined:27:4:27:18:define_constant:Global variable 'SOMEVAR' undefined at the module level:UNDEFINED global-statement:33:4:33:14:global_with_import:Using the global statement:UNDEFINED global-variable-not-assigned:39:4:39:19:global_no_assign:Using global for 'CONSTANT' but no assignment is done:UNDEFINED -global-statement:45:4:45:19:global_operator_assign:Using the global statement:UNDEFINED -global-statement:52:4:52:19:global_function_assign:Using the global statement:UNDEFINED -global-statement:62:4:62:15:override_func:Using the global statement:UNDEFINED -global-statement:71:4:71:14:func:Using the global statement:UNDEFINED +global-statement:45:4:45:19:global_del:Using the global statement:UNDEFINED +global-statement:52:4:52:19:global_operator_assign:Using the global statement:UNDEFINED +global-statement:59:4:59:19:global_function_assign:Using the global statement:UNDEFINED +global-statement:69:4:69:15:override_func:Using the global statement:UNDEFINED +global-statement:78:4:78:14:func:Using the global statement:UNDEFINED From f3ece3f3a97ca8905f57f024de3b7d037b6da0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:06:30 +0100 Subject: [PATCH 138/357] Add ``future=True`` to all ``NodeNG.frame`` calls (#5621) Co-authored-by: Pierre Sassoulas --- examples/custom.py | 2 +- pylint/checkers/base.py | 22 +++++++------- pylint/checkers/classes/class_checker.py | 30 +++++++++---------- pylint/checkers/format.py | 2 +- pylint/checkers/imports.py | 2 +- pylint/checkers/newstyle.py | 2 +- pylint/checkers/refactoring/not_checker.py | 2 +- .../refactoring/refactoring_checker.py | 16 +++++----- pylint/checkers/utils.py | 8 ++--- pylint/checkers/variables.py | 25 ++++++++-------- pylint/extensions/_check_docs_utils.py | 2 +- pylint/extensions/bad_builtin.py | 2 +- pylint/extensions/docparams.py | 6 ++-- pylint/pyreverse/inspector.py | 6 ++-- pylint/utils/utils.py | 4 +-- 15 files changed, 66 insertions(+), 65 deletions(-) diff --git a/examples/custom.py b/examples/custom.py index d4376fc4e3..d7757dc788 100644 --- a/examples/custom.py +++ b/examples/custom.py @@ -61,7 +61,7 @@ def visit_call(self, node: nodes.Call) -> None: and node.func.attrname == "create" ): return - in_class = node.frame() + in_class = node.frame(future=True) for param in node.args: in_class.locals[param.name] = node diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 2f79f852e0..febc7026be 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -719,7 +719,7 @@ def same_scope(current): @utils.check_messages("return-outside-function") def visit_return(self, node: nodes.Return) -> None: - if not isinstance(node.frame(), nodes.FunctionDef): + if not isinstance(node.frame(future=True), nodes.FunctionDef): self.add_message("return-outside-function", node=node) @utils.check_messages("yield-outside-function") @@ -827,7 +827,7 @@ def _check_inferred_class_is_abstract(self, inferred, node): ) def _check_yield_outside_func(self, node): - if not isinstance(node.frame(), (nodes.FunctionDef, nodes.Lambda)): + if not isinstance(node.frame(future=True), (nodes.FunctionDef, nodes.Lambda)): self.add_message("yield-outside-function", node=node) def _check_else_on_loop(self, node): @@ -862,7 +862,7 @@ def _check_in_loop(self, node, node_name): def _check_redefinition(self, redeftype, node): """check for redefinition of a function / method / class name""" - parent_frame = node.parent.frame() + parent_frame = node.parent.frame(future=True) # Ignore function stubs created for type information redefinitions = [ @@ -1426,7 +1426,7 @@ def visit_call(self, node: nodes.Call) -> None: name = node.func.name # ignore the name if it's not a builtin (i.e. not defined in the # locals nor globals scope) - if not (name in node.frame() or name in node.root()): + if not (name in node.frame(future=True) or name in node.root()): if name == "exec": self.add_message("exec-used", node=node) elif name == "reversed": @@ -1913,11 +1913,11 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._check_assign_to_new_keyword_violation(node.name, node) confidence = interfaces.HIGH if node.is_method(): - if utils.overrides_a_method(node.parent.frame(), node.name): + if utils.overrides_a_method(node.parent.frame(future=True), node.name): return confidence = ( interfaces.INFERENCE - if utils.has_known_bases(node.parent.frame()) + if utils.has_known_bases(node.parent.frame(future=True)) else interfaces.INFERENCE_FAILURE ) @@ -1943,7 +1943,7 @@ def visit_global(self, node: nodes.Global) -> None: def visit_assignname(self, node: nodes.AssignName) -> None: """check module level assigned names""" self._check_assign_to_new_keyword_violation(node.name, node) - frame = node.frame() + frame = node.frame(future=True) assign_type = node.assign_type() if isinstance(assign_type, nodes.Comprehension): self._check_name("inlinevar", node.name, node) @@ -2156,15 +2156,15 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: ): return - if isinstance(node.parent.frame(), nodes.ClassDef): + if isinstance(node.parent.frame(future=True), nodes.ClassDef): overridden = False confidence = ( interfaces.INFERENCE - if utils.has_known_bases(node.parent.frame()) + if utils.has_known_bases(node.parent.frame(future=True)) else interfaces.INFERENCE_FAILURE ) # check if node is from a method overridden by its ancestor - for ancestor in node.parent.frame().ancestors(): + for ancestor in node.parent.frame(future=True).ancestors(): if ancestor.qname() == "builtins.object": continue if node.name in ancestor and isinstance( @@ -2175,7 +2175,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._check_docstring( ftype, node, report_missing=not overridden, confidence=confidence # type: ignore[arg-type] ) - elif isinstance(node.parent.frame(), nodes.Module): + elif isinstance(node.parent.frame(future=True), nodes.Module): self._check_docstring(ftype, node) # type: ignore[arg-type] else: return diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 452d57c1b6..aaa5de0cb2 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1033,7 +1033,7 @@ def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: # Check if any method attr is defined in is a defining method # or if we have the attribute defined in a setter. - frames = (node.frame() for node in nodes_lst) + frames = (node.frame(future=True) for node in nodes_lst) if any( frame.name in defining_methods or is_property_setter(frame) for frame in frames @@ -1045,7 +1045,7 @@ def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: attr_defined = False # check if any parent method attr is defined in is a defining method for node in parent.instance_attrs[attr]: - if node.frame().name in defining_methods: + if node.frame(future=True).name in defining_methods: attr_defined = True if attr_defined: # we're done :) @@ -1056,12 +1056,12 @@ def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: cnode.local_attr(attr) except astroid.NotFoundError: for node in nodes_lst: - if node.frame().name not in defining_methods: + if node.frame(future=True).name not in defining_methods: # If the attribute was set by a call in any # of the defining methods, then don't emit # the warning. if _called_in_methods( - node.frame(), cnode, defining_methods + node.frame(future=True), cnode, defining_methods ): continue self.add_message( @@ -1077,7 +1077,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._check_useless_super_delegation(node) self._check_property_with_parameters(node) - klass = node.parent.frame() + klass = node.parent.frame(future=True) self._meth_could_be_func = True # check first argument is self if this is actually a method self._check_first_arg_for_type(node, klass.type == "metaclass") @@ -1138,12 +1138,12 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: # check if the method is hidden by an attribute try: overridden = klass.instance_attr(node.name)[0] - overridden_frame = overridden.frame() + overridden_frame = overridden.frame(future=True) if ( isinstance(overridden_frame, nodes.FunctionDef) and overridden_frame.type == "method" ): - overridden_frame = overridden_frame.parent.frame() + overridden_frame = overridden_frame.parent.frame(future=True) if not ( isinstance(overridden_frame, nodes.ClassDef) and klass.is_subtype_of(overridden_frame.qname()) @@ -1226,7 +1226,7 @@ def _check_useless_super_delegation(self, function): return # Check values of default args - klass = function.parent.frame() + klass = function.parent.frame(future=True) meth_node = None for overridden in klass.local_attr_ancestors(function.name): # get astroid for the searched method @@ -1396,7 +1396,7 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None: self._first_attrs.pop() if not self.linter.is_message_enabled("no-self-use"): return - class_node = node.parent.frame() + class_node = node.parent.frame(future=True) if ( self._meth_could_be_func and node.type == "method" @@ -1641,7 +1641,7 @@ def _check_protected_attribute_access(self, node: nodes.Attribute): return if ( - self._is_classmethod(node.frame()) + self._is_classmethod(node.frame(future=True)) and self._is_inferred_instance(node.expr, klass) and self._is_class_attribute(attrname, klass) ): @@ -1661,7 +1661,7 @@ def _check_protected_attribute_access(self, node: nodes.Attribute): def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: """Returns true if the node is located inside a special (aka dunder) method""" try: - frame_name = node.frame().name + frame_name = node.frame(future=True).name except AttributeError: return False return frame_name and frame_name in PYMETHODS @@ -1766,11 +1766,11 @@ def _check_accessed_members(self, node, accessed): defstmt = defstmts[0] # check that if the node is accessed in the same method as # it's defined, it's accessed after the initial assignment - frame = defstmt.frame() + frame = defstmt.frame(future=True) lno = defstmt.fromlineno for _node in nodes_lst: if ( - _node.frame() is frame + _node.frame(future=True) is frame and _node.fromlineno < lno and not astroid.are_exclusive( _node.statement(future=True), defstmt, excs @@ -1875,7 +1875,7 @@ def is_abstract(method): key=lambda item: item[0], ) for name, method in methods: - owner = method.parent.frame() + owner = method.parent.frame(future=True) if owner is node: continue # owner is not this class, it must be a parent class @@ -1893,7 +1893,7 @@ def _check_init(self, node): "super-init-not-called" ) and not self.linter.is_message_enabled("non-parent-init-called"): return - klass_node = node.parent.frame() + klass_node = node.parent.frame(future=True) to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) for stmt in node.nodes_of_class(nodes.Call): diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index f38944ded0..4f5f01e55b 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -661,7 +661,7 @@ def _check_multi_statement_line(self, node, line): and isinstance(node.value, nodes.Const) and node.value.value is Ellipsis ): - frame = node.frame() + frame = node.frame(future=True) if is_overload_stub(frame) or is_protocol_class(node_frame_class(frame)): return diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 528f7d80ca..b0daf4bd6e 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -910,7 +910,7 @@ def _check_reimport(self, node, basename=None, level=None): if not self.linter.is_message_enabled("reimported"): return - frame = node.frame() + frame = node.frame(future=True) root = node.root() contexts = [(frame, level)] if root is not frame: diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index 45b4409d87..1435509099 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -66,7 +66,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: # ignore actual functions or method within a new style class if not node.is_method(): return - klass = node.parent.frame() + klass = node.parent.frame(future=True) for stmt in node.nodes_of_class(nodes.Call): if node_frame_class(stmt) != node_frame_class(node): # Don't look down in other scopes. diff --git a/pylint/checkers/refactoring/not_checker.py b/pylint/checkers/refactoring/not_checker.py index f8c670e96f..ec29dfb8e0 100644 --- a/pylint/checkers/refactoring/not_checker.py +++ b/pylint/checkers/refactoring/not_checker.py @@ -62,7 +62,7 @@ def visit_unaryop(self, node: nodes.UnaryOp) -> None: if operator not in self.reverse_op: return # Ignore __ne__ as function of __eq__ - frame = node.frame() + frame = node.frame(future=True) if frame.name == "__ne__" and operator == "==": return for _type in (utils.node_type(left), utils.node_type(right)): diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index a182574561..2387595d39 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -97,7 +97,7 @@ def get_curline_index_start(): def _is_inside_context_manager(node: nodes.Call) -> bool: - frame = node.frame() + frame = node.frame(future=True) if not isinstance( frame, (nodes.FunctionDef, astroid.BoundMethod, astroid.UnboundMethod) ): @@ -108,7 +108,7 @@ def _is_inside_context_manager(node: nodes.Call) -> bool: def _is_a_return_statement(node: nodes.Call) -> bool: - frame = node.frame() + frame = node.frame(future=True) for parent in node.node_ancestors(): if parent is frame: break @@ -121,7 +121,7 @@ def _is_part_of_with_items(node: nodes.Call) -> bool: """Checks if one of the node's parents is a ``nodes.With`` node and that the node itself is located somewhere under its ``items``. """ - frame = node.frame() + frame = node.frame(future=True) current = node while current != frame: if isinstance(current, nodes.With): @@ -910,7 +910,7 @@ def visit_raise(self, node: nodes.Raise) -> None: def _check_stop_iteration_inside_generator(self, node): """Check if an exception of type StopIteration is raised inside a generator""" - frame = node.frame() + frame = node.frame(future=True) if not isinstance(frame, nodes.FunctionDef) or not frame.is_generator(): return if utils.node_ignores_exception(node, StopIteration): @@ -1069,7 +1069,7 @@ def _looks_like_infinite_iterator(param): inferred = utils.safe_infer(node.func) if getattr(inferred, "name", "") == "next": - frame = node.frame() + frame = node.frame(future=True) # The next builtin can only have up to two # positional arguments and no keyword arguments has_sentinel_value = len(node.args) > 1 @@ -1451,7 +1451,9 @@ def _append_context_managers_to_stack(self, node: nodes.Assign) -> None: or not isinstance(assignee, (nodes.AssignName, nodes.AssignAttr)) ): continue - stack = self._consider_using_with_stack.get_stack_for_frame(node.frame()) + stack = self._consider_using_with_stack.get_stack_for_frame( + node.frame(future=True) + ) varname = ( assignee.name if isinstance(assignee, nodes.AssignName) @@ -1479,7 +1481,7 @@ def _check_consider_using_with(self, node: nodes.Call): if ( node in self._consider_using_with_stack.get_stack_for_frame( - node.frame() + node.frame(future=True) ).values() ): # the result of this call was already assigned to a variable and will be checked when leaving the scope. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index b0b15e3618..65c7e3102f 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -668,7 +668,7 @@ def node_frame_class(node: nodes.NodeNG) -> Optional[nodes.ClassDef]: The function returns a class for a method node (or a staticmethod or a classmethod), otherwise it returns `None`. """ - klass = node.frame() + klass = node.frame(future=True) nodes_to_check = ( nodes.NodeNG, astroid.UnboundMethod, @@ -682,14 +682,14 @@ def node_frame_class(node: nodes.NodeNG) -> Optional[nodes.ClassDef]: if klass.parent is None: return None - klass = klass.parent.frame() + klass = klass.parent.frame(future=True) return klass def get_outer_class(class_node: astroid.ClassDef) -> Optional[astroid.ClassDef]: """Return the class that is the outer class of given (nested) class_node""" - parent_klass = class_node.parent.frame() + parent_klass = class_node.parent.frame(future=True) return parent_klass if isinstance(parent_klass, astroid.ClassDef) else None @@ -1086,7 +1086,7 @@ def class_is_abstract(node: nodes.ClassDef) -> bool: return True for method in node.methods(): - if method.parent.frame() is node: + if method.parent.frame(future=True) is node: if method.is_abstract(pass_is_abstract=False): return True return False diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 5ce58df140..2a94d3ce0f 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1108,7 +1108,7 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None: ) def visit_global(self, node: nodes.Global) -> None: """check names imported exists in the global scope""" - frame = node.frame() + frame = node.frame(future=True) if isinstance(frame, nodes.Module): self.add_message("global-at-module-level", node=node) return @@ -1142,7 +1142,7 @@ def visit_global(self, node: nodes.Global) -> None: ): self.add_message("redefined-builtin", args=name, node=node) break - if anode.frame() is module: + if anode.frame(future=True) is module: # module level assignment break if isinstance(anode, nodes.FunctionDef) and anode.parent is module: @@ -1344,7 +1344,7 @@ def _check_consumer( defnode = utils.assign_parent(found_nodes[0]) defstmt = defnode.statement(future=True) - defframe = defstmt.frame() + defframe = defstmt.frame(future=True) # The class reuses itself in the class scope. is_recursive_klass = ( @@ -1669,7 +1669,7 @@ def _is_variable_violation( # equivalent to frame.statement().scope() forbid_lookup = ( isinstance(frame, nodes.FunctionDef) - or isinstance(node.frame(), nodes.Lambda) + or isinstance(node.frame(future=True), nodes.Lambda) ) and _assigned_locally(node) if not forbid_lookup and defframe.root().lookup(node.name)[1]: maybe_before_assign = False @@ -1836,8 +1836,8 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool if not isinstance(defstmt, nodes.AnnAssign) or defstmt.value: return False - defstmt_frame = defstmt.frame() - node_frame = node.frame() + defstmt_frame = defstmt.frame(future=True) + node_frame = node.frame(future=True) parent = node while parent is not defstmt_frame.parent: @@ -1876,10 +1876,9 @@ def _is_first_level_self_reference( 1 = Break 2 = Break + emit message """ - if ( - node.frame().parent == defstmt - and node.statement(future=True) == node.frame() - ): + if node.frame(future=True).parent == defstmt and node.statement( + future=True + ) == node.frame(future=True): # Check if used as type annotation # Break but don't emit message if postponed evaluation is enabled if utils.is_node_in_type_annotation_context(node): @@ -2129,7 +2128,7 @@ def _is_name_ignored(self, stmt, name): def _check_unused_arguments(self, name, node, stmt, argnames): is_method = node.is_method() - klass = node.parent.frame() + klass = node.parent.frame(future=True) if is_method and isinstance(klass, nodes.ClassDef): confidence = ( INFERENCE if utils.has_known_bases(klass) else INFERENCE_FAILURE @@ -2180,12 +2179,12 @@ def _check_late_binding_closure(self, node: nodes.Name) -> None: if not self.linter.is_message_enabled("cell-var-from-loop"): return - node_scope = node.frame() + node_scope = node.frame(future=True) # If node appears in a default argument expression, # look at the next enclosing frame instead if utils.is_default_argument(node, node_scope): - node_scope = node_scope.parent.frame() + node_scope = node_scope.parent.frame(future=True) # Check if node is a cell var if ( diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 2ff86b00b8..f6fa347240 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -155,7 +155,7 @@ def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: exceptions = [target] elif isinstance(target, nodes.FunctionDef): for ret in target.nodes_of_class(nodes.Return): - if ret.frame() != target: + if ret.frame(future=True) != target: # return from inner function - ignore it continue diff --git a/pylint/extensions/bad_builtin.py b/pylint/extensions/bad_builtin.py index 197bf231bc..bf80722442 100644 --- a/pylint/extensions/bad_builtin.py +++ b/pylint/extensions/bad_builtin.py @@ -62,7 +62,7 @@ def visit_call(self, node: nodes.Call) -> None: name = node.func.name # ignore the name if it's not a builtin (i.e. not defined in the # locals nor globals scope) - if not (name in node.frame() or name in node.root()): + if not (name in node.frame(future=True) or name in node.root()): if name in self.config.bad_functions: hint = BUILTIN_HINTS.get(name) args = f"{name!r}. {hint}" if hint else repr(name) diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 8d9472fbfa..7c18e6b623 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -289,7 +289,7 @@ def check_functiondef_yields(self, node, node_doc): self.add_message("redundant-yields-doc", node=node) def visit_raise(self, node: nodes.Raise) -> None: - func_node = node.frame() + func_node = node.frame(future=True) if not isinstance(func_node, astroid.FunctionDef): return @@ -336,7 +336,7 @@ def visit_return(self, node: nodes.Return) -> None: if self.config.accept_no_return_doc: return - func_node = node.frame() + func_node = node.frame(future=True) if not isinstance(func_node, astroid.FunctionDef): return @@ -357,7 +357,7 @@ def visit_yield(self, node: nodes.Yield) -> None: if self.config.accept_no_yields_doc: return - func_node = node.frame() + func_node = node.frame(future=True) if not isinstance(func_node, astroid.FunctionDef): return diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 2c7162d7c1..d3774389e6 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -49,7 +49,7 @@ def interfaces(node, herited=True, handler_func=_iface_hdlr): implements = astroid.bases.Instance(node).getattr("__implements__")[0] except astroid.exceptions.NotFoundError: return - if not herited and implements.frame() is not node: + if not herited and implements.frame(future=True) is not node: return found = set() missing = False @@ -222,8 +222,8 @@ def visit_assignname(self, node: nodes.AssignName) -> None: if hasattr(node, "_handled"): return node._handled = True - if node.name in node.frame(): - frame = node.frame() + if node.name in node.frame(future=True): + frame = node.frame(future=True) else: # the name has been defined as 'global' in the frame and belongs # there. diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index e84ffa4724..f99690b33f 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -108,7 +108,7 @@ def diff_string(old, new): def get_module_and_frameid(node): """return the module name and the frame id in the module""" - frame = node.frame() + frame = node.frame(future=True) module, obj = "", [] while frame: if isinstance(frame, Module): @@ -116,7 +116,7 @@ def get_module_and_frameid(node): else: obj.append(getattr(frame, "name", "")) try: - frame = frame.parent.frame() + frame = frame.parent.frame(future=True) except AttributeError: break obj.reverse() From b376395657b72a2bb5648bb9eeb453458cf87460 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 10 Jan 2022 17:11:55 -0500 Subject: [PATCH 139/357] Fix false positive for `unused-variable` for a comprehension variable matching a type annotation (#5651) * Close #5326 --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/variables.py | 2 +- .../u/undefined/undefined_variable_py38.py | 21 +++++++++++++++++-- .../u/undefined/undefined_variable_py38.txt | 1 - 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 44f4bfa367..37523bbe33 100644 --- a/ChangeLog +++ b/ChangeLog @@ -99,6 +99,11 @@ Release date: TBA Closes #5461 +* Fix false positive for ``unused-variable`` for a comprehension variable matching + an outer scope type annotation. + + Closes #5326 + * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index ab88087b34..0d38432568 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -131,6 +131,11 @@ Other Changes Closes #5461 +* Fix false positive for ``unused-variable`` for a comprehension variable matching + an outer scope type annotation. + + Closes #5326 + * Require Python ``3.6.2`` to run pylint. Closes #5065 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 2a94d3ce0f..66af3ec940 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1849,7 +1849,7 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool # Local refs are ordered, so we break. # print(var) # var = 1 # <- irrelevant - if defstmt_frame == node_frame and not ref_node.lineno < node.lineno: + if defstmt_frame == node_frame and ref_node.lineno > node.lineno: break # If the parent of the local reference is anything but an AnnAssign diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index 81fa19f527..4ecc251336 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -11,10 +11,10 @@ def typing_and_assignment_expression(): print(var) -def typing_and_self_referncing_assignment_expression(): +def typing_and_self_referencing_assignment_expression(): """The variable gets assigned in an assignment expression that references itself""" var: int - if (var := var ** 2): # [undefined-variable] + if (var := var ** 2): # false negative--walrus operator! print(var) @@ -108,3 +108,20 @@ def no_parameters_in_function_default() -> None: if (x_0 := thing.this_value) < (x_1 := thing.that_value) else x_1, ) + + +# Tests for type annotation reused in comprehension + +def type_annotation_used_after_comprehension(): + """https://github.com/PyCQA/pylint/issues/5326#issuecomment-982635371""" + my_int: int + ints = [my_int + 1 for my_int in range(5)] + + for my_int in ints: + print(my_int) + + +def type_annotation_unused_after_comprehension(): + """https://github.com/PyCQA/pylint/issues/5326""" + my_int: int + _ = [print(kwarg1=my_int, kwarg2=my_int) for my_int in range(10)] diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index 6d7035ee11..da69ce0099 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -1,4 +1,3 @@ -undefined-variable:17:15:17:18:typing_and_self_referncing_assignment_expression:Undefined variable 'var':UNDEFINED undefined-variable:42:6:42:16::Undefined variable 'no_default':UNDEFINED undefined-variable:50:6:50:22::Undefined variable 'again_no_default':UNDEFINED undefined-variable:76:6:76:19::Undefined variable 'else_assign_1':UNDEFINED From 2631583577a3b2228af66214a364e4e284ba55dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:51:55 +0100 Subject: [PATCH 140/357] [pre-commit.ci] pre-commit autoupdate (#5661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.930 → v0.931](https://github.com/pre-commit/mirrors-mypy/compare/v0.930...v0.931) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 964df4fa48..ab2d8a9d1b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: types: [text] # necessary to include ChangeLog file files: ^(ChangeLog|doc/(.*/)*.*\.rst) - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.930 + rev: v0.931 hooks: - id: mypy name: mypy From 642268f636241e733409cb6cdf59f0eb87a03ae1 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 11 Jan 2022 10:50:30 -0500 Subject: [PATCH 141/357] Fix #5569: Fix a crash in `unused-private-member` when `type(self)` used in bound methods (#5662) --- ChangeLog | 5 ++++ doc/whatsnew/2.13.rst | 5 ++++ pylint/checkers/classes/class_checker.py | 25 +++++++++++++------ .../u/unused/unused_private_member.py | 10 ++++++++ .../u/unused/unused_private_member.txt | 1 + 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 37523bbe33..68d3b29cb4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,6 +35,11 @@ Release date: TBA Closes #4798 Closes #5081 +* Fix a crash in ``unused-private-member`` checker when analyzing code using + ``type(self)`` in bound methods. + + Closes #5569 + * Pyreverse - add output in mermaidjs format * ``used-before-assignment`` now considers that assignments in a try block diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 0d38432568..959ded73b8 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -74,6 +74,11 @@ Other Changes Closes #4716 +* Fix a crash in ``unused-private-member`` checker when analyzing code using + ``type(self)`` in bound methods. + + Closes #5569 + * Fix crash in ``unnecessary-dict-index-lookup`` checker if the output of ``items()`` is assigned to a 1-tuple. diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index aaa5de0cb2..f64e7addc5 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -970,6 +970,9 @@ def _check_unused_private_attributes(self, node: nodes.ClassDef) -> None: if attribute.attrname != assign_attr.attrname: continue + if self._is_type_self_call(attribute.expr): + continue + if ( assign_attr.expr.name in { @@ -1666,7 +1669,7 @@ def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: return False return frame_name and frame_name in PYMETHODS - def _is_type_self_call(self, expr): + def _is_type_self_call(self, expr: nodes.NodeNG) -> bool: return ( isinstance(expr, nodes.Call) and isinstance(expr.func, nodes.Name) @@ -2029,16 +2032,24 @@ def _uses_mandatory_method_param(self, node): """ return self._is_mandatory_method_param(node.expr) - def _is_mandatory_method_param(self, node): + def _is_mandatory_method_param(self, node: nodes.NodeNG) -> bool: """Check if nodes.Name corresponds to first attribute variable name Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. """ - return ( - self._first_attrs - and isinstance(node, nodes.Name) - and node.name == self._first_attrs[-1] - ) + if self._first_attrs: + first_attr = self._first_attrs[-1] + else: + # It's possible the function was already unregistered. + closest_func = utils.get_node_first_ancestor_of_type( + node, nodes.FunctionDef + ) + if closest_func is None: + return False + if not closest_func.args.args: + return False + first_attr = closest_func.args.args[0].name + return isinstance(node, nodes.Name) and node.name == first_attr def _ancestors_to_call(klass_node, method="__init__"): diff --git a/tests/functional/u/unused/unused_private_member.py b/tests/functional/u/unused/unused_private_member.py index fc42ea8fb8..1b81d738b8 100644 --- a/tests/functional/u/unused/unused_private_member.py +++ b/tests/functional/u/unused/unused_private_member.py @@ -309,3 +309,13 @@ class Foo: def method(self): print(self.__class__.__ham) + + +class TypeSelfCallInMethod: + """Regression test for issue 5569""" + @classmethod + def b(cls) -> None: + cls.__a = '' # [unused-private-member] + + def a(self): + return type(self).__a diff --git a/tests/functional/u/unused/unused_private_member.txt b/tests/functional/u/unused/unused_private_member.txt index 8d69dd02c3..99f34b7f53 100644 --- a/tests/functional/u/unused/unused_private_member.txt +++ b/tests/functional/u/unused/unused_private_member.txt @@ -17,3 +17,4 @@ unused-private-member:243:12:243:50:FalsePositive4681.__init__:Unused private me unused-private-member:274:4:275:26:FalsePositive4849.__unused_private_method:Unused private member `FalsePositive4849.__unused_private_method()`:UNDEFINED unused-private-member:291:4:294:44:Pony.__init_defaults:Unused private member `Pony.__init_defaults(self)`:UNDEFINED unused-private-member:296:4:298:20:Pony.__get_fur_color:Unused private member `Pony.__get_fur_color(self)`:UNDEFINED +unused-private-member:318:8:318:15:TypeSelfCallInMethod.b:Unused private member `TypeSelfCallInMethod.__a`:UNDEFINED From 7b7cc548cea64f7247de75d1ce849150152f2a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 11 Jan 2022 22:13:44 +0100 Subject: [PATCH 142/357] Make sure to split non-separated csv ``OuputLine's`` (#5665) * Make sure to split non-separated csv ``OuputLine's`` * Add test for too long output lines --- pylint/testutils/output_line.py | 85 +++++++++++++++-------------- tests/testutils/test_output_line.py | 11 ++++ 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index fe80247957..1061854bcc 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -117,49 +117,50 @@ def from_csv( """Create an OutputLine from a comma separated list (the functional tests expected output .txt files). """ + if isinstance(row, str): + row = row.split(",") try: - if isinstance(row, Sequence): - column = cls._get_column(row[2]) - if len(row) == 5: - warnings.warn( - "In pylint 3.0 functional tests expected output should always include the " - "expected confidence level, expected end_line and expected end_column. " - "An OutputLine should thus have a length of 8.", - DeprecationWarning, - ) - return cls( - row[0], - int(row[1]), - column, - None, - None, - row[3], - row[4], - UNDEFINED.name, - ) - if len(row) == 6: - warnings.warn( - "In pylint 3.0 functional tests expected output should always include the " - "expected end_line and expected end_column. An OutputLine should thus have " - "a length of 8.", - DeprecationWarning, - ) - return cls( - row[0], int(row[1]), column, None, None, row[3], row[4], row[5] - ) - if len(row) == 8: - end_line = cls._get_py38_none_value(row[3], check_endline) - end_column = cls._get_py38_none_value(row[4], check_endline) - return cls( - row[0], - int(row[1]), - column, - cls._value_to_optional_int(end_line), - cls._value_to_optional_int(end_column), - row[5], - row[6], - row[7], - ) + column = cls._get_column(row[2]) + if len(row) == 5: + warnings.warn( + "In pylint 3.0 functional tests expected output should always include the " + "expected confidence level, expected end_line and expected end_column. " + "An OutputLine should thus have a length of 8.", + DeprecationWarning, + ) + return cls( + row[0], + int(row[1]), + column, + None, + None, + row[3], + row[4], + UNDEFINED.name, + ) + if len(row) == 6: + warnings.warn( + "In pylint 3.0 functional tests expected output should always include the " + "expected end_line and expected end_column. An OutputLine should thus have " + "a length of 8.", + DeprecationWarning, + ) + return cls( + row[0], int(row[1]), column, None, None, row[3], row[4], row[5] + ) + if len(row) == 8: + end_line = cls._get_py38_none_value(row[3], check_endline) + end_column = cls._get_py38_none_value(row[4], check_endline) + return cls( + row[0], + int(row[1]), + column, + cls._value_to_optional_int(end_line), + cls._value_to_optional_int(end_column), + row[5], + row[6], + row[7], + ) raise IndexError except Exception as e: raise MalformedOutputLineException(row, e) from e diff --git a/tests/testutils/test_output_line.py b/tests/testutils/test_output_line.py index eafb3eb533..48ae107213 100644 --- a/tests/testutils/test_output_line.py +++ b/tests/testutils/test_output_line.py @@ -123,16 +123,27 @@ def test_output_line_to_csv(confidence: Confidence, message: Callable) -> None: def test_output_line_from_csv_error() -> None: """Test that errors are correctly raised for incorrect OutputLine's.""" + # Test a csv-string which does not have a number for line and column with pytest.raises( MalformedOutputLineException, match="msg-symbolic-name:42:27:MyClass.my_function:The message", ): OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'", True) + # Test a tuple which does not have a number for line and column with pytest.raises( MalformedOutputLineException, match="symbol='missing-docstring' ?" ): csv = ("missing-docstring", "line", "column", "obj", "msg") OutputLine.from_csv(csv, True) + # Test a csv-string that is too long + with pytest.raises( + MalformedOutputLineException, + match="msg-symbolic-name:42:27:MyClass.my_function:The message", + ): + OutputLine.from_csv( + "'missing-docstring', 1, 2, 'obj', 'msg', 'func', 'message', 'conf', 'too_long'", + True, + ) @pytest.mark.parametrize( From ca44a245d37a3e079931ba190e07211cb35dcadb Mon Sep 17 00:00:00 2001 From: kasium <15907922+kasium@users.noreply.github.com> Date: Tue, 11 Jan 2022 23:31:14 +0100 Subject: [PATCH 143/357] Disable bad-docstring-quotes for <= 3.7 (#5526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In python 3.7 and below, the ast parser might return wrong line numbers. Closes #3077 Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 5 +++ doc/whatsnew/2.13.rst | 7 ++++ pylint/extensions/docstyle.py | 6 ++-- tests/functional/ext/docstyle/docstyle.txt | 7 ---- .../ext/docstyle/docstyle_first_line_empty.py | 22 ++++++++++++ ...cstyle.rc => docstyle_first_line_empty.rc} | 0 .../docstyle/docstyle_first_line_empty.txt | 3 ++ .../ext/docstyle/docstyle_quotes_py37.py | 35 +++++++++++++++++++ .../ext/docstyle/docstyle_quotes_py37.rc | 5 +++ .../{docstyle.py => docstyle_quotes_py38.py} | 25 +++++-------- .../ext/docstyle/docstyle_quotes_py38.rc | 5 +++ .../ext/docstyle/docstyle_quotes_py38.txt | 4 +++ 12 files changed, 98 insertions(+), 26 deletions(-) delete mode 100644 tests/functional/ext/docstyle/docstyle.txt create mode 100644 tests/functional/ext/docstyle/docstyle_first_line_empty.py rename tests/functional/ext/docstyle/{docstyle.rc => docstyle_first_line_empty.rc} (100%) create mode 100644 tests/functional/ext/docstyle/docstyle_first_line_empty.txt create mode 100644 tests/functional/ext/docstyle/docstyle_quotes_py37.py create mode 100644 tests/functional/ext/docstyle/docstyle_quotes_py37.rc rename tests/functional/ext/docstyle/{docstyle.py => docstyle_quotes_py38.py} (63%) create mode 100644 tests/functional/ext/docstyle/docstyle_quotes_py38.rc create mode 100644 tests/functional/ext/docstyle/docstyle_quotes_py38.txt diff --git a/ChangeLog b/ChangeLog index 68d3b29cb4..93292a4112 100644 --- a/ChangeLog +++ b/ChangeLog @@ -184,6 +184,11 @@ Release date: TBA Closes #5460 +* Disable checker ``bad-docstring-quotes`` for Python <= 3.7, because in these versions the line + numbers for decorated functions and classes are not reliable which interferes with the checker. + + Closes #3077 + * Fixed incorrect classification of Numpy-style docstring as Google-style docstring for docstrings with property setter documentation. Docstring classification is now based on the highest amount of matched sections instead diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 959ded73b8..10e8c5b689 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -38,6 +38,13 @@ Extensions Docstring classification is now based on the highest amount of matched sections instead of the order in which the docstring styles were tried. +* ``DocStringStyleChecker`` + + * Disable checker ``bad-docstring-quotes`` for Python <= 3.7, because in these versions the line + numbers for decorated functions and classes are not reliable which interferes with the checker. + + Closes #3077 + Other Changes ============= diff --git a/pylint/extensions/docstyle.py b/pylint/extensions/docstyle.py index c0fed60fe9..d2f07003b1 100644 --- a/pylint/extensions/docstyle.py +++ b/pylint/extensions/docstyle.py @@ -17,6 +17,7 @@ from pylint import checkers from pylint.checkers.utils import check_messages +from pylint.constants import PY38_PLUS from pylint.interfaces import HIGH, IAstroidChecker if TYPE_CHECKING: @@ -33,7 +34,8 @@ class DocStringStyleChecker(checkers.BaseChecker): "C0198": ( 'Bad docstring quotes in %s, expected """, given %s', "bad-docstring-quotes", - "Used when a docstring does not have triple double quotes.", + "Used when a docstring does not have triple double quotes. " + "This checker only works on Python 3.8+.", ), "C0199": ( "First line empty in %s docstring", @@ -81,7 +83,7 @@ def _check_docstring(self, node_type, node): quotes = "'" else: quotes = False - if quotes: + if quotes and PY38_PLUS: self.add_message( "bad-docstring-quotes", node=node, diff --git a/tests/functional/ext/docstyle/docstyle.txt b/tests/functional/ext/docstyle/docstyle.txt deleted file mode 100644 index 0b45cbf8ee..0000000000 --- a/tests/functional/ext/docstyle/docstyle.txt +++ /dev/null @@ -1,7 +0,0 @@ -docstring-first-line-empty:4:0:7:19:check_messages:First line empty in function docstring:HIGH -docstring-first-line-empty:14:0:41:44:FFFF:First line empty in class docstring:HIGH -bad-docstring-quotes:19:4:22:11:FFFF.method1:"Bad docstring quotes in method, expected """""", given '''":HIGH -docstring-first-line-empty:19:4:22:11:FFFF.method1:First line empty in method docstring:HIGH -bad-docstring-quotes:24:4:25:25:FFFF.method2:"Bad docstring quotes in method, expected """""", given """:HIGH -bad-docstring-quotes:27:4:28:25:FFFF.method3:"Bad docstring quotes in method, expected """""", given '":HIGH -bad-docstring-quotes:30:4:31:30:FFFF.method4:"Bad docstring quotes in method, expected """""", given '":HIGH diff --git a/tests/functional/ext/docstyle/docstyle_first_line_empty.py b/tests/functional/ext/docstyle/docstyle_first_line_empty.py new file mode 100644 index 0000000000..92d7a5b7fe --- /dev/null +++ b/tests/functional/ext/docstyle/docstyle_first_line_empty.py @@ -0,0 +1,22 @@ +"""Checks of Dosctrings 'docstring-first-line-empty'""" +# pylint: disable=too-few-public-methods,bad-docstring-quotes + +def check_messages(*messages): # [docstring-first-line-empty] + """ + docstring""" + return messages + + +def function2(): + """Test Ok""" + + +class FFFF: # [docstring-first-line-empty] + """ + Test Docstring First Line Empty + """ + + def method1(self): # [docstring-first-line-empty] + ''' + Test Triple Single Quotes docstring + ''' diff --git a/tests/functional/ext/docstyle/docstyle.rc b/tests/functional/ext/docstyle/docstyle_first_line_empty.rc similarity index 100% rename from tests/functional/ext/docstyle/docstyle.rc rename to tests/functional/ext/docstyle/docstyle_first_line_empty.rc diff --git a/tests/functional/ext/docstyle/docstyle_first_line_empty.txt b/tests/functional/ext/docstyle/docstyle_first_line_empty.txt new file mode 100644 index 0000000000..3d1e7aa6a6 --- /dev/null +++ b/tests/functional/ext/docstyle/docstyle_first_line_empty.txt @@ -0,0 +1,3 @@ +docstring-first-line-empty:4:0:7:19:check_messages:First line empty in function docstring:HIGH +docstring-first-line-empty:14:0:22:11:FFFF:First line empty in class docstring:HIGH +docstring-first-line-empty:19:4:22:11:FFFF.method1:First line empty in method docstring:HIGH diff --git a/tests/functional/ext/docstyle/docstyle_quotes_py37.py b/tests/functional/ext/docstyle/docstyle_quotes_py37.py new file mode 100644 index 0000000000..fd3fc4c508 --- /dev/null +++ b/tests/functional/ext/docstyle/docstyle_quotes_py37.py @@ -0,0 +1,35 @@ +"""Checks of Dosctrings 'bad-docstring-quotes' + +Check that the checker is not enabled on python <= 3.7 +""" +# pylint: disable=docstring-first-line-empty,missing-class-docstring, undefined-variable + + +class FFFF: + def method1(self): + ''' + Test Triple Single Quotes docstring + ''' + + def method2(self): + "bad docstring 1" + + def method3(self): + 'bad docstring 2' + + def method4(self): + ' """bad docstring 3 ' + + @check_messages("bad-open-mode", "redundant-unittest-assert", "deprecated-module") + def method5(self): + """Test OK 1 with decorators""" + + def method6(self): + r"""Test OK 2 with raw string""" + + def method7(self): + u"""Test OK 3 with unicode string""" + + +def function2(): + """Test Ok""" diff --git a/tests/functional/ext/docstyle/docstyle_quotes_py37.rc b/tests/functional/ext/docstyle/docstyle_quotes_py37.rc new file mode 100644 index 0000000000..03be9f7f10 --- /dev/null +++ b/tests/functional/ext/docstyle/docstyle_quotes_py37.rc @@ -0,0 +1,5 @@ +[MASTER] +load-plugins=pylint.extensions.docstyle, + +[testoptions] +max_pyver=3.7 diff --git a/tests/functional/ext/docstyle/docstyle.py b/tests/functional/ext/docstyle/docstyle_quotes_py38.py similarity index 63% rename from tests/functional/ext/docstyle/docstyle.py rename to tests/functional/ext/docstyle/docstyle_quotes_py38.py index a5b6161b05..cb0f96f96c 100644 --- a/tests/functional/ext/docstyle/docstyle.py +++ b/tests/functional/ext/docstyle/docstyle_quotes_py38.py @@ -1,22 +1,9 @@ -"""Checks of Dosctrings 'docstring-first-line-empty' 'bad-docstring-quotes'""" +"""Checks of Dosctrings 'bad-docstring-quotes'""" +# pylint: disable=docstring-first-line-empty,missing-class-docstring, undefined-variable -def check_messages(*messages): # [docstring-first-line-empty] - """ - docstring""" - return messages - - -def function2(): - """Test Ok""" - - -class FFFF: # [docstring-first-line-empty] - """ - Test Docstring First Line Empty - """ - - def method1(self): # [docstring-first-line-empty, bad-docstring-quotes] +class FFFF: + def method1(self): # [bad-docstring-quotes] ''' Test Triple Single Quotes docstring ''' @@ -39,3 +26,7 @@ def method6(self): def method7(self): u"""Test OK 3 with unicode string""" + + +def function2(): + """Test Ok""" diff --git a/tests/functional/ext/docstyle/docstyle_quotes_py38.rc b/tests/functional/ext/docstyle/docstyle_quotes_py38.rc new file mode 100644 index 0000000000..ef16b89192 --- /dev/null +++ b/tests/functional/ext/docstyle/docstyle_quotes_py38.rc @@ -0,0 +1,5 @@ +[MASTER] +load-plugins=pylint.extensions.docstyle, + +[testoptions] +min_pyver=3.8 diff --git a/tests/functional/ext/docstyle/docstyle_quotes_py38.txt b/tests/functional/ext/docstyle/docstyle_quotes_py38.txt new file mode 100644 index 0000000000..a158059989 --- /dev/null +++ b/tests/functional/ext/docstyle/docstyle_quotes_py38.txt @@ -0,0 +1,4 @@ +bad-docstring-quotes:6:4:9:11:FFFF.method1:"Bad docstring quotes in method, expected """""", given '''":HIGH +bad-docstring-quotes:11:4:12:25:FFFF.method2:"Bad docstring quotes in method, expected """""", given """:HIGH +bad-docstring-quotes:14:4:15:25:FFFF.method3:"Bad docstring quotes in method, expected """""", given '":HIGH +bad-docstring-quotes:17:4:18:30:FFFF.method4:"Bad docstring quotes in method, expected """""", given '":HIGH From af974aa5498071baa73344a8ca84075b2e76305a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 12 Jan 2022 11:18:43 -0500 Subject: [PATCH 144/357] Fix #5586: False positive for `used-before-assignment` with homonyms in filtered comprehensions and except blocks (#5666) Co-authored-by: Pierre Sassoulas --- ChangeLog | 6 ++++++ doc/whatsnew/2.13.rst | 6 ++++++ pylint/checkers/variables.py | 7 +++++++ .../use/used_before_assignment_filtered_comprehension.py | 9 +++++++++ 4 files changed, 28 insertions(+) create mode 100644 tests/functional/u/use/used_before_assignment_filtered_comprehension.py diff --git a/ChangeLog b/ChangeLog index 93292a4112..6d4239fa12 100644 --- a/ChangeLog +++ b/ChangeLog @@ -96,6 +96,12 @@ Release date: TBA Closes #5360, #3877 +* Fixed a false positive (affecting unreleased development) for + ``used-before-assignment`` involving homonyms between filtered comprehensions + and assignments in except blocks. + + Closes #5586 + * Fixed crash with slots assignments and annotated assignments. Closes #5479 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 10e8c5b689..74499108d3 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -139,6 +139,12 @@ Other Changes Closes #5360, #3877 +* Fixed a false positive (affecting unreleased development) for + ``used-before-assignment`` involving homonyms between filtered comprehensions + and assignments in except blocks. + + Closes #5586 + * Fixed crash on list comprehensions that used ``type`` as inner variable name. Closes #5461 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 66af3ec940..bc291c1b9e 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1316,6 +1316,13 @@ def _check_consumer( if utils.is_func_decorator(current_consumer.node) or not ( current_consumer.scope_type == "comprehension" and self._has_homonym_in_upper_function_scope(node, consumer_level) + # But don't catch homonyms against the filter of a comprehension, + # (like "if x" in "[x for x in expr() if x]") + # https://github.com/PyCQA/pylint/issues/5586 + and not ( + isinstance(node.parent.parent, nodes.Comprehension) + and node.parent in node.parent.parent.ifs + ) ): self._check_late_binding_closure(node) self._loopvar_name(node) diff --git a/tests/functional/u/use/used_before_assignment_filtered_comprehension.py b/tests/functional/u/use/used_before_assignment_filtered_comprehension.py new file mode 100644 index 0000000000..f638b0be9b --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_filtered_comprehension.py @@ -0,0 +1,9 @@ +"""Homonym between filtered comprehension and assignment in except block.""" + +def func(): + """https://github.com/PyCQA/pylint/issues/5586""" + try: + print(value for value in range(1 / 0) if isinstance(value, int)) + except ZeroDivisionError: + value = 1 + print(value) From 6bbb7d5a0f530171faa3ea2271d9d1edab0c902b Mon Sep 17 00:00:00 2001 From: Arianna Y Date: Thu, 13 Jan 2022 02:37:21 -0600 Subject: [PATCH 145/357] Update primer package pandas-dev branch to main (#5669) --- tests/primer/packages_to_lint_batch_two.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/primer/packages_to_lint_batch_two.json b/tests/primer/packages_to_lint_batch_two.json index 636d155ff9..929d6e560b 100644 --- a/tests/primer/packages_to_lint_batch_two.json +++ b/tests/primer/packages_to_lint_batch_two.json @@ -1,6 +1,6 @@ { "pandas": { - "branch": "master", + "branch": "main", "directories": ["pandas"], "pylint_additional_args": ["--ignore-patterns=\"test_"], "url": "https://github.com/pandas-dev/pandas.git" From aaa3770d325223ee5c13a637f09ff16e5bbc5851 Mon Sep 17 00:00:00 2001 From: Kound Date: Thu, 13 Jan 2022 12:26:08 +0100 Subject: [PATCH 146/357] Add a checker for misleading unicode (#5311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- CONTRIBUTORS.txt | 3 +- ChangeLog | 22 + doc/whatsnew/2.13.rst | 23 + pylint/checkers/__init__.py | 4 +- pylint/checkers/unicode.py | 536 ++++++++++++++++++ tests/checkers/unittest_unicode/__init__.py | 82 +++ .../unittest_unicode/unittest_bad_chars.py | 267 +++++++++ .../unittest_bidirectional_unicode.py | 92 +++ .../unittest_unicode/unittest_functions.py | 259 +++++++++ .../unittest_invalid_encoding.py | 138 +++++ tests/conftest.py | 5 + .../b/bad_char/bad_char_backspace.py | 6 + .../b/bad_char/bad_char_backspace.txt | 1 + .../b/bad_char/bad_char_carriage_return.py | 6 + .../b/bad_char/bad_char_carriage_return.rc | 3 + .../b/bad_char/bad_char_carriage_return.txt | 1 + tests/functional/b/bad_char/bad_char_esc.py | 6 + tests/functional/b/bad_char/bad_char_esc.txt | 1 + tests/functional/b/bad_char/bad_char_sub.py | 6 + tests/functional/b/bad_char/bad_char_sub.txt | 1 + .../b/bad_char/bad_char_zero_width_space.py | 6 + .../b/bad_char/bad_char_zero_width_space.txt | 1 + .../e/.#emacs_file_lock_redefined_conf.rc | 1 + .../i/implicit/implicit_str_concat_latin1.py | 1 + .../i/implicit/implicit_str_concat_latin1.txt | 1 + tests/functional/n/non/non_ascii_name.py | 4 +- .../u/unicode/unicode_bidi_commenting_out.py | 12 + .../u/unicode/unicode_bidi_commenting_out.txt | 1 + .../u/unicode/unicode_bidi_early_return.py | 19 + .../u/unicode/unicode_bidi_early_return.txt | 1 + .../u/unicode/unicode_bidi_pep672.py | 8 + .../u/unicode/unicode_bidi_pep672.txt | 1 + .../unicode/invisible_function.txt | 15 + .../unicode/pep_bidirectional_utf_16_bom.txt | Bin 0 -> 215 bytes .../pep_bidirectional_utf_16_le_no_bom.txt | Bin 0 -> 211 bytes .../unicode/pep_bidirectional_utf_32_bom.txt | Bin 0 -> 429 bytes .../pep_bidirectional_utf_32_le_no_bom.txt | Bin 0 -> 421 bytes tests/test_self.py | 2 +- 38 files changed, 1530 insertions(+), 5 deletions(-) create mode 100644 pylint/checkers/unicode.py create mode 100644 tests/checkers/unittest_unicode/__init__.py create mode 100644 tests/checkers/unittest_unicode/unittest_bad_chars.py create mode 100644 tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py create mode 100644 tests/checkers/unittest_unicode/unittest_functions.py create mode 100644 tests/checkers/unittest_unicode/unittest_invalid_encoding.py create mode 100644 tests/functional/b/bad_char/bad_char_backspace.py create mode 100644 tests/functional/b/bad_char/bad_char_backspace.txt create mode 100644 tests/functional/b/bad_char/bad_char_carriage_return.py create mode 100644 tests/functional/b/bad_char/bad_char_carriage_return.rc create mode 100644 tests/functional/b/bad_char/bad_char_carriage_return.txt create mode 100644 tests/functional/b/bad_char/bad_char_esc.py create mode 100644 tests/functional/b/bad_char/bad_char_esc.txt create mode 100644 tests/functional/b/bad_char/bad_char_sub.py create mode 100644 tests/functional/b/bad_char/bad_char_sub.txt create mode 100644 tests/functional/b/bad_char/bad_char_zero_width_space.py create mode 100644 tests/functional/b/bad_char/bad_char_zero_width_space.txt create mode 100644 tests/functional/i/implicit/implicit_str_concat_latin1.txt create mode 100644 tests/functional/u/unicode/unicode_bidi_commenting_out.py create mode 100644 tests/functional/u/unicode/unicode_bidi_commenting_out.txt create mode 100644 tests/functional/u/unicode/unicode_bidi_early_return.py create mode 100644 tests/functional/u/unicode/unicode_bidi_early_return.txt create mode 100644 tests/functional/u/unicode/unicode_bidi_pep672.py create mode 100644 tests/functional/u/unicode/unicode_bidi_pep672.txt create mode 100644 tests/regrtest_data/unicode/invisible_function.txt create mode 100644 tests/regrtest_data/unicode/pep_bidirectional_utf_16_bom.txt create mode 100644 tests/regrtest_data/unicode/pep_bidirectional_utf_16_le_no_bom.txt create mode 100644 tests/regrtest_data/unicode/pep_bidirectional_utf_32_bom.txt create mode 100644 tests/regrtest_data/unicode/pep_bidirectional_utf_32_le_no_bom.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index aa4fb2e245..3a380f040b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -596,5 +596,6 @@ contributors: * Kian-Meng, Ang: contributor -* Carli* Freudenberg (CarliJoy): contributor +* Carli Freudenberg (CarliJoy): contributor + - Fixed issue 5281, added Unicode checker - Improve non-ascii-name checker diff --git a/ChangeLog b/ChangeLog index 6d4239fa12..61fa4b7502 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,28 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Added several checkers to deal with unicode security issues + (see `Trojan Sources `_ and + `PEP 672 `_ for details) that also + concern the readability of the code. In detail the following checks were added: + + * ``bad-file-encoding`` checks that the file is encoded in UTF-8 as suggested by + `PEP8 `_. + UTF-16 and UTF-32 are `not supported by Python `_ + at the moment. If this ever changes + ``invalid-unicode-codec`` checks that they aren't used, to allow for backwards + compatibility. + + * ``bidirectional-unicode`` checks for bidirectional unicode characters that + could make code execution different than what the user expects. + + * ``invalid-character-backspace``, ``invalid-character-carriage-return``, + ``invalid-character-sub``, ``invalid-character-esc``, + ``invalid-character-zero-width-space`` and ``invalid-character-nul`` + to check for possibly harmful unescaped characters. + + Closes #5281 + * Fixed false positive ``consider-using-dict-comprehension`` when creating a dict using a list of tuples where key AND value vary depending on the same condition. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 74499108d3..881c5c5f93 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -10,6 +10,29 @@ Summary -- Release highlights New checkers ============ + +* Added several checkers to deal with unicode security issues + (see `Trojan Sources `_ and + `PEP 672 `_ for details) that also + concern the readability of the code. In detail the following checks were added: + + * ``bad-file-encoding`` checks that the file is encoded in UTF-8 as suggested by + `PEP8 `_. + UTF-16 and UTF-32 are `not supported by Python `_ + at the moment. If this ever changes + ``invalid-unicode-codec`` checks that they aren't used, to allow for backwards + compatibility. + + * ``bidirectional-unicode`` checks for bidirectional unicode characters that + could make code execution different than what the user expects. + + * ``invalid-character-backspace``, ``invalid-character-carriage-return``, + ``invalid-character-sub``, ``invalid-character-esc``, + ``invalid-character-zero-width-space`` and ``invalid-character-nul`` + to check for possibly harmful unescaped characters. + + Closes #5281 + * ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily. Closes #5460 diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 914425313e..5ca89f7493 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -43,10 +43,12 @@ . . 24: non-ascii-names -25-50: not yet used: reserved for future internal checkers. +25: unicode +26-50: not yet used: reserved for future internal checkers. This file is not updated. Use script/get_unused_message_id_category.py to get the next free checker id. + 51-99: perhaps used: reserved for external checkers The raw_metrics checker has no number associated since it doesn't emit any diff --git a/pylint/checkers/unicode.py b/pylint/checkers/unicode.py new file mode 100644 index 0000000000..88b534daea --- /dev/null +++ b/pylint/checkers/unicode.py @@ -0,0 +1,536 @@ +# Copyright (c) 2021-2022 Carli Freudenberg + +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +"""Unicode and some other ASCII characters can be used to create programs that run +much different compared to what a human reader would expect from them. + +PEP 672 lists some examples. +See: https://www.python.org/dev/peps/pep-0672/ + +The following checkers are intended to make users are aware of these issues. +""" +import codecs +import contextlib +import io +import re +from collections import OrderedDict +from functools import lru_cache +from tokenize import detect_encoding +from typing import Dict, Iterable, NamedTuple, Tuple, TypeVar + +from astroid import nodes + +import pylint.checkers +import pylint.interfaces +import pylint.lint + +_StrLike = TypeVar("_StrLike", str, bytes) + +# Based on: +# https://golangexample.com/go-linter-which-checks-for-dangerous-unicode-character-sequences/ +# We use '\u' because it doesn't require a map lookup and is therefore faster +BIDI_UNICODE = [ + "\u202A", # \N{LEFT-TO-RIGHT EMBEDDING} + "\u202B", # \N{RIGHT-TO-LEFT EMBEDDING} + "\u202C", # \N{POP DIRECTIONAL FORMATTING} + "\u202D", # \N{LEFT-TO-RIGHT OVERRIDE} + "\u202E", # \N{RIGHT-TO-LEFT OVERRIDE} + "\u2066", # \N{LEFT-TO-RIGHT ISOLATE} + "\u2067", # \N{RIGHT-TO-LEFT ISOLATE} + "\u2068", # \N{FIRST STRONG ISOLATE} + "\u2069", # \N{POP DIRECTIONAL ISOLATE} + # The following was part of PEP 672: + # https://www.python.org/dev/peps/pep-0672/ + # so the list above might not be complete + "\u200F", # \n{RIGHT-TO-LEFT MARK} + # We don't use + # "\u200E" # \n{LEFT-TO-RIGHT MARK} + # as this is the default for latin files and can't be used + # to hide code +] + + +class _BadChar(NamedTuple): + """Representation of an ASCII char considered bad""" + + name: str + unescaped: str + escaped: str + code: str + help_text: str + + def description(self) -> str: + """Used for the detailed error message description""" + return ( + f"Invalid unescaped character {self.name}, " + f'use "{self.escaped}" instead.' + ) + + def human_code(self) -> str: + """Used to generate the human readable error message""" + return f"invalid-character-{self.name}" + + +# Based on https://www.python.org/dev/peps/pep-0672/ +BAD_CHARS = [ + _BadChar( + "backspace", + "\b", + "\\b", + "E2510", + ( + "Moves the cursor back, so the character after it will overwrite the " + "character before." + ), + ), + _BadChar( + "carriage-return", + "\r", + "\\r", + "E2511", + ( + "Moves the cursor to the start of line, subsequent characters overwrite " + "the start of the line." + ), + ), + _BadChar( + "sub", + "\x1A", + "\\x1A", + "E2512", + ( + "Ctrl+Z “End of text” on Windows. Some programs (such as type) ignore " + "the rest of the file after it." + ), + ), + _BadChar( + "esc", + "\x1B", + "\\x1B", + "E2513", + ( + "Commonly initiates escape codes which allow arbitrary control " + "of the terminal." + ), + ), + _BadChar( + "nul", + "\0", + "\\0", + "E2514", + "Mostly end of input for python.", + ), + _BadChar( + # Zero Width with Space. At the time of writing not accepted by Python. + # But used in Trojan Source Examples, so still included and tested for. + "zero-width-space", + "\u200B", # \n{ZERO WIDTH SPACE} + "\\u200B", + "E2515", + "Invisible space character could hide real code execution.", + ), +] +BAD_ASCII_SEARCH_DICT = {char.unescaped: char for char in BAD_CHARS} + + +def _line_length(line: _StrLike, codec: str) -> int: + """Get the length of a string like line as displayed in an editor""" + if isinstance(line, bytes): + decoded = _remove_bom(line, codec).decode(codec, "replace") + else: + decoded = line + + stripped = decoded.rstrip("\n") + + if stripped != decoded: + stripped = stripped.rstrip("\r") + + return len(stripped) + + +def _map_positions_to_result( + line: _StrLike, + search_dict: Dict[_StrLike, _BadChar], + new_line: _StrLike, + byte_str_length: int = 1, +) -> Dict[int, _BadChar]: + """Get all occurrences of search dict keys within line + + Ignores Windows end of line and can handle bytes as well as string. + Also takes care of encodings for which the length of an encoded code point does not + default to 8 Bit. + """ + + result: Dict[int, _BadChar] = {} + + for search_for, char in search_dict.items(): + if search_for not in line: + continue + + # Special Handling for Windows '\r\n' + if char.unescaped == "\r" and line.endswith(new_line): + ignore_pos = len(line) - 2 * byte_str_length + else: + ignore_pos = None + + start = 0 + pos = line.find(search_for, start) + while pos > 0: + if pos != ignore_pos: + # Calculate the column + col = int(pos / byte_str_length) + result[col] = char + start = pos + 1 + pos = line.find(search_for, start) + + return result + + +UNICODE_BOMS = { + "utf-8": codecs.BOM_UTF8, + "utf-16": codecs.BOM_UTF16, + "utf-32": codecs.BOM_UTF32, + "utf-16le": codecs.BOM_UTF16_LE, + "utf-16be": codecs.BOM_UTF16_BE, + "utf-32le": codecs.BOM_UTF32_LE, + "utf-32be": codecs.BOM_UTF32_BE, +} +BOM_SORTED_TO_CODEC = OrderedDict( + # Sorted by length of BOM of each codec + (UNICODE_BOMS[codec], codec) + for codec in ("utf-32le", "utf-32be", "utf-8", "utf-16le", "utf-16be") +) + +UTF_NAME_REGEX_COMPILED = re.compile( + "utf[ -]?(8|16|32)[ -]?(le|be|)?(sig)?", flags=re.IGNORECASE +) + + +def _normalize_codec_name(codec: str) -> str: + """Make sure the codec name is always given as defined in the BOM dict""" + return UTF_NAME_REGEX_COMPILED.sub(r"utf-\1\2", codec).lower() + + +def _remove_bom(encoded: bytes, encoding: str) -> bytes: + """remove the bom if given from a line""" + if not encoding.startswith("utf"): + return encoded + bom = UNICODE_BOMS[encoding] + if encoded.startswith(bom): + return encoded[len(bom) :] + return encoded + + +def _encode_without_bom(string: str, encoding: str) -> bytes: + """Encode a string but remove the BOM""" + return _remove_bom(string.encode(encoding), encoding) + + +def _byte_to_str_length(codec: str) -> int: + """return how many byte are usually(!) a character point""" + if codec.startswith("utf-32"): + return 4 + if codec.startswith("utf-16"): + return 2 + + return 1 + + +@lru_cache(maxsize=1000) +def _cached_encode_search(string: str, encoding: str) -> bytes: + """A cached version of encode used for search pattern""" + return _encode_without_bom(string, encoding) + + +def _fix_utf16_32_line_stream(steam: Iterable[bytes], codec: str) -> Iterable[bytes]: + """Handle line ending for UTF16 and UTF32 correctly + + Currently Python simply strips the required zeros after \n after the + line ending. Leading to lines that can't be decoded propery + """ + if not codec.startswith("utf-16") and not codec.startswith("utf-32"): + yield from steam + else: + # First we get all the bytes in memory + content = b"".join(line for line in steam) + + new_line = _cached_encode_search("\n", codec) + + # Now we split the line by the real new line in the correct encoding + # we can't use split as it would strip the \n that we need + start = 0 + while True: + pos = content.find(new_line, start) + if pos >= 0: + yield content[start : pos + len(new_line)] + else: + # Yield the rest and finish + if content[start:]: + yield content[start:] + break + + start = pos + len(new_line) + + +def extract_codec_from_bom(first_line: bytes) -> str: + """Try to extract the codec (unicode only) by checking for the BOM + + For details about BOM see https://unicode.org/faq/utf_bom.html#BOM + + Args: + first_line: the first line of a file + + Returns: + a codec name + + Raises: + ValueError: if no codec was found + """ + for bom, codec in BOM_SORTED_TO_CODEC.items(): + if first_line.startswith(bom): + return codec + + raise ValueError("No BOM found. Could not detect Unicode codec.") + + +class UnicodeChecker(pylint.checkers.BaseChecker): + """Check characters that could be used to hide bad code to humans + + This includes: + + - Bidirectional Unicode (see https://trojansource.codes/) + + - Bad ASCII characters (see PEP672) + + If a programmer requires to use such a character they should use the escaped + version, that is also much easier to read and does not depend on the editor used. + + The Checker also includes a check that UTF-16 and UTF-32 are not used to encode + Python files. + + At the time of writing Python supported only UTF-8. See + https://stackoverflow.com/questions/69897842/ and https://bugs.python.org/issue1503789 + for background. + """ + + __implements__ = pylint.interfaces.IRawChecker + priority = -1 + + name = "unicode_checker" + + msgs = { + "E2501": ( + # This error will be only displayed to users once Python Supports + # UTF-16/UTF-32 (if at all) + "UTF-16 and UTF-32 aren't backward compatible. Use UTF-8 instead", + "invalid-unicode-codec", + ( + "For compatibility use UTF-8 instead of UTF-16/UTF-32. " + "See also https://bugs.python.org/issue1503789 for a history " + "of this issue. And " + "https://softwareengineering.stackexchange.com/questions/102205/should-utf-16-be-considered-harmful " + "for some possible problems when using UTF-16 for instance." + ), + ), + "E2502": ( + ( + "Contains control characters that can permit obfuscated code " + "executed differently than displayed" + ), + "bidirectional-unicode", + ( + "bidirectional unicode are typically not displayed characters required " + "to display right-to-left (RTL) script " + "(i.e. Chinese, Japanese, Arabic, Hebrew, ...) correctly. " + "So can you trust this code? " + "Are you sure it displayed correctly in all editors? " + "If you did not write it or your language is not RTL," + " remove the special characters, as they could be used to trick you into executing code, " + "that does something else than what it looks like.\n" + "More Information:\n" + "https://en.wikipedia.org/wiki/Bidirectional_text\n" + "https://trojansource.codes/" + ), + ), + "C2503": ( + "PEP8 recommends UTF-8 as encoding for Python files", + "bad-file-encoding", + ( + "PEP8 recommends UTF-8 default encoding for Python files. See " + "https://www.python.org/dev/peps/pep-0008/#id20" + ), + ), + **{ + bad_char.code: ( + bad_char.description(), + bad_char.human_code(), + bad_char.help_text, + ) + for bad_char in BAD_CHARS + }, + } + + @staticmethod + def _is_invalid_codec(codec: str) -> bool: + return codec.startswith("utf-16") or codec.startswith("utf-32") + + @staticmethod + def _is_unicode(codec: str) -> bool: + return codec.startswith("utf") + + @classmethod + def _find_line_matches(cls, line: bytes, codec: str) -> Dict[int, _BadChar]: + """Find all matches of BAD_CHARS within line + + Args: + line: the input + codec: that will be used to convert line/or search string into + + Return: + A dictionary with the column offset and the BadASCIIChar + """ + # We try to decode in Unicode to get the correct column offset + # if we would use bytes, it could be off because UTF-8 has no fixed length + try: + line_search = line.decode(codec, errors="strict") + search_dict = BAD_ASCII_SEARCH_DICT + return _map_positions_to_result(line_search, search_dict, "\n") + except UnicodeDecodeError: + # If we can't decode properly, we simply use bytes, even so the column offsets + # might be wrong a bit, but it is still better then nothing + line_search_byte = line + search_dict_byte: Dict[bytes, _BadChar] = {} + for char in BAD_CHARS: + # Some characters might not exist in all encodings + with contextlib.suppress(UnicodeDecodeError): + search_dict_byte[ + _cached_encode_search(char.unescaped, codec) + ] = char + + return _map_positions_to_result( + line_search_byte, + search_dict_byte, + _cached_encode_search("\n", codec), + byte_str_length=_byte_to_str_length(codec), + ) + + @staticmethod + def _determine_codec(stream: io.BytesIO) -> Tuple[str, int]: + """determine the codec from the given stream + + first tries https://www.python.org/dev/peps/pep-0263/ + and if this fails also checks for BOMs of UTF-16 and UTF-32 + to be future-proof. + + Args: + stream: The byte stream to analyse + + Returns: A tuple consisting of: + - normalized codec name + - the line in which the codec was found + + Raises: + SyntaxError: if failing to detect codec + """ + try: + # First try to detect encoding with PEP 263 + # Doesn't work with UTF-16/32 at the time of writing + # see https://bugs.python.org/issue1503789 + codec, lines = detect_encoding(stream.readline) + + # lines are empty if UTF-8 BOM is found + codec_definition_line = len(lines) or 1 + except SyntaxError as e: + # Codec could not be detected by Python, we try manually to check for + # UTF 16/32 BOMs, which aren't supported by Python at the time of writing. + # This is only included to be future save and handle these codecs as well + stream.seek(0) + try: + codec = extract_codec_from_bom(stream.readline()) + codec_definition_line = 1 + except ValueError as ve: + # Failed to detect codec, so the syntax error originated not from + # UTF16/32 codec usage. So simply raise the error again. + raise e from ve + + return _normalize_codec_name(codec), codec_definition_line + + def _check_codec(self, codec: str, codec_definition_line: int) -> None: + """Check validity of the codec""" + if codec != "utf-8": + msg = "bad-file-encoding" + if self._is_invalid_codec(codec): + msg = "invalid-unicode-codec" + self.add_message( + msg, + # Currently Nodes will lead to crashes of pylint + # node=node, + line=codec_definition_line, + end_lineno=codec_definition_line, + confidence=pylint.interfaces.HIGH, + col_offset=None, + end_col_offset=None, + ) + + def _check_invalid_chars(self, line: bytes, lineno: int, codec: str) -> None: + """Look for chars considered bad""" + matches = self._find_line_matches(line, codec) + for col, char in matches.items(): + self.add_message( + char.human_code(), + # Currently Nodes will lead to crashes of pylint + # node=node, + line=lineno, + end_lineno=lineno, + confidence=pylint.interfaces.HIGH, + col_offset=col + 1, + end_col_offset=col + len(char.unescaped) + 1, + ) + + def _check_bidi_chars(self, line: bytes, lineno: int, codec: str) -> None: + """Look for Bidirectional Unicode, if we use unicode""" + if not self._is_unicode(codec): + return + for dangerous in BIDI_UNICODE: + if _cached_encode_search(dangerous, codec) in line: + # Note that we don't add a col_offset on purpose: + # Using these unicode characters it depends on the editor + # how it displays the location of characters in the line. + # So we mark the complete line. + self.add_message( + "bidirectional-unicode", + # Currently Nodes will lead to crashes of pylint + # node=node, + line=lineno, + end_lineno=lineno, + # We mark the complete line, as bidi controls make it hard + # to determine the correct cursor position within an editor + col_offset=0, + end_col_offset=_line_length(line, codec), + confidence=pylint.interfaces.HIGH, + ) + # We look for bidirectional unicode only once per line + # as we mark the complete line anyway + break + + def process_module(self, node: nodes.Module) -> None: + """Perform the actual check by checking module stream.""" + with node.stream() as stream: + codec, codec_line = self._determine_codec(stream) + self._check_codec(codec, codec_line) + + stream.seek(0) + + # Check for invalid content (controls/chars) + for (lineno, line) in enumerate( + _fix_utf16_32_line_stream(stream, codec), start=1 + ): + if lineno == 1: + line = _remove_bom(line, codec) + self._check_bidi_chars(line, lineno, codec) + self._check_invalid_chars(line, lineno, codec) + + +def register(linter: pylint.lint.PyLinter) -> None: + linter.register_checker(UnicodeChecker(linter)) diff --git a/tests/checkers/unittest_unicode/__init__.py b/tests/checkers/unittest_unicode/__init__.py new file mode 100644 index 0000000000..7a748245af --- /dev/null +++ b/tests/checkers/unittest_unicode/__init__.py @@ -0,0 +1,82 @@ +import io +from pathlib import Path + +import pylint.interfaces +import pylint.testutils + +CODEC_AND_MSG = [ + ("utf-8", tuple()), + ( + "utf-16", + ( + pylint.testutils.MessageTest( + msg_id="invalid-unicode-codec", + confidence=pylint.interfaces.HIGH, + # node=module, + line=1, + end_line=1, + col_offset=None, + end_col_offset=None, + ), + ), + ), + ( + "utf-32", + ( + pylint.testutils.MessageTest( + msg_id="invalid-unicode-codec", + confidence=pylint.interfaces.HIGH, + # node=module, + line=1, + end_line=1, + col_offset=None, + end_col_offset=None, + ), + ), + ), + ( + "iso-8859-1", + ( + pylint.testutils.MessageTest( + msg_id="bad-file-encoding", + confidence=pylint.interfaces.HIGH, + # node=module, + line=1, + end_line=1, + col_offset=None, + end_col_offset=None, + ), + ), + ), + ( + "ascii", + ( + pylint.testutils.MessageTest( + msg_id="bad-file-encoding", + confidence=pylint.interfaces.HIGH, + # node=module, + line=1, + end_line=1, + col_offset=None, + end_col_offset=None, + ), + ), + ), +] + + +class FakeNode: + """Simple Faker representing a Module node. + + Astroid crashes in a number of cases if we want to lint unsupported encodings. + So, this is used to test the behaviour of the encoding checker. + This shall ensure that our checks keep working once Python supports UTF16/32. + """ + + file: Path + + def __init__(self, content: bytes): + self.content = io.BytesIO(content) + + def stream(self): + return self.content diff --git a/tests/checkers/unittest_unicode/unittest_bad_chars.py b/tests/checkers/unittest_unicode/unittest_bad_chars.py new file mode 100644 index 0000000000..f764aa0cc4 --- /dev/null +++ b/tests/checkers/unittest_unicode/unittest_bad_chars.py @@ -0,0 +1,267 @@ +# pylint: disable=redefined-outer-name +import itertools +from pathlib import Path +from typing import Callable, Tuple, cast + +import astroid +import pytest +from astroid import AstroidBuildingError, nodes + +import pylint.checkers.unicode +import pylint.interfaces +import pylint.testutils + +from . import CODEC_AND_MSG, FakeNode + + +@pytest.fixture() +def bad_char_file_generator(tmp_path: Path) -> Callable[[str, bool, str], Path]: + """generates a test file for bad chars + + The generator also ensures that file generated is correct + """ + + def encode_without_bom(string, encoding): + return pylint.checkers.unicode._encode_without_bom(string, encoding) + + # All lines contain a not extra checked invalid character + lines = ( + "# Example File containing bad ASCII", + "# invalid char backspace: \b", + "# Bad carriage-return \r # not at the end", + "# Invalid char sub: \x1A", + "# Invalid char esc: \x1B", + ) + + def _bad_char_file_generator(codec: str, add_invalid_bytes: bool, line_ending: str): + byte_suffix = b"" + if add_invalid_bytes: + if codec == "utf-8": + byte_suffix = b"BAD:\x80abc" + elif codec == "utf-16": + byte_suffix = b"BAD:\n" # Generates Truncated Data + else: + byte_suffix = b"BAD:\xc3\x28 " + byte_suffix = encode_without_bom(" foobar ", codec) + byte_suffix + + line_ending_encoded = encode_without_bom(line_ending, codec) + + # Start content with BOM / codec definition and two empty lines + content = f"# coding: {codec} \n # \n ".encode(codec) + + # Generate context with the given codec and line ending + for lineno, line in enumerate(lines): + byte_line = encode_without_bom(line, codec) + byte_line += byte_suffix + line_ending_encoded + content += byte_line + + # Directly test the generated content + if not add_invalid_bytes: + # Test that the content is correct and gives no errors + try: + byte_line.decode(codec, "strict") + except UnicodeDecodeError as e: + raise ValueError( + f"Line {lineno} did raise unexpected error: {byte_line}\n{e}" + ) from e + else: + try: + # But if there was a byte_suffix we expect an error + # because that is what we want to test for + byte_line.decode(codec, "strict") + except UnicodeDecodeError: + ... + else: + raise ValueError( + f"Line {lineno} did not raise decode error: {byte_line}" + ) + + file = tmp_path / "bad_chars.py" + file.write_bytes(content) + return file + + return _bad_char_file_generator + + +class TestBadCharsChecker(pylint.testutils.CheckerTestCase): + CHECKER_CLASS = pylint.checkers.unicode.UnicodeChecker + + checker: pylint.checkers.unicode.UnicodeChecker + + @pytest.mark.parametrize( + "codec_and_msg, line_ending, add_invalid_bytes", + [ + pytest.param( + codec_and_msg, + line_ending[0], + suffix[0], + id=f"{codec_and_msg[0]}_{line_ending[1]}_{suffix[1]}", + ) + for codec_and_msg, line_ending, suffix in itertools.product( + CODEC_AND_MSG, + (("\n", "linux"), ("\r\n", "windows")), + ((False, "valid_line"), (True, "not_decode_able_line")), + ) + # Only utf8 can drop invalid lines + if codec_and_msg[0].startswith("utf") or not suffix[0] + ], + ) + def test_find_bad_chars( + self, + bad_char_file_generator: Callable[[str, bool, str], Path], + codec_and_msg: Tuple[str, Tuple[pylint.testutils.MessageTest]], + line_ending: str, + add_invalid_bytes: bool, + ): + """All combinations of bad characters that are accepted by Python at the moment + are tested in all possible combinations of + - line ending + - encoding + - including not encode-able byte (or not) + """ + codec, start_msg = codec_and_msg + + start_lines = 2 + + file = bad_char_file_generator(codec, add_invalid_bytes, line_ending) + + try: + # We need to use ast from file as only this function reads bytes and not + # string + module = astroid.MANAGER.ast_from_string(file) + except AstroidBuildingError: + module = cast(nodes.Module, FakeNode(file.read_bytes())) + + expected = [ + *start_msg, + pylint.testutils.MessageTest( + msg_id="invalid-character-backspace", + line=2 + start_lines, + end_line=2 + start_lines, + # node=module, + args=None, + confidence=pylint.interfaces.HIGH, + col_offset=27, + end_col_offset=28, + ), + pylint.testutils.MessageTest( + msg_id="invalid-character-carriage-return", + line=3 + start_lines, + end_line=3 + start_lines, + # node=module, + args=None, + confidence=pylint.interfaces.HIGH, + col_offset=23, + end_col_offset=24, + ), + pylint.testutils.MessageTest( + msg_id="invalid-character-sub", + line=4 + start_lines, + end_line=4 + start_lines, + # node=module, + args=None, + confidence=pylint.interfaces.HIGH, + col_offset=21, + end_col_offset=22, + ), + pylint.testutils.MessageTest( + msg_id="invalid-character-esc", + line=5 + start_lines, + end_line=5 + start_lines, + # node=module, + args=None, + confidence=pylint.interfaces.HIGH, + col_offset=21, + end_col_offset=22, + ), + ] + with self.assertAddsMessages(*expected): + self.checker.process_module(module) + + @pytest.mark.parametrize( + "codec_and_msg, char, msg_id", + [ + pytest.param( + codec_and_msg, + char_msg[0], + char_msg[1], + id=f"{char_msg[1]}_{codec_and_msg[0]}", + ) + for codec_and_msg, char_msg in itertools.product( + CODEC_AND_MSG, + ( + ("\0", "invalid-character-nul"), + ("\N{ZERO WIDTH SPACE}", "invalid-character-zero-width-space"), + ), + ) + # Only utf contains zero width space + if ( + char_msg[0] != "\N{ZERO WIDTH SPACE}" + or codec_and_msg[0].startswith("utf") + ) + ], + ) + def test_bad_chars_that_would_currently_crash_python( + self, + char: str, + msg_id: str, + codec_and_msg: Tuple[str, Tuple[pylint.testutils.MessageTest]], + ): + """Special test for a file containing chars that lead to + Python or Astroid crashes (which causes Pylint to exit early) + """ + codec, start_msg = codec_and_msg + # Create file that will fail loading in astroid. + # We still want to check this, in case this behavior changes + content = f"# # coding: {codec}\n# file containing {char} <-\n" + module = FakeNode(content.encode(codec)) + + expected = [ + *start_msg, + pylint.testutils.MessageTest( + msg_id=msg_id, + line=2, + end_line=2, + # node=module, + args=None, + confidence=pylint.interfaces.HIGH, + col_offset=19, + end_col_offset=20, + ), + ] + + with self.assertAddsMessages(*expected): + self.checker.process_module(cast(nodes.Module, module)) + + @pytest.mark.parametrize( + "char, msg, codec", + [ + pytest.param( + char.unescaped, + char.human_code(), + codec_and_msg[0], + id=f"{char.name}_{codec_and_msg[0]}", + ) + for char, codec_and_msg in itertools.product( + pylint.checkers.unicode.BAD_CHARS, CODEC_AND_MSG + ) + # Only utf contains zero width space + if ( + char.unescaped != "\N{ZERO WIDTH SPACE}" + or codec_and_msg[0].startswith("utf") + ) + ], + ) + def test___check_invalid_chars(self, char: str, msg: str, codec: str) -> None: + """Check function should deliver correct column no matter which codec we used""" + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id=msg, + line=55, + args=None, + confidence=pylint.interfaces.HIGH, + col_offset=5, + end_col_offset=6, + ) + ): + self.checker._check_invalid_chars(f"#234{char}".encode(codec), 55, codec) diff --git a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py new file mode 100644 index 0000000000..0eb0faf5a2 --- /dev/null +++ b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py @@ -0,0 +1,92 @@ +import itertools +import unicodedata +from typing import cast + +import astroid +import pytest +from astroid import nodes + +import pylint.checkers.unicode +import pylint.interfaces +import pylint.testutils + +from . import FakeNode + + +class TestBidirectionalUnicodeChecker(pylint.testutils.CheckerTestCase): + CHECKER_CLASS = pylint.checkers.unicode.UnicodeChecker + + checker: pylint.checkers.unicode.UnicodeChecker + + def test_finds_bidirectional_unicode_that_currently_not_parsed( + self, unicode_example_dir + ): + """Test an example from https://github.com/nickboucher/trojan-source/tree/main/Python + that is currently not working Python but producing a syntax error + + So we test this to make sure it stays like this + """ + + test_file = unicode_example_dir / "invisible_function.txt" + + with pytest.raises(astroid.AstroidSyntaxError): + astroid.MANAGER.ast_from_string(test_file.read_text("utf-8")) + + with pytest.raises(AssertionError): + # The following errors are not risen at the moment, + # But we keep this in order to allow writing the test fast, if + # the condition above isn't met anymore. + module = FakeNode(test_file.read_bytes()) + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id="bidirectional-unicode", + confidence=pylint.interfaces.HIGH, + # node=module, + line=6, + end_line=10, + col_offset=0, + end_col_offset=17, + ), + pylint.testutils.MessageTest( + msg_id="bidirectional-unicode", + confidence=pylint.interfaces.HIGH, + line=10, + # node=module, + end_line=10, + col_offset=0, + end_col_offset=20, + ), + ): + self.checker.process_module(cast(nodes.Module, module)) + + @pytest.mark.parametrize( + "bad_string, codec", + [ + pytest.param( + char, + codec, + id=f"{unicodedata.name(char)}_{codec}".replace(" ", "_"), + ) + for char, codec in itertools.product( + pylint.checkers.unicode.BIDI_UNICODE, + ("utf-8", "utf-16le", "utf-16be", "utf-32le", "utf-32be"), + ) + ], + ) + def test_find_bidi_string(self, bad_string: str, codec: str): + """Ensure that all Bidirectional strings are detected + + Tests also UTF-16 and UTF-32. + """ + expected = pylint.testutils.MessageTest( + msg_id="bidirectional-unicode", + confidence=pylint.interfaces.HIGH, + line=1, + # node=module, + end_line=1, + col_offset=0, + end_col_offset=3, + ) + + with self.assertAddsMessages(expected): + self.checker._check_bidi_chars(f"# {bad_string}".encode(codec), 1, codec) diff --git a/tests/checkers/unittest_unicode/unittest_functions.py b/tests/checkers/unittest_unicode/unittest_functions.py new file mode 100644 index 0000000000..a84c0be42e --- /dev/null +++ b/tests/checkers/unittest_unicode/unittest_functions.py @@ -0,0 +1,259 @@ +import itertools +from pathlib import Path +from typing import Dict + +import pytest + +import pylint.checkers.unicode + +SEARCH_DICT_BYTE_UTF8 = { + char.unescaped.encode("utf-8"): char for char in pylint.checkers.unicode.BAD_CHARS +} + + +@pytest.mark.parametrize( + "line, expected, search_dict", + [ + # Test special carrier return cases + pytest.param( + "valid windows\r\n", + {}, + pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT, + id="valid-windows", + ), + pytest.param( + b"TOTO = ('Caf\xe9', 'Caf\xe9', 'Caf\xe9')\r\n", + {}, + SEARCH_DICT_BYTE_UTF8, + id="valid-windows-bytes", + ), + pytest.param( + "invalid\r windows\r\n", + {7: pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT["\r"]}, + pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT, + id="invalid-carrier-return-windows", + ), + pytest.param( + "invalid\r linux\n", + {7: pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT["\r"]}, + pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT, + id="invalid-carrier-return-linux", + ), + pytest.param( + b"invalid\r windows\r\n", + {7: pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT["\r"]}, + SEARCH_DICT_BYTE_UTF8, + id="invalid-carrier-return-windows-bytes", + ), + pytest.param( + b"invalid\r linux\n", + {7: pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT["\r"]}, + SEARCH_DICT_BYTE_UTF8, + id="invalid-carrier-return-linux-bytes", + ), + # Auto test Linux all remaining Linux cases ... + *( + pytest.param( + f"invalid{char.unescaped} back\n", + {7: char}, + pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT, + id=f"invalid-{char.name}-linux", + ) + for char in pylint.checkers.unicode.BAD_CHARS + if char.unescaped != "\r" + ), + # ... also byte encoded + *( + pytest.param( + f"invalid{char.unescaped} back\n".encode("ASCII"), + {7: char}, + SEARCH_DICT_BYTE_UTF8, + id=f"invalid-{char.name}-linux-bytes", + ) + for char in pylint.checkers.unicode.BAD_CHARS[:-1] + if char.unescaped != "\r" + ), + # Test all remaining windows cases ... + *( + pytest.param( + f"invalid{char.unescaped} back\r\n", + {7: char}, + pylint.checkers.unicode.BAD_ASCII_SEARCH_DICT, + id=f"invalid-{char.name}-windows", + ) + for char in pylint.checkers.unicode.BAD_CHARS + if char.unescaped != "\r" + ), + # ... also byte encoded + *( + pytest.param( + f"invalid{char.unescaped} back\r\n".encode("ASCII"), + {7: char}, + SEARCH_DICT_BYTE_UTF8, + id=f"invalid-{char.name}-windows-bytes", + ) + for char in pylint.checkers.unicode.BAD_CHARS[:-1] + if char.unescaped != "\r" + ), + ], +) +def test_map_positions_to_result( + line: pylint.checkers.unicode._StrLike, + expected: Dict[int, pylint.checkers.unicode._BadChar], + search_dict, +): + """test all possible outcomes for map position function in UTF-8 and ASCII""" + if isinstance(line, bytes): + newline = b"\n" + else: + newline = "\n" + assert ( + pylint.checkers.unicode._map_positions_to_result( + line, search_dict, new_line=newline + ) + == expected + ) + + +@pytest.mark.parametrize( + "line", + [ + pytest.param("1234567890", id="no_line_ending"), + pytest.param(b"1234567890", id="no_line_ending_byte"), + pytest.param("1234567890\n", id="linux"), + pytest.param(b"1234567890\n", id="linux_byte"), + pytest.param("1234567890\r\n", id="windows"), + pytest.param(b"1234567890\r\n", id="windows_byte"), + pytest.param("12345678\n\r", id="wrong_order"), + pytest.param(b"12345678\n\r", id="wrong_order_byte"), + ], +) +def test_line_length(line: pylint.checkers.unicode._StrLike): + assert pylint.checkers.unicode._line_length(line, "utf-8") == 10 + + +@pytest.mark.parametrize( + "line", + [ + pytest.param("1234567890", id="no_line_ending"), + pytest.param("1234567890\n", id="linux"), + pytest.param("1234567890\r\n", id="windows"), + pytest.param("12345678\n\r", id="wrong_order"), + ], +) +def test_line_length_utf16(line: str): + assert pylint.checkers.unicode._line_length(line.encode("utf-16"), "utf-16") == 10 + + +@pytest.mark.parametrize( + "line", + [ + pytest.param("1234567890", id="no_line_ending"), + pytest.param("1234567890\n", id="linux"), + pytest.param("1234567890\r\n", id="windows"), + pytest.param("12345678\n\r", id="wrong_order"), + ], +) +def test_line_length_utf32(line: str): + assert pylint.checkers.unicode._line_length(line.encode("utf-32"), "utf-32") == 10 + + +@pytest.mark.parametrize( + "codec, expected", + [ + ("utf-8sig", "utf-8"), + ("utf8", "utf-8"), + ("utf 8", "utf-8"), + ("utf-8", "utf-8"), + ("utf-8", "utf-8"), + ("utf-16", "utf-16"), + ("utf-32", "utf-32"), + ("utf 16", "utf-16"), + ("utf 32", "utf-32"), + ("utf 16 LE", "utf-16le"), + ("utf 32-BE", "utf-32be"), + ("UTF-32", "utf-32"), + ("UTF-32-le", "utf-32le"), + ("UTF-16 LE", "utf-16le"), + ("UTF-16BE", "utf-16be"), + ("UTF8", "utf-8"), + ("Latin1", "latin1"), + ("ASCII", "ascii"), + ], +) +def test__normalize_codec_name(codec: str, expected: str): + assert pylint.checkers.unicode._normalize_codec_name(codec) == expected + + +@pytest.mark.parametrize( + "codec, line_ending, final_new_line", + [ + pytest.param( + codec, + line_ending[0], + final_nl[0], + id=f"{codec}_{line_ending[1]}_{final_nl[1]}", + ) + for codec, line_ending, final_nl in itertools.product( + ( + "utf-8", + "utf-16", + "utf-16le", + "utf-16be", + "utf-32", + "utf-32le", + "utf-32be", + ), + (("\n", "linux"), ("\r\n", "windows")), + ((True, "final_nl"), (False, "no_final_nl")), + ) + ], +) +def test___fix_utf16_32_line_stream( + tmp_path: Path, codec: str, line_ending: str, final_new_line: bool +): + """content of stream should be the same as should be the length""" + + def decode_line(line: bytes, codec: str) -> str: + return line.decode(codec) + + file = tmp_path / "test.txt" + + content = [ + f"line1{line_ending}", + f"# Line 2{line_ending}", + f"łöł{line_ending}", + f"last line{line_ending if final_new_line else ''}", + ] + + text = "".join(content) + encoded = text.encode(codec) + + file.write_bytes(encoded) + + gathered = b"" + collected = [] + with file.open("rb") as f: + for line in pylint.checkers.unicode._fix_utf16_32_line_stream(f, codec): + gathered += line + collected.append(decode_line(line, codec)) + + # Test content equality + assert collected == content + # Test byte equality + assert gathered == encoded + + +@pytest.mark.parametrize( + "codec, expected", + [ + ("utf-32", 4), + ("utf-32-le", 4), + ("utf-16", 2), + ("utf-8", 1), + ("latin1", 1), + ("ascii", 1), + ], +) +def test__byte_to_str_length(codec: str, expected: int): + assert pylint.checkers.unicode._byte_to_str_length(codec) == expected diff --git a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py new file mode 100644 index 0000000000..5a6f1547c0 --- /dev/null +++ b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py @@ -0,0 +1,138 @@ +import codecs +import io +import shutil +from pathlib import Path +from typing import Tuple, cast + +import pytest +from astroid import nodes + +import pylint.checkers.unicode +import pylint.interfaces +import pylint.testutils + +from . import CODEC_AND_MSG, FakeNode + + +class TestInvalidEncoding(pylint.testutils.CheckerTestCase): + CHECKER_CLASS = pylint.checkers.unicode.UnicodeChecker + checker: pylint.checkers.unicode.UnicodeChecker + + @pytest.mark.parametrize( + "test_file, line_no", + [ + pytest.param( + "pep_bidirectional_utf_16_le_no_bom.txt", + 2, + marks=pytest.mark.xfail( + reason="Python currently doesn't support UTF-16 code detection" + ), + ), + pytest.param( + "pep_bidirectional_utf_32_le_no_bom.txt", + 2, + marks=pytest.mark.xfail( + reason="Python currently doesn't support UTF-32 code detection" + ), + ), + # A note to the xfails above: If you open these files in an editor, you + # only will see garbage if you don't select the correct encoding by hand. + # Still maybe in the future the python way of defining the encoding could + # work - even so it is unlikely as the first line is not ASCII and would + # have to be treated differently anyway... + ("pep_bidirectional_utf_16_bom.txt", 1), + ("pep_bidirectional_utf_32_bom.txt", 1), + ], + ) + def test_invalid_unicode_files( + self, unicode_example_dir: Path, tmp_path: Path, test_file: str, line_no: int + ): + test_file_path = unicode_example_dir / test_file + target = shutil.copy( + test_file_path, tmp_path / test_file.replace(".txt", ".py") + ) + + # Fake node as otherwise we get syntax errors etc... + # So currently the UTF-16/UTF-32 tests does not work, as UTF-16 / UTF-32 + # is not really working at all in in Python, but checking it now already + # is future save in case that changes.... + + module = FakeNode(Path(target).read_bytes()) + + with self.assertAddsMessages( + pylint.testutils.MessageTest( + msg_id="invalid-unicode-codec", + confidence=pylint.interfaces.HIGH, + # node=module, + line=line_no, + end_line=1, + col_offset=None, + end_col_offset=None, + ), + pylint.testutils.MessageTest( + msg_id="bidirectional-unicode", + confidence=pylint.interfaces.HIGH, + # node=module, + line=line_no + 2, + end_line=line_no + 2, + col_offset=0, + end_col_offset=37, + ), + ): + self.checker.process_module(cast(nodes.Module, module)) + + @pytest.mark.parametrize( + "content, codec, line", + [ + pytest.param(b"# Nothing", "utf-8", 1, id="default_utf8"), + pytest.param(b"# coding: latin-1", "iso-8859-1", 1, id="pep263_latin1"), + pytest.param( + b"#!/usr/bin/python\n# coding: latin-1", + "iso-8859-1", + 2, + id="pep263_latin1_multiline", + ), + pytest.param(b"# coding: ascii", "ascii", 1, id="pep263_ascii"), + pytest.param(b"# coding: UTF-8", "utf-8", 1, id="pep263_utf-8"), + # This looks correct but is actually wrong. If you would try to decode + # the byte to utf-16be it would fail + pytest.param( + b"# coding: UTF-16le", "utf-16le", 1, id="pep263_utf-16le_fake" + ), + # This contains no bom but a correct encoding line in none ascii + # So this fails at the moment + pytest.param( + "# coding: UTF-16le".encode("utf-16le"), + "utf-16le", + 1, + id="pep263_utf-16le_real", + marks=pytest.mark.xfail(reason="Currently not supported by Python"), + ), + *( + pytest.param(bom, codec, 1, id=f"bom_{codec}") + for codec, bom in ( + ("utf-8", codecs.BOM_UTF8), + ("utf-16le", codecs.BOM_UTF16_LE), + ("utf-16be", codecs.BOM_UTF16_BE), + ("utf-32le", codecs.BOM_UTF32_LE), + ("utf-32be", codecs.BOM_UTF32_BE), + ) + ), + ], + ) + def test__determine_codec(self, content: bytes, codec: str, line: int): + """The codec determined should be exact no matter what we throw at it""" + assert self.checker._determine_codec(io.BytesIO(content)) == (codec, line) + + def test__determine_codec_raises_syntax_error_on_invalid_input(self): + """invalid input should lead to a SyntaxError""" + with pytest.raises(SyntaxError): + self.checker._determine_codec(io.BytesIO(b"\x80abc")) + + @pytest.mark.parametrize( + "codec, msg", + (pytest.param(codec, msg, id=codec) for codec, msg in CODEC_AND_MSG), + ) + def test___check_codec(self, codec: str, msg: Tuple[pylint.testutils.MessageTest]): + with self.assertAddsMessages(*msg): + self.checker._check_codec(codec, 1) diff --git a/tests/conftest.py b/tests/conftest.py index bdd3e4516c..e004e16c36 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -58,6 +58,11 @@ def reporter(): return MinimalTestReporter +@pytest.fixture +def unicode_example_dir(tests_directory: Path) -> Path: + return tests_directory / "regrtest_data" / "unicode" + + def pytest_addoption(parser) -> None: parser.addoption( "--primer-stdlib", diff --git a/tests/functional/b/bad_char/bad_char_backspace.py b/tests/functional/b/bad_char/bad_char_backspace.py new file mode 100644 index 0000000000..5a2325b1a3 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_backspace.py @@ -0,0 +1,6 @@ +# pylint: disable=trailing-whitespace, missing-module-docstring, trailing-newlines, missing-final-newline + +# +2: [invalid-character-backspace] +BACKSPACE = ''' +--><--- +''' # bad char backspace should be only used as \b diff --git a/tests/functional/b/bad_char/bad_char_backspace.txt b/tests/functional/b/bad_char/bad_char_backspace.txt new file mode 100644 index 0000000000..4850fad0ef --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_backspace.txt @@ -0,0 +1 @@ +invalid-character-backspace:5:4:5:5::"Invalid unescaped character backspace, use ""\b"" instead.":HIGH diff --git a/tests/functional/b/bad_char/bad_char_carriage_return.py b/tests/functional/b/bad_char/bad_char_carriage_return.py new file mode 100644 index 0000000000..bb17880ca7 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_carriage_return.py @@ -0,0 +1,6 @@ +# pylint: disable=trailing-whitespace, missing-module-docstring, trailing-newlines, missing-final-newline + +# +2: [invalid-character-carriage-return] +CARRIAGE_RETURN = ''' +--> <--- +''' # bad char carriage-return should be only used as \r diff --git a/tests/functional/b/bad_char/bad_char_carriage_return.rc b/tests/functional/b/bad_char/bad_char_carriage_return.rc new file mode 100644 index 0000000000..7ca0c92636 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_carriage_return.rc @@ -0,0 +1,3 @@ +[testoptions] +# This test cannot run on Windows as \r will be the newline +exclude_platforms=win32 diff --git a/tests/functional/b/bad_char/bad_char_carriage_return.txt b/tests/functional/b/bad_char/bad_char_carriage_return.txt new file mode 100644 index 0000000000..3fdb3533fc --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_carriage_return.txt @@ -0,0 +1 @@ +invalid-character-carriage-return:5:4:5:5::"Invalid unescaped character carriage-return, use ""\r"" instead.":HIGH diff --git a/tests/functional/b/bad_char/bad_char_esc.py b/tests/functional/b/bad_char/bad_char_esc.py new file mode 100644 index 0000000000..53bb0b4ed8 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_esc.py @@ -0,0 +1,6 @@ +# pylint: disable=trailing-whitespace, missing-module-docstring, trailing-newlines, missing-final-newline + +# +2: [invalid-character-esc] +ESC = ''' +--><--- +''' # bad char esc should be only used as \x1B diff --git a/tests/functional/b/bad_char/bad_char_esc.txt b/tests/functional/b/bad_char/bad_char_esc.txt new file mode 100644 index 0000000000..5009deb382 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_esc.txt @@ -0,0 +1 @@ +invalid-character-esc:5:4:5:5::"Invalid unescaped character esc, use ""\x1B"" instead.":HIGH diff --git a/tests/functional/b/bad_char/bad_char_sub.py b/tests/functional/b/bad_char/bad_char_sub.py new file mode 100644 index 0000000000..57b8b5081c --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_sub.py @@ -0,0 +1,6 @@ +# pylint: disable=trailing-whitespace, missing-module-docstring, trailing-newlines, missing-final-newline + +# +2: [invalid-character-sub] +SUB = ''' +--><--- +''' # bad char sub should be only used as \x1A diff --git a/tests/functional/b/bad_char/bad_char_sub.txt b/tests/functional/b/bad_char/bad_char_sub.txt new file mode 100644 index 0000000000..51ee45e16c --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_sub.txt @@ -0,0 +1 @@ +invalid-character-sub:5:4:5:5::"Invalid unescaped character sub, use ""\x1A"" instead.":HIGH diff --git a/tests/functional/b/bad_char/bad_char_zero_width_space.py b/tests/functional/b/bad_char/bad_char_zero_width_space.py new file mode 100644 index 0000000000..b9f0df0311 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_zero_width_space.py @@ -0,0 +1,6 @@ +# pylint: disable=trailing-whitespace, missing-module-docstring, trailing-newlines, missing-final-newline + +# +2: [invalid-character-zero-width-space] +ZERO_WIDTH_SPACE = ''' +-->​<--- +''' # bad char zero-width-space should be only used as \u200B diff --git a/tests/functional/b/bad_char/bad_char_zero_width_space.txt b/tests/functional/b/bad_char/bad_char_zero_width_space.txt new file mode 100644 index 0000000000..cab9980e75 --- /dev/null +++ b/tests/functional/b/bad_char/bad_char_zero_width_space.txt @@ -0,0 +1 @@ +invalid-character-zero-width-space:5:4:5:5::"Invalid unescaped character zero-width-space, use ""\u200B"" instead.":HIGH diff --git a/tests/functional/e/.#emacs_file_lock_redefined_conf.rc b/tests/functional/e/.#emacs_file_lock_redefined_conf.rc index 76cd083fd3..f6cf1bf873 100644 --- a/tests/functional/e/.#emacs_file_lock_redefined_conf.rc +++ b/tests/functional/e/.#emacs_file_lock_redefined_conf.rc @@ -1,2 +1,3 @@ [MASTER] ignore-patterns="" +disable=non-ascii-name diff --git a/tests/functional/i/implicit/implicit_str_concat_latin1.py b/tests/functional/i/implicit/implicit_str_concat_latin1.py index cf8c3e6ab8..4c9a656cf4 100644 --- a/tests/functional/i/implicit/implicit_str_concat_latin1.py +++ b/tests/functional/i/implicit/implicit_str_concat_latin1.py @@ -1,4 +1,5 @@ # coding: latin_1 +# -1: [bad-file-encoding] #pylint: disable=invalid-name,missing-docstring TOTO = ('Caf', 'Caf', 'Caf') diff --git a/tests/functional/i/implicit/implicit_str_concat_latin1.txt b/tests/functional/i/implicit/implicit_str_concat_latin1.txt new file mode 100644 index 0000000000..2461245388 --- /dev/null +++ b/tests/functional/i/implicit/implicit_str_concat_latin1.txt @@ -0,0 +1 @@ +bad-file-encoding:1:0:1:None::PEP8 recommends UTF-8 as encoding for Python files:HIGH diff --git a/tests/functional/n/non/non_ascii_name.py b/tests/functional/n/non/non_ascii_name.py index 276a23f753..c8b08e6578 100644 --- a/tests/functional/n/non/non_ascii_name.py +++ b/tests/functional/n/non/non_ascii_name.py @@ -1,6 +1,6 @@ """ Tests for non-ascii-name checker. """ -áéíóú = 4444 # [non-ascii-name] +áéíóú = 4444 # [non-ascii-name] -def úóíéá(): # [non-ascii-name] +def úóíéá(): # [non-ascii-name] """yo""" diff --git a/tests/functional/u/unicode/unicode_bidi_commenting_out.py b/tests/functional/u/unicode/unicode_bidi_commenting_out.py new file mode 100644 index 0000000000..f5a1275fd9 --- /dev/null +++ b/tests/functional/u/unicode/unicode_bidi_commenting_out.py @@ -0,0 +1,12 @@ +""" +Example #1 of trojan unicode, see https://trojansource.codes/ +Taken from https://github.com/nickboucher/trojan-source/tree/main/Python +""" + + +def a_function(): + """A simple function""" + access_level = "user" + # +1: [bidirectional-unicode] + if access_level != "none‮⁦": # Check if admin ⁩⁦' and access_level != 'user + print("You are an admin.") diff --git a/tests/functional/u/unicode/unicode_bidi_commenting_out.txt b/tests/functional/u/unicode/unicode_bidi_commenting_out.txt new file mode 100644 index 0000000000..7c1d4f4289 --- /dev/null +++ b/tests/functional/u/unicode/unicode_bidi_commenting_out.txt @@ -0,0 +1 @@ +bidirectional-unicode:11:0:11:80::Contains control characters that can permit obfuscated code executed differently than displayed:HIGH diff --git a/tests/functional/u/unicode/unicode_bidi_early_return.py b/tests/functional/u/unicode/unicode_bidi_early_return.py new file mode 100644 index 0000000000..1de7a50406 --- /dev/null +++ b/tests/functional/u/unicode/unicode_bidi_early_return.py @@ -0,0 +1,19 @@ +""" +Example #2 of trojan unicode, see https://trojansource.codes/ +Taken from https://github.com/nickboucher/trojan-source/tree/main/Python +""" +# pylint: disable=unreachable + +bank = {"alice": 100} + +# +4: [bidirectional-unicode] + + +def subtract_funds(account: str, amount: int): + """Subtract funds from bank account then ⁧""" + return + bank[account] -= amount + return + + +subtract_funds("alice", 50) diff --git a/tests/functional/u/unicode/unicode_bidi_early_return.txt b/tests/functional/u/unicode/unicode_bidi_early_return.txt new file mode 100644 index 0000000000..576edc887f --- /dev/null +++ b/tests/functional/u/unicode/unicode_bidi_early_return.txt @@ -0,0 +1 @@ +bidirectional-unicode:13:0:13:49::Contains control characters that can permit obfuscated code executed differently than displayed:HIGH diff --git a/tests/functional/u/unicode/unicode_bidi_pep672.py b/tests/functional/u/unicode/unicode_bidi_pep672.py new file mode 100644 index 0000000000..710167c093 --- /dev/null +++ b/tests/functional/u/unicode/unicode_bidi_pep672.py @@ -0,0 +1,8 @@ +""" +Example of trojan unicode, see https://trojansource.codes/ +This example was taken from PEP672 +""" +# pylint: disable=invalid-name + +# +1: [bidirectional-unicode] +example = "x‏" * 100 # "‏x" is assigned diff --git a/tests/functional/u/unicode/unicode_bidi_pep672.txt b/tests/functional/u/unicode/unicode_bidi_pep672.txt new file mode 100644 index 0000000000..c4d3dcf5c9 --- /dev/null +++ b/tests/functional/u/unicode/unicode_bidi_pep672.txt @@ -0,0 +1 @@ +bidirectional-unicode:8:0:8:43::Contains control characters that can permit obfuscated code executed differently than displayed:HIGH diff --git a/tests/regrtest_data/unicode/invisible_function.txt b/tests/regrtest_data/unicode/invisible_function.txt new file mode 100644 index 0000000000..ebec0247ec --- /dev/null +++ b/tests/regrtest_data/unicode/invisible_function.txt @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +def is_admin(): + return True + +def is_​admin(): + return False + +def main(): + if is_​admin(): + print("You are an admin.") + + +if __name__ == '__main__': + main() diff --git a/tests/regrtest_data/unicode/pep_bidirectional_utf_16_bom.txt b/tests/regrtest_data/unicode/pep_bidirectional_utf_16_bom.txt new file mode 100644 index 0000000000000000000000000000000000000000..67cce9b5272d4e839606636410f3cb3ad4e749f8 GIT binary patch literal 215 zcmZ9GI}XAy5JX>{hAUX60SZJKiby#N;^Ra}Bp?z{xIFLz4MMB6XFapyc|E5D?i^_- zL>-CVyYl5EPPFVfb8v2{DY?m4Y{*%wE6BKT{jfbVqHNNoSMjU-pLTjSEZu#$GuNfl lt+XQplMqDS_ueJQp^zCdwvVvWj12ySV+&c5Z&$*yu6p)tHfq hRfwXkS+O^QQ7&~xQGT)p|D3mu)=}JhU-Hg^;tMq{AJ6~* literal 0 HcmV?d00001 diff --git a/tests/regrtest_data/unicode/pep_bidirectional_utf_32_bom.txt b/tests/regrtest_data/unicode/pep_bidirectional_utf_32_bom.txt new file mode 100644 index 0000000000000000000000000000000000000000..afa27e9ef140d7e4251c1f1a6d63f74c25271586 GIT binary patch literal 429 zcmajbISK+m3_#J;HeMlUqZZ=Q37$pV5!`S?FR%U&Go@HOGFv8@`Me|A5s`RchXW=^ zT(QOti48onhwD0Gi4#_E-?c5KxWjyNXBcCIgmoG?>a=E{I|Ct z*FJq?AJ%=rI$hX{brSY&bnl5K6UT(;l{h30iY#mOKkM9Rp84KW@9?d)*1U|JyYAol LhrZiCwXen(H;N)O literal 0 HcmV?d00001 diff --git a/tests/regrtest_data/unicode/pep_bidirectional_utf_32_le_no_bom.txt b/tests/regrtest_data/unicode/pep_bidirectional_utf_32_le_no_bom.txt new file mode 100644 index 0000000000000000000000000000000000000000..dab9046fa48e13072ac208aa02617a11fda45159 GIT binary patch literal 421 zcmaLTISK+n5J1t6C-Dk}lc<5XWWt<9#0|j>H}v-Y4_%=KkLsfdQ2P>%BaREaZ*KHYirE8(LD3LC-3l`S!-3t&RzH4{6F99 If7(~`1w~#T$^ZZW literal 0 HcmV?d00001 diff --git a/tests/test_self.py b/tests/test_self.py index 99bf56280d..444a5076b9 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1203,7 +1203,7 @@ def test_fail_on_info_only_exit_code(self, args, expected): ), ( "colorized", - "tests/regrtest_data/unused_variable.py:4:4: W0612: Unused variable 'variable' (unused-variable)", + "tests/regrtest_data/unused_variable.py:4:4: W0612: \x1B[35mUnused variable 'variable'\x1B[0m (\x1B[35munused-variable\x1B[0m)", ), ("json", '"message": "Unused variable \'variable\'",'), ], From e75e37a2908ef1c003ea5816c1e234523bdd38be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 13 Jan 2022 15:47:52 +0100 Subject: [PATCH 147/357] Remove ``lru_cache`` from ``get_active_msgids`` (#5672) Ref #5670 --- pylint/message/message_id_store.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pylint/message/message_id_store.py b/pylint/message/message_id_store.py index c4d1f4e8f7..e585be5a06 100644 --- a/pylint/message/message_id_store.py +++ b/pylint/message/message_id_store.py @@ -1,6 +1,5 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -import functools from typing import Dict, List, NoReturn, Optional, Tuple from pylint.exceptions import InvalidMessageError, UnknownMessageError @@ -14,6 +13,7 @@ def __init__(self) -> None: self.__msgid_to_symbol: Dict[str, str] = {} self.__symbol_to_msgid: Dict[str, str] = {} self.__old_names: Dict[str, List[str]] = {} + self.__active_msgids: Dict[str, List[str]] = {} def __len__(self) -> int: return len(self.__msgid_to_symbol) @@ -104,14 +104,17 @@ def _raise_duplicate_msgid(symbol: str, msgid: str, other_msgid: str) -> NoRetur ) raise InvalidMessageError(error_message) - @functools.lru_cache() def get_active_msgids(self, msgid_or_symbol: str) -> List[str]: """Return msgids but the input can be a symbol. - The cache has no limit as its size will likely stay minimal. For each message we store - about 1000 characters, so even if we would have 1000 messages the cache would only - take up ~= 1 Mb. + self.__active_msgids is used to implement a primitive cache for this function. """ + try: + return self.__active_msgids[msgid_or_symbol] + except KeyError: + pass + + # If we don't have a cached value yet we compute it msgid: Optional[str] if msgid_or_symbol[1:].isdigit(): # Only msgid can have a digit as second letter @@ -123,4 +126,8 @@ def get_active_msgids(self, msgid_or_symbol: str) -> List[str]: if not msgid or not symbol: error_msg = f"No such message id or symbol '{msgid_or_symbol}'." raise UnknownMessageError(error_msg) - return self.__old_names.get(msgid, [msgid]) + ids = self.__old_names.get(msgid, [msgid]) + + # Add to cache + self.__active_msgids[msgid_or_symbol] = ids + return ids From faf0c849fd3c8da5ed8fd46e80a024ce4b668073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 13 Jan 2022 20:38:41 +0100 Subject: [PATCH 148/357] Fix crash on properties & inherited methods in ``implicit-booleaness`` (#5652) --- ChangeLog | 5 +++ doc/whatsnew/2.13.rst | 5 +++ .../implicit_booleaness_checker.py | 24 +++++----- tests/checkers/unittest_refactoring.py | 44 ------------------- .../use_implicit_booleaness_not_comparison.py | 43 ++++++++++++++++++ ...use_implicit_booleaness_not_comparison.txt | 2 + 6 files changed, 69 insertions(+), 54 deletions(-) diff --git a/ChangeLog b/ChangeLog index 61fa4b7502..124b53bbab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -79,6 +79,11 @@ Release date: TBA Closes #5483 +* Fixed crash on properties and inherited class methods when comparing them for + equality against an empty dict. + + Closes #5646 + * Fixed a false positive for ``assigning-non-slot`` when the slotted class defined ``__setattr__``. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 881c5c5f93..49b7247d2c 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -79,6 +79,11 @@ Other Changes * When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with the ``dill`` package. The ``dill`` package has therefore been added as a dependency. +* Fixed crash on properties and inherited class methods when comparing them for + equality against an empty dict. + + Closes #5646 + * By default, pylint does no longer take files starting with ``.#`` into account. Those are considered `emacs file locks`_. This behavior can be reverted by redefining the ``ignore-patterns`` option. diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index 6167e3e88f..c4b25eeb7c 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -1,9 +1,9 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -from typing import List +from typing import List, Union import astroid -from astroid import nodes +from astroid import bases, nodes from pylint import checkers, interfaces from pylint.checkers import utils @@ -107,7 +107,7 @@ def visit_call(self, node: nodes.Call) -> None: except astroid.InferenceError: # Probably undefined-variable, abort check return - mother_classes = self.base_classes_of_node(instance) + mother_classes = self.base_names_of_instance(instance) affected_by_pep8 = any( t in mother_classes for t in ("str", "tuple", "list", "set") ) @@ -165,7 +165,7 @@ def _check_use_implicit_booleaness_not_comparison( target_instance = utils.safe_infer(target_node) if target_instance is None: continue - mother_classes = self.base_classes_of_node(target_instance) + mother_classes = self.base_names_of_instance(target_instance) is_base_comprehension_type = any( t in mother_classes for t in ("tuple", "list", "dict", "set") ) @@ -209,9 +209,13 @@ def _check_use_implicit_booleaness_not_comparison( ) @staticmethod - def base_classes_of_node(instance: nodes.ClassDef) -> List[str]: - """Return all the classes names that a ClassDef inherit from including 'object'.""" - try: - return [instance.name] + [x.name for x in instance.ancestors()] - except TypeError: - return [instance.name] + def base_names_of_instance( + node: Union[bases.Uninferable, bases.Instance] + ) -> List[str]: + """Return all names inherited by a class instance or those returned by a function. + + The inherited names include 'object'. + """ + if isinstance(node, bases.Instance): + return [node.name] + [x.name for x in node.ancestors()] + return [] diff --git a/tests/checkers/unittest_refactoring.py b/tests/checkers/unittest_refactoring.py index accc4b0670..a2694200b7 100644 --- a/tests/checkers/unittest_refactoring.py +++ b/tests/checkers/unittest_refactoring.py @@ -5,10 +5,8 @@ import signal from contextlib import contextmanager -import astroid import pytest -from pylint.checkers.refactoring import ImplicitBooleanessChecker from pylint.lint import Run from pylint.reporters.text import TextReporter @@ -28,48 +26,6 @@ def _handle(_signum, _frame): signal.signal(signal.SIGALRM, signal.SIG_DFL) -def test_class_tree_detection() -> None: - module = astroid.parse( - """ -class ClassWithBool(list): - def __bool__(self): - return True - -class ClassWithoutBool(dict): - pass - -class ChildClassWithBool(ClassWithBool): - pass - -class ChildClassWithoutBool(ClassWithoutBool): - pass -""" - ) - with_bool, without_bool, child_with_bool, child_without_bool = module.body - assert ImplicitBooleanessChecker().base_classes_of_node(with_bool) == [ - "ClassWithBool", - "list", - "object", - ] - assert ImplicitBooleanessChecker().base_classes_of_node(without_bool) == [ - "ClassWithoutBool", - "dict", - "object", - ] - assert ImplicitBooleanessChecker().base_classes_of_node(child_with_bool) == [ - "ChildClassWithBool", - "ClassWithBool", - "list", - "object", - ] - assert ImplicitBooleanessChecker().base_classes_of_node(child_without_bool) == [ - "ChildClassWithoutBool", - "ClassWithoutBool", - "dict", - "object", - ] - - @pytest.mark.skipif(not hasattr(signal, "setitimer"), reason="Assumes POSIX signals") def test_process_tokens() -> None: with timeout(8.0): diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison.py b/tests/functional/u/use/use_implicit_booleaness_not_comparison.py index ce64fd1ed3..681ee0607a 100644 --- a/tests/functional/u/use/use_implicit_booleaness_not_comparison.py +++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison.py @@ -194,3 +194,46 @@ def test_function(): long_test = {} if long_test == { }: # [use-implicit-booleaness-not-comparison] pass + + +# Check for properties and uninferable class methods +# See https://github.com/PyCQA/pylint/issues/5646 +from xyz import AnotherClassWithProperty + + +class ParentWithProperty: + + @classmethod + @property + def parent_function(cls): + return {} + + +class MyClassWithProxy(ParentWithProperty): + + attribute = True + + @property + @classmethod + def my_property(cls): + return {} + + @property + @classmethod + def my_difficult_property(cls): + if cls.attribute: + return {} + return MyClassWithProxy() + + + +def test_func(): + """Some assertions against empty dicts.""" + my_class = MyClassWithProxy() + assert my_class.parent_function == {} # [use-implicit-booleaness-not-comparison] + assert my_class.my_property == {} # [use-implicit-booleaness-not-comparison] + + # If the return value is not always implicit boolean, don't raise + assert my_class.my_difficult_property == {} + # Uninferable does not raise + assert AnotherClassWithProperty().my_property == {} diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt b/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt index ecd9189d65..d316d5acd3 100644 --- a/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt +++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt @@ -28,3 +28,5 @@ use-implicit-booleaness-not-comparison:160:3:160:20::'numpy_array >= ()' can be use-implicit-booleaness-not-comparison:185:3:185:13::'data == {}' can be simplified to 'not data' as an empty sequence is falsey:UNDEFINED use-implicit-booleaness-not-comparison:187:3:187:13::'data != {}' can be simplified to 'data' as an empty sequence is falsey:UNDEFINED use-implicit-booleaness-not-comparison:195:3:195:26::'long_test == {}' can be simplified to 'not long_test' as an empty sequence is falsey:UNDEFINED +use-implicit-booleaness-not-comparison:233:11:233:41:test_func:'my_class.parent_function == {}' can be simplified to 'not my_class.parent_function' as an empty sequence is falsey:UNDEFINED +use-implicit-booleaness-not-comparison:234:11:234:37:test_func:'my_class.my_property == {}' can be simplified to 'not my_class.my_property' as an empty sequence is falsey:UNDEFINED From 4a6b6bf33053c5887274da14e00dd22a7dcb4284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 14 Jan 2022 09:37:48 +0100 Subject: [PATCH 149/357] Add ``lru-cache-decorating-method`` checker (#5674) Co-authored-by: Pierre Sassoulas --- ChangeLog | 6 ++ doc/whatsnew/2.13.rst | 6 ++ pylint/checkers/stdlib.py | 43 ++++++++++++- pylint/message/message_definition_store.py | 6 +- .../l/lru_cache_decorating_method.py | 63 +++++++++++++++++++ .../l/lru_cache_decorating_method.txt | 10 +++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 tests/functional/l/lru_cache_decorating_method.py create mode 100644 tests/functional/l/lru_cache_decorating_method.txt diff --git a/ChangeLog b/ChangeLog index 124b53bbab..ef6aa6bdcc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,12 @@ Release date: TBA Closes #5588 +* Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache`` + on class methods. This is unrecommended as it creates memory leaks by never letting the instance + getting garbage collected. + + Closes #5670 + * Rewrote checker for ``non-ascii-name``. It now ensures __all__ Python names are ASCII and also properly checks the names of imports (``non-ascii-module-import``) as diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 49b7247d2c..3d6a1b263e 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -46,6 +46,12 @@ New checkers enter on a non specialized keyboard. See `Confusable Characters in PEP 672 `_ +* Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache`` + on class methods. This is unrecommended as it creates memory leaks by never letting the instance + getting garbage collected. + + Closes #5670 + Removed checkers ================ diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 638b7f3c93..a203f2d07c 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -39,11 +39,12 @@ import sys from collections.abc import Iterable -from typing import TYPE_CHECKING, Any, Dict, Optional, Set +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set import astroid from astroid import nodes +from pylint import interfaces from pylint.checkers import BaseChecker, DeprecatedMixin, utils from pylint.interfaces import IAstroidChecker @@ -61,6 +62,12 @@ SUBPROCESS_RUN = "subprocess.run" OPEN_MODULE = {"_io", "pathlib"} DEBUG_BREAKPOINTS = ("builtins.breakpoint", "sys.breakpointhook", "pdb.set_trace") +LRU_CACHE = { + "functools.lru_cache", # Inferred for @lru_cache + "functools._lru_cache_wrapper.wrapper", # Inferred for @lru_cache() on >= Python 3.8 + "functools.lru_cache.decorating_function", # Inferred for @lru_cache() on <= Python 3.7 +} +NON_INSTANCE_METHODS = {"builtins.staticmethod", "builtins.classmethod"} DEPRECATED_MODULES = { @@ -446,6 +453,14 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "Calls to breakpoint(), sys.breakpointhook() and pdb.set_trace() should be removed " "from code that is not actively being debugged.", ), + "W1516": ( + "lru_cache shouldn't be used on a method as it creates memory leaks", + "lru-cache-decorating-method", + "By decorating a method with lru_cache the 'self' argument will be linked to " + "to the lru_cache function and therefore never garbage collected. Unless your instance " + "will never need to be garbage collected (singleton) it is recommended to refactor " + "code to avoid this pattern.", + ), } def __init__( @@ -571,6 +586,32 @@ def visit_boolop(self, node: nodes.BoolOp) -> None: for value in node.values: self._check_datetime(value) + @utils.check_messages("lru-cache-decorating-method") + def visit_functiondef(self, node: nodes.FunctionDef) -> None: + if node.decorators and isinstance(node.parent, nodes.ClassDef): + self._check_lru_cache_decorators(node.decorators) + + def _check_lru_cache_decorators(self, decorators: nodes.Decorators) -> None: + """Check if instance methods are decorated with functools.lru_cache.""" + lru_cache_nodes: List[nodes.NodeNG] = [] + for d_node in decorators.nodes: + try: + for infered_node in d_node.infer(): + q_name = infered_node.qname() + if q_name in NON_INSTANCE_METHODS: + return + if q_name in LRU_CACHE: + lru_cache_nodes.append(d_node) + break + except astroid.InferenceError: + pass + for lru_cache_node in lru_cache_nodes: + self.add_message( + "lru-cache-decorating-method", + node=lru_cache_node, + confidence=interfaces.INFERENCE, + ) + def _check_redundant_assert(self, node, infer): if ( isinstance(infer, astroid.BoundMethod) diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py index 766cdd4465..150c35fe83 100644 --- a/pylint/message/message_definition_store.py +++ b/pylint/message/message_definition_store.py @@ -47,7 +47,11 @@ def register_message(self, message: MessageDefinition) -> None: self._messages_definitions[message.msgid] = message self._msgs_by_category[message.msgid[0]].append(message.msgid) - @functools.lru_cache() + # We disable the message here because MessageDefinitionStore is only + # initialized once and due to the size of the class does not run the + # risk of creating a large memory leak. + # See discussion in: https://github.com/PyCQA/pylint/pull/5673 + @functools.lru_cache() # pylint: disable=lru-cache-decorating-method def get_message_definitions(self, msgid_or_symbol: str) -> List[MessageDefinition]: """Returns the Message definition for either a numeric or symbolic id. diff --git a/tests/functional/l/lru_cache_decorating_method.py b/tests/functional/l/lru_cache_decorating_method.py new file mode 100644 index 0000000000..ea9e994fbd --- /dev/null +++ b/tests/functional/l/lru_cache_decorating_method.py @@ -0,0 +1,63 @@ +"""Tests for lru-cache-decorating-method""" +# pylint: disable=no-self-use, missing-function-docstring, reimported, too-few-public-methods +# pylint: disable=missing-class-docstring, function-redefined + +import functools +import functools as aliased_functools +from functools import lru_cache +from functools import lru_cache as aliased_cache + + +@lru_cache +def my_func(param): + return param + 1 + + +class MyClassWithMethods: + @lru_cache + @staticmethod + def my_func(param): + return param + 1 + + @lru_cache + @classmethod + def my_func(cls, param): + return param + 1 + + @lru_cache # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @functools.lru_cache # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @aliased_functools.lru_cache # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @aliased_cache # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @lru_cache() # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @functools.lru_cache() # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @aliased_functools.lru_cache() # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @aliased_cache() # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + # Check double decorating to check robustness of checker itself + @aliased_cache() # [lru-cache-decorating-method] + @aliased_cache() # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 diff --git a/tests/functional/l/lru_cache_decorating_method.txt b/tests/functional/l/lru_cache_decorating_method.txt new file mode 100644 index 0000000000..8186d319f5 --- /dev/null +++ b/tests/functional/l/lru_cache_decorating_method.txt @@ -0,0 +1,10 @@ +lru-cache-decorating-method:27:5:27:14:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:31:5:31:24:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:35:5:35:32:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:39:5:39:18:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:43:5:43:16:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:47:5:47:26:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:51:5:51:34:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:55:5:55:20:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:60:5:60:20:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:61:5:61:20:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE From 383fb559682a3470d147cf90eb398cabcede6993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:41:08 +0100 Subject: [PATCH 150/357] Add typing to `ScopeConsumer` (#5680) --- pylint/checkers/variables.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index bc291c1b9e..1d7b17c1b0 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -63,7 +63,18 @@ import sys from enum import Enum from functools import lru_cache -from typing import TYPE_CHECKING, Any, DefaultDict, List, Optional, Set, Tuple, Union +from typing import ( + TYPE_CHECKING, + Any, + DefaultDict, + Dict, + List, + NamedTuple, + Optional, + Set, + Tuple, + Union, +) import astroid from astroid import nodes @@ -536,9 +547,13 @@ def _has_locals_call_after_node(stmt, scope): } -ScopeConsumer = collections.namedtuple( - "ScopeConsumer", "to_consume consumed consumed_uncertain scope_type" -) +class ScopeConsumer(NamedTuple): + """Store nodes and their consumption states.""" + + to_consume: Dict[str, List[nodes.NodeNG]] + consumed: Dict[str, List[nodes.NodeNG]] + consumed_uncertain: DefaultDict[str, List[nodes.NodeNG]] + scope_type: str class NamesConsumer: From 57a06023b2821037323069b451d0c59a1c087575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 14 Jan 2022 20:42:38 +0100 Subject: [PATCH 151/357] Add typing to ``get_next_to_consume`` and ``_check_consumer`` (#5681) --- pylint/checkers/variables.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1d7b17c1b0..edbe5ba299 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -619,7 +619,7 @@ def mark_as_consumed(self, name, consumed_nodes): else: del self.to_consume[name] - def get_next_to_consume(self, node): + def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]: """Return a list of the nodes that define `node` from this scope. If it is uncertain whether a node will be consumed, such as for statements in except blocks, add it to self.consumed_uncertain instead of returning it. @@ -1236,18 +1236,14 @@ def _undefined_and_used_before_checker( action, found_nodes = self._check_consumer( node, stmt, frame, current_consumer, i, base_scope_type ) - if action is VariableVisitConsumerAction.CONTINUE: continue if action is VariableVisitConsumerAction.CONSUME: - # pylint: disable-next=fixme - # TODO: remove assert after _check_consumer return value better typed - assert found_nodes is not None, "Cannot consume an empty list of nodes." # Any nodes added to consumed_uncertain by get_next_to_consume() # should be added back so that they are marked as used. # They will have already had a chance to emit used-before-assignment. # We check here instead of before every single return in _check_consumer() - found_nodes += current_consumer.consumed_uncertain[node.name] + found_nodes += current_consumer.consumed_uncertain[node.name] # type: ignore[operator] current_consumer.mark_as_consumed(node.name, found_nodes) if action in { VariableVisitConsumerAction.RETURN, @@ -1321,7 +1317,16 @@ def _check_consumer( current_consumer: NamesConsumer, consumer_level: int, base_scope_type: Any, - ) -> Tuple[VariableVisitConsumerAction, Optional[Any]]: + ) -> Union[ + Tuple[ + Union[ + Literal[VariableVisitConsumerAction.CONTINUE], + Literal[VariableVisitConsumerAction.RETURN], + ], + None, + ], + Tuple[Literal[VariableVisitConsumerAction.CONSUME], List[nodes.NodeNG]], + ]: """Checks a consumer for conditions that should trigger messages""" # If the name has already been consumed, only check it's not a loop # variable used outside the loop. @@ -1354,7 +1359,7 @@ def _check_consumer( # return a CONSUME action so that _undefined_and_used_before_checker() # will mark them as used return (VariableVisitConsumerAction.CONSUME, found_nodes) - return (VariableVisitConsumerAction.RETURN, found_nodes) + return (VariableVisitConsumerAction.RETURN, None) self._check_late_binding_closure(node) From 51cee6981b20ca546bfea73b0d8dc330531433f2 Mon Sep 17 00:00:00 2001 From: Arianna Y Date: Fri, 14 Jan 2022 16:43:29 -0600 Subject: [PATCH 152/357] Fix unprotected accesses to parent.name and add tests (#5675) --- ChangeLog | 6 +++++ doc/whatsnew/2.13.rst | 5 ++++ pylint/checkers/classes/class_checker.py | 10 +++---- pylint/checkers/typecheck.py | 2 +- tests/functional/a/arguments_renamed.py | 26 +++++++++++++++++++ tests/functional/a/arguments_renamed.txt | 2 ++ .../o/overridden_final_method_py38.py | 15 +++++++++++ .../o/overridden_final_method_py38.txt | 1 + 8 files changed, 61 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index ef6aa6bdcc..2fc2242de7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,12 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' + +* Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were + defined outside the top level of a class. + + Closes #5648 + * Added several checkers to deal with unicode security issues (see `Trojan Sources `_ and `PEP 672 `_ for details) that also diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 3d6a1b263e..dc908d7e55 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -77,6 +77,11 @@ Extensions Other Changes ============= +* Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were + defined outside the top level of a class. + + Closes #5648 + * Fixed false positive ``consider-using-dict-comprehension`` when creating a dict using a list of tuples where key AND value vary depending on the same condition. diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index f64e7addc5..a3517ca348 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1325,7 +1325,7 @@ def _check_invalid_overridden_method(self, function_node, parent_function_node): ) and self._py38_plus: self.add_message( "overridden-final-method", - args=(function_node.name, parent_function_node.parent.name), + args=(function_node.name, parent_function_node.parent.frame().name), node=function_node, ) @@ -1997,24 +1997,24 @@ def _check_signature(self, method1, refmethod, class_type, cls): error_type = "arguments-differ" msg_args = ( msg - + f"was {total_args_refmethod} in '{refmethod.parent.name}.{refmethod.name}' and " + + f"was {total_args_refmethod} in '{refmethod.parent.frame().name}.{refmethod.name}' and " f"is now {total_args_method1} in", class_type, - f"{method1.parent.name}.{method1.name}", + f"{method1.parent.frame().name}.{method1.name}", ) elif "renamed" in msg: error_type = "arguments-renamed" msg_args = ( msg, class_type, - f"{method1.parent.name}.{method1.name}", + f"{method1.parent.frame().name}.{method1.name}", ) else: error_type = "arguments-differ" msg_args = ( msg, class_type, - f"{method1.parent.name}.{method1.name}", + f"{method1.parent.frame().name}.{method1.name}", ) self.add_message(error_type, args=msg_args, node=method1) elif ( diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index c0a7725f93..bb9c3de52b 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1585,7 +1585,7 @@ def _check_invalid_sequence_index(self, subscript: nodes.Subscript): not isinstance(itemmethod, nodes.FunctionDef) or itemmethod.root().name != "builtins" or not itemmethod.parent - or itemmethod.parent.name not in SEQUENCE_TYPES + or itemmethod.parent.frame().name not in SEQUENCE_TYPES ): return None diff --git a/tests/functional/a/arguments_renamed.py b/tests/functional/a/arguments_renamed.py index 22e7519ac6..e24b670fda 100644 --- a/tests/functional/a/arguments_renamed.py +++ b/tests/functional/a/arguments_renamed.py @@ -72,3 +72,29 @@ def test2(self, _arg, ignored_barg): # no error here def test3(self, dummy_param, arg2): # no error here print(f"arguments: {arg2}") + +# Check for crash on method definitions not at top level of class +# https://github.com/PyCQA/pylint/issues/5648 +class FruitConditional: + + define_eat = True + + def brew(self, fruit_name: str): + print(f"Brewing a fruit named {fruit_name}") + + if define_eat: + def eat_with_condiment(self, fruit_name:str, condiment: Condiment): + print(f"Eating a fruit named {fruit_name} with {condiment}") + +class FruitOverrideConditional(FruitConditional): + + fruit = "orange" + override_condiment = True + + if fruit == "orange": + def brew(self, orange_name: str): # [arguments-renamed] + print(f"Brewing an orange named {orange_name}") + + if override_condiment: + def eat_with_condiment(self, fruit_name: str, condiment: Condiment, error: str): # [arguments-differ] + print(f"Eating a fruit named {fruit_name} with {condiment}") diff --git a/tests/functional/a/arguments_renamed.txt b/tests/functional/a/arguments_renamed.txt index 1656c5f9eb..366aa47300 100644 --- a/tests/functional/a/arguments_renamed.txt +++ b/tests/functional/a/arguments_renamed.txt @@ -7,3 +7,5 @@ arguments-renamed:48:4:49:22:Child2.test:Parameter 'arg' has been renamed to 'va arguments-differ:51:4:52:58:Child2.kwargs_test:Number of parameters was 4 in 'Parent.kwargs_test' and is now 3 in overridden 'Child2.kwargs_test' method:UNDEFINED arguments-renamed:51:4:52:58:Child2.kwargs_test:Parameter 'var2' has been renamed to 'kw2' in overridden 'Child2.kwargs_test' method:UNDEFINED arguments-renamed:67:4:68:56:ChildDefaults.test1:Parameter 'barg' has been renamed to 'param2' in overridden 'ChildDefaults.test1' method:UNDEFINED +arguments-renamed:95:8:96:59:FruitOverrideConditional.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'FruitOverrideConditional.brew' method:UNDEFINED +arguments-differ:99:12:100:76:FruitOverrideConditional.eat_with_condiment:Number of parameters was 3 in 'FruitConditional.eat_with_condiment' and is now 4 in overridden 'FruitOverrideConditional.eat_with_condiment' method:UNDEFINED diff --git a/tests/functional/o/overridden_final_method_py38.py b/tests/functional/o/overridden_final_method_py38.py index d7b27b6e9a..d74a34051a 100644 --- a/tests/functional/o/overridden_final_method_py38.py +++ b/tests/functional/o/overridden_final_method_py38.py @@ -14,3 +14,18 @@ def my_method(self): class Subclass(Base): def my_method(self): # [overridden-final-method] pass + +# Check for crash on method definitions not at top level of class +# https://github.com/PyCQA/pylint/issues/5648 +class BaseConditional: + + create_final_method = True + if create_final_method: + @final + def my_method(self): + pass + +class Subclass2(BaseConditional): + + def my_method(self): # [overridden-final-method] + pass diff --git a/tests/functional/o/overridden_final_method_py38.txt b/tests/functional/o/overridden_final_method_py38.txt index 28ca17c00f..8f7af49e2a 100644 --- a/tests/functional/o/overridden_final_method_py38.txt +++ b/tests/functional/o/overridden_final_method_py38.txt @@ -1 +1,2 @@ overridden-final-method:15:4:16:12:Subclass.my_method:Method 'my_method' overrides a method decorated with typing.final which is defined in class 'Base':UNDEFINED +overridden-final-method:30:4:31:12:Subclass2.my_method:Method 'my_method' overrides a method decorated with typing.final which is defined in class 'BaseConditional':UNDEFINED From 3b68aa7243986ae0b2e81e32ff114e0edae0d0e0 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Jan 2022 03:04:58 -0500 Subject: [PATCH 153/357] Fix false negative for `used-before-assignment` when an Except intervenes between Try/Finally (#5583) Co-authored-by: Pierre Sassoulas --- ChangeLog | 4 + doc/whatsnew/2.13.rst | 4 + pylint/checkers/variables.py | 77 +++++++++--- .../c/consider/consider_using_with_open.py | 3 +- .../c/consider/consider_using_with_open.txt | 1 + .../u/use/used_before_assignment_issue85.py | 117 ++++++++++++++++++ .../u/use/used_before_assignment_issue85.txt | 9 ++ 7 files changed, 198 insertions(+), 17 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2fc2242de7..bbc1d7d338 100644 --- a/ChangeLog +++ b/ChangeLog @@ -87,6 +87,10 @@ Release date: TBA Closes #4045 +* Fixed false negative for ``used-before-assignment`` in finally blocks + if an except handler did not define the assignment that might have failed + in the try block. + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index dc908d7e55..5fce171e1f 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -150,6 +150,10 @@ Other Changes Closes #4045 +* Fixed false negative for ``used-before-assignment`` in finally blocks + if an except handler did not define the assignment that might have failed + in the try block. + * Fix a false positive for ``assigning-non-slot`` when the slotted class defined ``__setattr__``. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index edbe5ba299..1baad8b6f4 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -667,23 +667,15 @@ def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]: # If this node is in a Finally block of a Try/Finally, # filter out assignments in the try portion, assuming they may fail - if ( - found_nodes - and isinstance(node_statement.parent, nodes.TryFinally) - and node_statement in node_statement.parent.finalbody - ): - filtered_nodes = [ - n - for n in found_nodes - if not ( - n.statement(future=True).parent is node_statement.parent - and n.statement(future=True) in n.statement(future=True).parent.body + if found_nodes: + uncertain_nodes = ( + self._uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( + found_nodes, node_statement ) - ] - filtered_nodes_set = set(filtered_nodes) - difference = [n for n in found_nodes if n not in filtered_nodes_set] - self.consumed_uncertain[node.name] += difference - found_nodes = filtered_nodes + ) + self.consumed_uncertain[node.name] += uncertain_nodes + uncertain_nodes_set = set(uncertain_nodes) + found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set] # If this node is in an ExceptHandler, # filter out assignments in the try portion, assuming they may fail @@ -794,6 +786,59 @@ def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( uncertain_nodes.append(other_node) return uncertain_nodes + @staticmethod + def _uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( + found_nodes: List[nodes.NodeNG], node_statement: nodes.Statement + ) -> List[nodes.NodeNG]: + uncertain_nodes: List[nodes.NodeNG] = [] + ( + closest_try_finally_ancestor, + child_of_closest_try_finally_ancestor, + ) = utils.get_node_first_ancestor_of_type_and_its_child( + node_statement, nodes.TryFinally + ) + if closest_try_finally_ancestor is None: + return uncertain_nodes + if ( + child_of_closest_try_finally_ancestor + not in closest_try_finally_ancestor.finalbody + ): + return uncertain_nodes + for other_node in found_nodes: + other_node_statement = other_node.statement(future=True) + ( + other_node_try_finally_ancestor, + child_of_other_node_try_finally_ancestor, + ) = utils.get_node_first_ancestor_of_type_and_its_child( + other_node_statement, nodes.TryFinally + ) + if other_node_try_finally_ancestor is None: + continue + # other_node needs to descend from the try of a try/finally. + if ( + child_of_other_node_try_finally_ancestor + not in other_node_try_finally_ancestor.body + ): + continue + # If the two try/finally ancestors are not the same, then + # node_statement's closest try/finally ancestor needs to be in + # the final body of other_node's try/finally ancestor, or + # descend from one of the statements in that final body. + if ( + other_node_try_finally_ancestor is not closest_try_finally_ancestor + and not any( + other_node_final_statement is closest_try_finally_ancestor + or other_node_final_statement.parent_of( + closest_try_finally_ancestor + ) + for other_node_final_statement in other_node_try_finally_ancestor.finalbody + ) + ): + continue + # Passed all tests for uncertain execution + uncertain_nodes.append(other_node) + return uncertain_nodes + # pylint: disable=too-many-public-methods class VariablesChecker(BaseChecker): diff --git a/tests/functional/c/consider/consider_using_with_open.py b/tests/functional/c/consider/consider_using_with_open.py index 7c584d8f53..d35b58eb1c 100644 --- a/tests/functional/c/consider/consider_using_with_open.py +++ b/tests/functional/c/consider/consider_using_with_open.py @@ -137,7 +137,8 @@ def test_defined_in_try_and_finally(self): except FileNotFoundError: Path("foo").touch() finally: - file_handle.open("foo", encoding="utf") # must not trigger + # +1: [used-before-assignment] + file_handle.open("foo", encoding="utf") # must not trigger consider-using-with with file_handle: return file_handle.read() diff --git a/tests/functional/c/consider/consider_using_with_open.txt b/tests/functional/c/consider/consider_using_with_open.txt index b52b661af1..5a91c439b6 100644 --- a/tests/functional/c/consider/consider_using_with_open.txt +++ b/tests/functional/c/consider/consider_using_with_open.txt @@ -4,3 +4,4 @@ consider-using-with:46:4:46:33:test_open_outside_assignment:Consider using 'with consider-using-with:47:14:47:43:test_open_outside_assignment:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:52:8:52:37:test_open_inside_with_block:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:120:26:122:13:TestControlFlow.test_triggers_if_reassigned_after_if_else:Consider using 'with' for resource-allocating operations:UNDEFINED +used-before-assignment:141:12:141:23:TestControlFlow.test_defined_in_try_and_finally:Using variable 'file_handle' before assignment:UNDEFINED diff --git a/tests/functional/u/use/used_before_assignment_issue85.py b/tests/functional/u/use/used_before_assignment_issue85.py index 58d8e38d85..367af9dfa0 100644 --- a/tests/functional/u/use/used_before_assignment_issue85.py +++ b/tests/functional/u/use/used_before_assignment_issue85.py @@ -7,3 +7,120 @@ def main(): finally: print(res) # [used-before-assignment] print(res) + + +def try_except_finally(): + """When evaluating finally blocks, assume try statements fail.""" + try: + res = 1 / 0 + res = 42 + except ZeroDivisionError: + print() + finally: + print(res) # [used-before-assignment] + print(res) + + +def try_except_finally_assignment_in_final_block(): + """Assignment of the name in the final block does not warn.""" + try: + res = 1 / 0 + res = 42 + except ZeroDivisionError: + print() + finally: + res = 999 + print(res) + print(res) + + +def try_except_finally_nested_try_finally_in_try(): + """Don't confuse assignments in different finally statements where + one is nested inside a try. + """ + try: + try: + res = 1 / 0 + finally: + print(res) # [used-before-assignment] + print(1 / 0) + except ZeroDivisionError: + print() + finally: + res = 999 # this assignment could be confused for that above + print(res) + print(res) + + +def try_except_finally_nested_in_finally(): + """Until Pylint comes to a consensus on requiring all except handlers to + define a name, raise, or return (https://github.com/PyCQA/pylint/issues/5524), + Pylint assumes statements in try blocks succeed when accessed *after* + except or finally blocks and fail when accessed *in* except or finally + blocks.) + """ + try: + outer_times = 1 + finally: + try: + inner_times = 1 + except TypeError: + pass + finally: + print(outer_times) # [used-before-assignment] + print(inner_times) # see docstring: might emit in a future version + + +def try_except_finally_nested_in_finally_2(): + """Neither name is accessed after a finally block.""" + try: + outer_times = 1 + finally: + try: + inner_times = 1 + except TypeError: + pass + finally: + print(inner_times) # [used-before-assignment] + print(outer_times) # [used-before-assignment] + + +def try_except_finally_nested_in_finally_3(): + """One name is never accessed after a finally block, but just emit + once per name. + """ + try: + outer_times = 1 + finally: + try: + inner_times = 1 + except TypeError: + pass + finally: + print(inner_times) # [used-before-assignment] + print(outer_times) # [used-before-assignment] + print(inner_times) + # used-before-assignment is only raised once per name + print(outer_times) + + +def try_except_finally_nested_in_finally_4(): + """Triple nesting: don't assume direct parentages of outer try/finally + and inner try/finally. + """ + try: + outer_times = 1 + finally: + try: + pass + finally: + try: + inner_times = 1 + except TypeError: + pass + finally: + print(inner_times) # [used-before-assignment] + print(outer_times) # [used-before-assignment] + print(inner_times) + # used-before-assignment is only raised once per name + print(outer_times) diff --git a/tests/functional/u/use/used_before_assignment_issue85.txt b/tests/functional/u/use/used_before_assignment_issue85.txt index ce6e4b9d06..15af569be1 100644 --- a/tests/functional/u/use/used_before_assignment_issue85.txt +++ b/tests/functional/u/use/used_before_assignment_issue85.txt @@ -1 +1,10 @@ used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED +used-before-assignment:20:14:20:17:try_except_finally:Using variable 'res' before assignment:UNDEFINED +used-before-assignment:45:18:45:21:try_except_finally_nested_try_finally_in_try:Using variable 'res' before assignment:UNDEFINED +used-before-assignment:70:18:70:29:try_except_finally_nested_in_finally:Using variable 'outer_times' before assignment:UNDEFINED +used-before-assignment:84:18:84:29:try_except_finally_nested_in_finally_2:Using variable 'inner_times' before assignment:UNDEFINED +used-before-assignment:85:14:85:25:try_except_finally_nested_in_finally_2:Using variable 'outer_times' before assignment:UNDEFINED +used-before-assignment:100:18:100:29:try_except_finally_nested_in_finally_3:Using variable 'inner_times' before assignment:UNDEFINED +used-before-assignment:101:18:101:29:try_except_finally_nested_in_finally_3:Using variable 'outer_times' before assignment:UNDEFINED +used-before-assignment:122:22:122:33:try_except_finally_nested_in_finally_4:Using variable 'inner_times' before assignment:UNDEFINED +used-before-assignment:123:22:123:33:try_except_finally_nested_in_finally_4:Using variable 'outer_times' before assignment:UNDEFINED From 981cf9beb913213d39628bfa9e805748186dc59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 15 Jan 2022 09:50:20 +0100 Subject: [PATCH 154/357] Follow-up to some test changes (#5685) * Add `end_line` attribute to ``test___check_invalid_chars`` * Remove ``unicode_example_dir`` from conftest --- tests/checkers/unittest_unicode/unittest_bad_chars.py | 1 + .../unittest_unicode/unittest_bidirectional_unicode.py | 9 +++++---- .../unittest_unicode/unittest_invalid_encoding.py | 8 ++++---- tests/conftest.py | 5 ----- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/checkers/unittest_unicode/unittest_bad_chars.py b/tests/checkers/unittest_unicode/unittest_bad_chars.py index f764aa0cc4..3605ac06da 100644 --- a/tests/checkers/unittest_unicode/unittest_bad_chars.py +++ b/tests/checkers/unittest_unicode/unittest_bad_chars.py @@ -260,6 +260,7 @@ def test___check_invalid_chars(self, char: str, msg: str, codec: str) -> None: line=55, args=None, confidence=pylint.interfaces.HIGH, + end_line=55, col_offset=5, end_col_offset=6, ) diff --git a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py index 0eb0faf5a2..c76db73ea3 100644 --- a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py +++ b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py @@ -1,5 +1,6 @@ import itertools import unicodedata +from pathlib import Path from typing import cast import astroid @@ -12,22 +13,22 @@ from . import FakeNode +UNICODE_TESTS = Path(__file__).parent.parent.parent / "regrtest_data" / "unicode" + class TestBidirectionalUnicodeChecker(pylint.testutils.CheckerTestCase): CHECKER_CLASS = pylint.checkers.unicode.UnicodeChecker checker: pylint.checkers.unicode.UnicodeChecker - def test_finds_bidirectional_unicode_that_currently_not_parsed( - self, unicode_example_dir - ): + def test_finds_bidirectional_unicode_that_currently_not_parsed(self): """Test an example from https://github.com/nickboucher/trojan-source/tree/main/Python that is currently not working Python but producing a syntax error So we test this to make sure it stays like this """ - test_file = unicode_example_dir / "invisible_function.txt" + test_file = UNICODE_TESTS / "invisible_function.txt" with pytest.raises(astroid.AstroidSyntaxError): astroid.MANAGER.ast_from_string(test_file.read_text("utf-8")) diff --git a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py index 5a6f1547c0..a2e58dc831 100644 --- a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py +++ b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py @@ -13,6 +13,8 @@ from . import CODEC_AND_MSG, FakeNode +UNICODE_TESTS = Path(__file__).parent.parent.parent / "regrtest_data" / "unicode" + class TestInvalidEncoding(pylint.testutils.CheckerTestCase): CHECKER_CLASS = pylint.checkers.unicode.UnicodeChecker @@ -44,10 +46,8 @@ class TestInvalidEncoding(pylint.testutils.CheckerTestCase): ("pep_bidirectional_utf_32_bom.txt", 1), ], ) - def test_invalid_unicode_files( - self, unicode_example_dir: Path, tmp_path: Path, test_file: str, line_no: int - ): - test_file_path = unicode_example_dir / test_file + def test_invalid_unicode_files(self, tmp_path: Path, test_file: str, line_no: int): + test_file_path = UNICODE_TESTS / test_file target = shutil.copy( test_file_path, tmp_path / test_file.replace(".txt", ".py") ) diff --git a/tests/conftest.py b/tests/conftest.py index e004e16c36..bdd3e4516c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -58,11 +58,6 @@ def reporter(): return MinimalTestReporter -@pytest.fixture -def unicode_example_dir(tests_directory: Path) -> Path: - return tests_directory / "regrtest_data" / "unicode" - - def pytest_addoption(parser) -> None: parser.addoption( "--primer-stdlib", From fdd71ee0cad6dc965c4a7bd7bc3d16e2ab564f70 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Jan 2022 12:04:12 -0500 Subject: [PATCH 155/357] Fix typing of get_node_first_ancestor_of_type() and related method (#5686) --- pylint/checkers/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 65c7e3102f..7e08a9048a 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1715,7 +1715,7 @@ def returns_bool(node: nodes.NodeNG) -> bool: def get_node_first_ancestor_of_type( - node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node]]] + node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node], ...]] ) -> Optional[T_Node]: """Return the first parent node that is any of the provided types (or None)""" for ancestor in node.node_ancestors(): @@ -1725,7 +1725,7 @@ def get_node_first_ancestor_of_type( def get_node_first_ancestor_of_type_and_its_child( - node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node]]] + node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node], ...]] ) -> Union[Tuple[None, None], Tuple[T_Node, nodes.NodeNG]]: """Modified version of get_node_first_ancestor_of_type to also return the descendant visited directly before reaching the sought ancestor. Useful From 9e49b30d7d6df718fe08e3503ff6043a3580e20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 16 Jan 2022 20:16:06 +0100 Subject: [PATCH 156/357] Allow dev version of next minor ``astroid`` version (#5689) --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 71f9a1bf9e..1699b1f32d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,10 @@ packages = find: install_requires = dill>=0.2 platformdirs>=2.2.0 - astroid>=2.9.2,<2.10 # (You should also upgrade requirements_test_min.txt) + # 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.9.2,<=2.10.0-dev0 isort>=4.2.5,<6 mccabe>=0.6,<0.7 toml>=0.9.2 From a6a10e50fa63e46bbb649627f3891834458afab9 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 16 Jan 2022 14:16:38 -0500 Subject: [PATCH 157/357] Fix #5568: Allow nested classes as return annotations (#5688) Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 ++++ doc/whatsnew/2.13.rst | 5 ++++ pylint/checkers/variables.py | 24 +++++++++---------- .../u/undefined/undefined_variable.py | 15 ++++++++++++ 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index bbc1d7d338..ca0dc81846 100644 --- a/ChangeLog +++ b/ChangeLog @@ -74,6 +74,11 @@ Release date: TBA Closes #5569 +* Fix false positive for ``undefined-variable`` when ``namedtuple`` class + attributes are used as return annotations. + + Closes #5568 + * Pyreverse - add output in mermaidjs format * ``used-before-assignment`` now considers that assignments in a try block diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 5fce171e1f..52353a4a0a 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -139,6 +139,11 @@ Other Changes Closes #5323 +* Fix false positive for ``undefined-variable`` when ``namedtuple`` class + attributes are used as return annotations. + + Closes #5568 + * ``used-before-assignment`` now considers that assignments in a try block may not have occurred when the except or finally blocks are executed. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1baad8b6f4..b540ed6154 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1781,20 +1781,18 @@ def _is_variable_violation( frame, nodes.FunctionDef ): # Special rule for function return annotations, - # which uses the same name as the class where - # the function lives. + # using a name defined earlier in the class containing the function. if node is frame.returns and defframe.parent_of(frame.returns): - maybe_before_assign = annotation_return = True - - if ( - maybe_before_assign - and defframe.name in defframe.locals - and defframe.locals[node.name][0].lineno < frame.lineno - ): - # Detect class assignments with the same - # name as the class. In this case, no warning - # should be raised. - maybe_before_assign = False + annotation_return = True + if ( + frame.returns.name in defframe.locals + and defframe.locals[node.name][0].lineno < frame.lineno + ): + # Detect class assignments with a name defined earlier in the + # class. In this case, no warning should be raised. + maybe_before_assign = False + else: + maybe_before_assign = True if isinstance(node.parent, nodes.Arguments): maybe_before_assign = stmt.fromlineno <= defstmt.fromlineno elif is_recursive_klass: diff --git a/tests/functional/u/undefined/undefined_variable.py b/tests/functional/u/undefined/undefined_variable.py index 6ce9aaa6ec..f9961509a4 100644 --- a/tests/functional/u/undefined/undefined_variable.py +++ b/tests/functional/u/undefined/undefined_variable.py @@ -425,3 +425,18 @@ def typing_and_value_assignment_with_tuple_assignment(): var_one, var_two = 1, 1 print(var_one) print(var_two) + + +def nested_class_as_return_annotation(): + """A namedtuple as a class attribute is used as a return annotation + + Taken from https://github.com/PyCQA/pylint/issues/5568""" + from collections import namedtuple + + class MyObject: + Coords = namedtuple('Point', ['x', 'y']) + + def my_method(self) -> Coords: + pass + + print(MyObject) From 56129b7e05ed606a40546659daaac510c7572d20 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Sun, 16 Jan 2022 22:06:24 +0100 Subject: [PATCH 158/357] Add checker `redefined-slots-in-subclass` (#5640) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #5617 Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 4 +++ doc/whatsnew/2.13.rst | 4 +++ pylint/checkers/classes/class_checker.py | 42 +++++++++++++++++++++++- tests/functional/r/redefined_slots.py | 33 +++++++++++++++++++ tests/functional/r/redefined_slots.txt | 2 ++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/functional/r/redefined_slots.py create mode 100644 tests/functional/r/redefined_slots.txt diff --git a/ChangeLog b/ChangeLog index ca0dc81846..6c77e9a0b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -115,6 +115,10 @@ Release date: TBA Closes #5499 +* Add checker ``redefined-slots-in-subclass``: Emitted when a slot is redefined in a subclass. + + Closes #5617 + * Fixed false positive for ``global-variable-not-assigned`` when the ``del`` statement is used Closes #5333 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 52353a4a0a..a8507ed234 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -37,6 +37,10 @@ New checkers Closes #5460 +* Add checker ``redefined-slots-in-subclass``: Emitted when a slot is redefined in a subclass. + + Closes #5617 + * Rewrote Checker of ``non-ascii-name``. It now ensures __all__ Python names are ASCII and also properly checks the names of imports (``non-ascii-module-import``) as diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index a3517ca348..89f80a3f7a 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -588,6 +588,11 @@ def _has_same_layout_slots(slots, assigned_value): "subclassed-final-class", "Used when a class decorated with typing.final has been subclassed.", ), + "W0244": ( + "Redefined slots %r in subclass", + "redefined-slots-in-subclass", + "Used when a slot is re-defined in a subclass.", + ), "E0236": ( "Invalid object %r in __slots__, must contain only non empty strings", "invalid-slots-object", @@ -792,6 +797,7 @@ def _ignore_mixin(self): "useless-object-inheritance", "inconsistent-mro", "duplicate-bases", + "redefined-slots-in-subclass", ) def visit_classdef(self, node: nodes.ClassDef) -> None: """init visit variable _accessed""" @@ -1329,7 +1335,7 @@ def _check_invalid_overridden_method(self, function_node, parent_function_node): node=function_node, ) - def _check_slots(self, node): + def _check_slots(self, node: nodes.ClassDef) -> None: if "__slots__" not in node.locals: return for slots in node.igetattr("__slots__"): @@ -1359,6 +1365,40 @@ def _check_slots(self, node): self._check_slots_elt(elt, node) except astroid.InferenceError: continue + self._check_redefined_slots(node, slots, values) + + def _check_redefined_slots( + self, + node: nodes.ClassDef, + slots_node: nodes.NodeNG, + slots_list: List[nodes.NodeNG], + ) -> None: + """Check if `node` redefines a slot which is defined in an ancestor class""" + slots_names: List[str] = [] + for slot in slots_list: + if isinstance(slot, nodes.Const): + slots_names.append(slot.value) + else: + inferred_slot = safe_infer(slot) + if inferred_slot: + slots_names.append(inferred_slot.value) + + # Slots of all parent classes + ancestors_slots_names = { + slot.value + for ancestor in node.local_attr_ancestors("__slots__") + for slot in ancestor.slots() or [] + } + + # Slots which are common to `node` and its parent classes + redefined_slots = ancestors_slots_names.intersection(slots_names) + + if redefined_slots: + self.add_message( + "redefined-slots-in-subclass", + args=", ".join(name for name in slots_names if name in redefined_slots), + node=slots_node, + ) def _check_slots_elt(self, elt, node): for inferred in elt.infer(): diff --git a/tests/functional/r/redefined_slots.py b/tests/functional/r/redefined_slots.py new file mode 100644 index 0000000000..bd9281e40c --- /dev/null +++ b/tests/functional/r/redefined_slots.py @@ -0,0 +1,33 @@ +"""Checks that a subclass does not redefine a slot which has been defined in a parent class.""" + +# pylint: disable=too-few-public-methods + +from collections import deque + + +class Base: + """Class defining the `a`, `b` & `deque.__name__` slots""" + __slots__ = ("a", "b", deque.__name__) + + +class Subclass1(Base): + """Redefining the `a` & `deque.__name__` slots & adding the `d` & `e` slots""" + __slots__ = ("a", deque.__name__, "d", "e") # [redefined-slots-in-subclass] + + +class Subclass2(Base): + """Adding the `f`, `g` & `h` slots""" + __slots__ = ("f", "g", "h") + + +class Base2: + """Class defining the `i`, `j` & `k` slots""" + __slots__ = ("i", "j", "k") + + +class Subclass3(Base, Base2): + """Adding the `l`, `m`, `n` slots + Redefining the `a`, `b`, & `c` slot already defined in `Base` + Redefining the `i`, `j`, `k` slot already defined in `Base2` + """ + __slots__ = ("a", "b", "c", "i", "j", "k", "l", "m", "n") # [redefined-slots-in-subclass] diff --git a/tests/functional/r/redefined_slots.txt b/tests/functional/r/redefined_slots.txt new file mode 100644 index 0000000000..48cb9ea983 --- /dev/null +++ b/tests/functional/r/redefined_slots.txt @@ -0,0 +1,2 @@ +redefined-slots-in-subclass:15:16:15:47:Subclass1:Redefined slots 'a, deque' in subclass:UNDEFINED +redefined-slots-in-subclass:33:16:33:61:Subclass3:Redefined slots 'a, b, i, j, k' in subclass:UNDEFINED From f2c4e35865aeb09bd3be8b9acff6b92b168444f4 Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Mon, 17 Jan 2022 10:34:44 +0100 Subject: [PATCH 159/357] Fixed false positive for ``global-variable-undefined`` (#5690) * Fixed false positive for ``global-variable-undefined`` when ``global`` is used with a class name Closes #3088 Co-authored-by: Pierre Sassoulas --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/utils.py | 4 +++- pylint/checkers/variables.py | 5 ++++- tests/functional/g/globals.py | 14 +++++++++++++- tests/functional/g/globals.txt | 23 ++++++++++++----------- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6c77e9a0b7..e457271884 100644 --- a/ChangeLog +++ b/ChangeLog @@ -42,6 +42,10 @@ Release date: TBA Closes #5588 +* Fixed false positive for ``global-variable-undefined`` when ``global`` is used with a class name + + Closes #3088 + * Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache`` on class methods. This is unrecommended as it creates memory leaks by never letting the instance getting garbage collected. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a8507ed234..d3422ccfbd 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -94,6 +94,10 @@ Other Changes * When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with the ``dill`` package. The ``dill`` package has therefore been added as a dependency. +* Fixed false positive for ``global-variable-undefined`` when ``global`` is used with a class name + + Closes #3088 + * Fixed crash on properties and inherited class methods when comparing them for equality against an empty dict. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 7e08a9048a..a73ee2fa9d 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1668,7 +1668,9 @@ def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: """Check if the given variable name is reassigned in the same scope after the current node""" return any( a.name == varname and a.lineno > node.lineno - for a in node.scope().nodes_of_class((nodes.AssignName, nodes.FunctionDef)) + for a in node.scope().nodes_of_class( + (nodes.AssignName, nodes.ClassDef, nodes.FunctionDef) + ) ) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index b540ed6154..b8b8934442 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1205,7 +1205,10 @@ def visit_global(self, node: nodes.Global) -> None: if anode.frame(future=True) is module: # module level assignment break - if isinstance(anode, nodes.FunctionDef) and anode.parent is module: + if ( + isinstance(anode, (nodes.ClassDef, nodes.FunctionDef)) + and anode.parent is module + ): # module level function assignment break else: diff --git a/tests/functional/g/globals.py b/tests/functional/g/globals.py index 56c852d8a6..f12c06800c 100644 --- a/tests/functional/g/globals.py +++ b/tests/functional/g/globals.py @@ -1,5 +1,5 @@ """Warnings about global statements and usage of global variables.""" -# pylint: disable=invalid-name, redefined-outer-name, missing-function-docstring, import-outside-toplevel +# pylint: disable=invalid-name, redefined-outer-name, missing-function-docstring, missing-class-docstring, import-outside-toplevel, too-few-public-methods from __future__ import print_function global CSTE # [global-at-module-level] @@ -9,6 +9,9 @@ def FUNC(): pass +class CLASS: + pass + def fix_contant(value): """all this is ok, but try not using global ;)""" global CONSTANT # [global-statement] @@ -78,3 +81,12 @@ def func(): global sys # [global-statement] import sys + +def override_class(): + """Overriding a class should only throw a global statement error""" + global CLASS # [global-statement] + + class CLASS(): + pass + + CLASS() diff --git a/tests/functional/g/globals.txt b/tests/functional/g/globals.txt index 4e3f6dca43..975506cc8e 100644 --- a/tests/functional/g/globals.txt +++ b/tests/functional/g/globals.txt @@ -1,13 +1,14 @@ global-at-module-level:5:0:5:11::Using the global statement at the module level:UNDEFINED undefined-variable:6:6:6:10::Undefined variable 'CSTE':UNDEFINED -global-statement:14:4:14:19:fix_contant:Using the global statement:UNDEFINED -global-variable-not-assigned:21:4:21:14:other:Using global for 'HOP' but no assignment is done:UNDEFINED -undefined-variable:22:10:22:13:other:Undefined variable 'HOP':UNDEFINED -global-variable-undefined:27:4:27:18:define_constant:Global variable 'SOMEVAR' undefined at the module level:UNDEFINED -global-statement:33:4:33:14:global_with_import:Using the global statement:UNDEFINED -global-variable-not-assigned:39:4:39:19:global_no_assign:Using global for 'CONSTANT' but no assignment is done:UNDEFINED -global-statement:45:4:45:19:global_del:Using the global statement:UNDEFINED -global-statement:52:4:52:19:global_operator_assign:Using the global statement:UNDEFINED -global-statement:59:4:59:19:global_function_assign:Using the global statement:UNDEFINED -global-statement:69:4:69:15:override_func:Using the global statement:UNDEFINED -global-statement:78:4:78:14:func:Using the global statement:UNDEFINED +global-statement:17:4:17:19:fix_contant:Using the global statement:UNDEFINED +global-variable-not-assigned:24:4:24:14:other:Using global for 'HOP' but no assignment is done:UNDEFINED +undefined-variable:25:10:25:13:other:Undefined variable 'HOP':UNDEFINED +global-variable-undefined:30:4:30:18:define_constant:Global variable 'SOMEVAR' undefined at the module level:UNDEFINED +global-statement:36:4:36:14:global_with_import:Using the global statement:UNDEFINED +global-variable-not-assigned:42:4:42:19:global_no_assign:Using global for 'CONSTANT' but no assignment is done:UNDEFINED +global-statement:48:4:48:19:global_del:Using the global statement:UNDEFINED +global-statement:55:4:55:19:global_operator_assign:Using the global statement:UNDEFINED +global-statement:62:4:62:19:global_function_assign:Using the global statement:UNDEFINED +global-statement:72:4:72:15:override_func:Using the global statement:UNDEFINED +global-statement:81:4:81:14:func:Using the global statement:UNDEFINED +global-statement:87:4:87:16:override_class:Using the global statement:UNDEFINED From c851eb3897062d9ad21086b99885c596a72edd31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 14:27:27 +0100 Subject: [PATCH 160/357] Bump sphinx from 4.3.2 to 4.4.0 (#5692) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.3.2 to 4.4.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.3.2...v4.4.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 943b5e598e..5554d802b0 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ -Sphinx==4.3.2 +Sphinx==4.4.0 python-docs-theme==2021.11.1 -e . From 2ab7d536159f5e54c195db7a42f00261c3b67827 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 14:40:30 +0100 Subject: [PATCH 161/357] Bump python-docs-theme from 2021.11.1 to 2022.1 (#5691) Bumps [python-docs-theme](https://github.com/python/python-docs-theme) from 2021.11.1 to 2022.1. - [Release notes](https://github.com/python/python-docs-theme/releases) - [Changelog](https://github.com/python/python-docs-theme/blob/master/CHANGELOG.rst) - [Commits](https://github.com/python/python-docs-theme/compare/2021.11.1...2022.1) --- updated-dependencies: - dependency-name: python-docs-theme dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 5554d802b0..acaea1e853 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,3 @@ Sphinx==4.4.0 -python-docs-theme==2021.11.1 +python-docs-theme==2022.1 -e . From ccd9aee3c0a64c8a9de52be03daa0fcb7a6dd84b Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Wed, 19 Jan 2022 16:00:11 +0100 Subject: [PATCH 162/357] ``unused-variable`` when `nonlocal` name in a multiple-assignment (#5700) * Fixed false positive for ``unused-variable`` when a `nonlocal` name is assigned as part of a multi-name assignment. Closes #3781 Co-authored-by: Jacob Walls --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/variables.py | 4 +++- tests/functional/n/nonlocal_without_binding.py | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index e457271884..21e26fd374 100644 --- a/ChangeLog +++ b/ChangeLog @@ -46,6 +46,10 @@ Release date: TBA Closes #3088 +* Fixed false positive for ``unused-variable`` when a ``nonlocal`` name is assigned as part of a multi-name assignment. + + Closes #3781 + * Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache`` on class methods. This is unrecommended as it creates memory leaks by never letting the instance getting garbage collected. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index d3422ccfbd..4f2ba057b5 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -275,6 +275,10 @@ Other Changes Closes #4955 +* Fixed false positive for ``unused-variable`` when a ``nonlocal`` name is assigned as part of a multi-name assignment. + + Closes #3781 + * Fixed false positive for ``global-variable-not-assigned`` when the ``del`` statement is used Closes #5333 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index b8b8934442..8f97318c6f 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2136,7 +2136,9 @@ def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names): if name in argnames: self._check_unused_arguments(name, node, stmt, argnames) else: - if stmt.parent and isinstance(stmt.parent, (nodes.Assign, nodes.AnnAssign)): + if stmt.parent and isinstance( + stmt.parent, (nodes.Assign, nodes.AnnAssign, nodes.Tuple) + ): if name in nonlocal_names: return diff --git a/tests/functional/n/nonlocal_without_binding.py b/tests/functional/n/nonlocal_without_binding.py index 22887bcee6..593d924ee9 100644 --- a/tests/functional/n/nonlocal_without_binding.py +++ b/tests/functional/n/nonlocal_without_binding.py @@ -40,3 +40,17 @@ class Class: local = 1 return local + nonlocal_ + + +def function(): + """Test for `unused-variable` when multiple-assignment contains a `nonlocal`""" + myint, mylist = 0, [] + + print(mylist) + + def inner(): + nonlocal myint + mylist.append(myint) + myint += 1 + + return inner() From 7611ec443509888b96087d1519be9d73cf3b4c79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:08:42 +0100 Subject: [PATCH 163/357] Update pre-commit requirement from ~=2.16 to ~=2.17 (#5710) Updates the requirements on [pre-commit](https://github.com/pre-commit/pre-commit) to permit the latest version. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.16.0...v2.17.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 0bad61018d..e39fb4fb22 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -2,7 +2,7 @@ -r requirements_test_min.txt coveralls~=3.3 coverage~=6.2 -pre-commit~=2.16;python_full_version>="3.6.2" +pre-commit~=2.17;python_full_version>="3.6.2" tbump~=6.6.0 pyenchant~=3.2 pytest-cov~=3.0 From 6977e48ab9399ce9d953ea86fb480d362d9cb6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 24 Jan 2022 20:19:17 +0100 Subject: [PATCH 164/357] Fix ``super-init-not-called`` if parent or ``self`` is a ``Protocol`` (#5697) Co-authored-by: Pierre Sassoulas --- ChangeLog | 4 ++ doc/whatsnew/2.13.rst | 4 ++ pylint/checkers/classes/class_checker.py | 39 ++++++++++++++----- tests/functional/i/init_not_called.txt | 4 +- .../n/non/non_init_parent_called.txt | 2 +- .../s/super/super_init_not_called.py | 17 ++++++-- .../s/super/super_init_not_called.txt | 1 + .../super/super_init_not_called_extensions.py | 22 +++++++++++ .../super/super_init_not_called_extensions.rc | 2 + .../super_init_not_called_extensions.txt | 1 + .../super_init_not_called_extensions_py310.py | 22 +++++++++++ .../super_init_not_called_extensions_py310.rc | 4 ++ ...super_init_not_called_extensions_py310.txt | 1 + .../s/super/super_init_not_called_py38.py | 20 ++++++++++ .../s/super/super_init_not_called_py38.rc | 2 + 15 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 tests/functional/s/super/super_init_not_called.txt create mode 100644 tests/functional/s/super/super_init_not_called_extensions.py create mode 100644 tests/functional/s/super/super_init_not_called_extensions.rc create mode 100644 tests/functional/s/super/super_init_not_called_extensions.txt create mode 100644 tests/functional/s/super/super_init_not_called_extensions_py310.py create mode 100644 tests/functional/s/super/super_init_not_called_extensions_py310.rc create mode 100644 tests/functional/s/super/super_init_not_called_extensions_py310.txt create mode 100644 tests/functional/s/super/super_init_not_called_py38.py create mode 100644 tests/functional/s/super/super_init_not_called_py38.rc diff --git a/ChangeLog b/ChangeLog index 21e26fd374..40e05883b1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -210,6 +210,10 @@ Release date: TBA * The ``PyLinter`` class will now be initialized with a ``TextReporter`` as its reporter if none is provided. +* Fix ``super-init-not-called`` when parent or ``self`` is a ``Protocol`` + + Closes #4790 + * Fix false positive ``not-callable`` with attributes that alias ``NamedTuple`` Partially closes #1730 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 4f2ba057b5..a31078850a 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -111,6 +111,10 @@ Other Changes .. _`emacs file locks`: https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Locks.html +* Fix ``super-init-not-called`` when parent or ``self`` is a ``Protocol`` + + Closes #4790 + * An astroid issue where symlinks were not being taken into account was fixed diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 89f80a3f7a..c39ecafe46 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -52,10 +52,10 @@ """Classes checker for Python code""" import collections from itertools import chain, zip_longest -from typing import List, Pattern +from typing import Dict, List, Pattern import astroid -from astroid import nodes +from astroid import bases, nodes from pylint.checkers import BaseChecker, utils from pylint.checkers.utils import ( @@ -81,7 +81,7 @@ unimplemented_abstract_methods, uninferable_final_decorators, ) -from pylint.interfaces import IAstroidChecker +from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} @@ -1086,12 +1086,13 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._check_useless_super_delegation(node) self._check_property_with_parameters(node) - klass = node.parent.frame(future=True) + # 'is_method()' is called and makes sure that this is a 'nodes.ClassDef' + klass = node.parent.frame(future=True) # type: nodes.ClassDef self._meth_could_be_func = True # check first argument is self if this is actually a method self._check_first_arg_for_type(node, klass.type == "metaclass") if node.name == "__init__": - self._check_init(node) + self._check_init(node, klass) return # check signature if the method overloads inherited method for overridden in klass.local_attr_ancestors(node.name): @@ -1928,7 +1929,7 @@ def is_abstract(method): continue self.add_message("abstract-method", node=node, args=(name, owner.name)) - def _check_init(self, node): + def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> None: """check that the __init__ method call super or ancestors'__init__ method (unless it is used for type hinting with `typing.overload`) """ @@ -1936,7 +1937,6 @@ def _check_init(self, node): "super-init-not-called" ) and not self.linter.is_message_enabled("non-parent-init-called"): return - klass_node = node.parent.frame(future=True) to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) for stmt in node.nodes_of_class(nodes.Call): @@ -1980,12 +1980,29 @@ def _check_init(self, node): except astroid.InferenceError: continue for klass, method in not_called_yet.items(): + # Return if klass is protocol + if klass.qname() in utils.TYPING_PROTOCOLS: + return + + # Return if any of the klass' first-order bases is protocol + for base in klass.bases: + # We don't need to catch InferenceError here as _ancestors_to_call + # already does this for us. + for inf_base in base.infer(): + if inf_base.qname() in utils.TYPING_PROTOCOLS: + return + if decorated_with(node, ["typing.overload"]): continue cls = node_frame_class(method) if klass.name == "object" or (cls and cls.name == "object"): continue - self.add_message("super-init-not-called", args=klass.name, node=node) + self.add_message( + "super-init-not-called", + args=klass.name, + node=node, + confidence=INFERENCE, + ) def _check_signature(self, method1, refmethod, class_type, cls): """check that the signature of the two given methods match""" @@ -2092,11 +2109,13 @@ def _is_mandatory_method_param(self, node: nodes.NodeNG) -> bool: return isinstance(node, nodes.Name) and node.name == first_attr -def _ancestors_to_call(klass_node, method="__init__"): +def _ancestors_to_call( + klass_node: nodes.ClassDef, method="__init__" +) -> Dict[nodes.ClassDef, bases.UnboundMethod]: """return a dictionary where keys are the list of base classes providing the queried method, and so that should/may be called from the method node """ - to_call = {} + to_call: Dict[nodes.ClassDef, bases.UnboundMethod] = {} for base_node in klass_node.ancestors(recurs=False): try: to_call[base_node] = next(base_node.igetattr(method)) diff --git a/tests/functional/i/init_not_called.txt b/tests/functional/i/init_not_called.txt index 9d04c923f1..f1779a26a0 100644 --- a/tests/functional/i/init_not_called.txt +++ b/tests/functional/i/init_not_called.txt @@ -1,2 +1,2 @@ -super-init-not-called:25:4:26:27:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called:UNDEFINED -super-init-not-called:58:4:59:20:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called:UNDEFINED +super-init-not-called:25:4:26:27:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called:INFERENCE +super-init-not-called:58:4:59:20:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called:INFERENCE diff --git a/tests/functional/n/non/non_init_parent_called.txt b/tests/functional/n/non/non_init_parent_called.txt index a6b50d5ce5..97f6eb4edc 100644 --- a/tests/functional/n/non/non_init_parent_called.txt +++ b/tests/functional/n/non/non_init_parent_called.txt @@ -2,5 +2,5 @@ import-error:7:0:7:18::Unable to import 'nonexistant':UNDEFINED non-parent-init-called:15:8:15:26:AAAA.__init__:__init__ method from a non direct base class 'BBBBMixin' is called:UNDEFINED no-member:23:50:23:77:CCC:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE no-member:28:8:28:35:CCC.__init__:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE -super-init-not-called:49:4:51:25:Super2.__init__:__init__ method from base class 'dict' is not called:UNDEFINED +super-init-not-called:49:4:51:25:Super2.__init__:__init__ method from base class 'dict' is not called:INFERENCE no-member:51:8:51:23:Super2.__init__:Super of 'Super2' has no '__woohoo__' member:INFERENCE diff --git a/tests/functional/s/super/super_init_not_called.py b/tests/functional/s/super/super_init_not_called.py index 5f2bf9ee8a..d2e9cffd87 100644 --- a/tests/functional/s/super/super_init_not_called.py +++ b/tests/functional/s/super/super_init_not_called.py @@ -1,13 +1,22 @@ -"""This should not emit a super-init-not-called warning. It previously did this, because -``next(node.infer())`` was used in that checker's logic and the first inferred node -was an Uninferable object, leading to this false positive.""" +"""Tests for super-init-not-called.""" # pylint: disable=too-few-public-methods import ctypes class Foo(ctypes.BigEndianStructure): - """A class""" + """This class should not emit a super-init-not-called warning. + + It previously did, because ``next(node.infer())`` was used in that checker's logic + and the first inferred node was an Uninferable object, leading to this false positive. + """ def __init__(self): ctypes.BigEndianStructure.__init__(self) + + +class UninferableChild(UninferableParent): # [undefined-variable] + """An implementation that test if we don't crash on uninferable parents.""" + + def __init__(self): + ... diff --git a/tests/functional/s/super/super_init_not_called.txt b/tests/functional/s/super/super_init_not_called.txt new file mode 100644 index 0000000000..b453d8a856 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called.txt @@ -0,0 +1 @@ +undefined-variable:18:23:18:40:UninferableChild:Undefined variable 'UninferableParent':UNDEFINED diff --git a/tests/functional/s/super/super_init_not_called_extensions.py b/tests/functional/s/super/super_init_not_called_extensions.py new file mode 100644 index 0000000000..241e0008a1 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_extensions.py @@ -0,0 +1,22 @@ +"""Tests for super-init-not-called.""" +# pylint: disable=too-few-public-methods + +from typing_extensions import Protocol as ExtensionProtocol + + +class TestProto(ExtensionProtocol): + """A protocol without __init__ using Protocol from typing_extensions.""" + + +class TestParent(TestProto): + """An implementation.""" + + def __init__(self): + ... + + +class TestChild(TestParent): + """An implementation which should call the init of TestParent.""" + + def __init__(self): # [super-init-not-called] + ... diff --git a/tests/functional/s/super/super_init_not_called_extensions.rc b/tests/functional/s/super/super_init_not_called_extensions.rc new file mode 100644 index 0000000000..d584aa9595 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_extensions.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.9 diff --git a/tests/functional/s/super/super_init_not_called_extensions.txt b/tests/functional/s/super/super_init_not_called_extensions.txt new file mode 100644 index 0000000000..6a8ad14fe6 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_extensions.txt @@ -0,0 +1 @@ +super-init-not-called:21:4:22:11:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE diff --git a/tests/functional/s/super/super_init_not_called_extensions_py310.py b/tests/functional/s/super/super_init_not_called_extensions_py310.py new file mode 100644 index 0000000000..241e0008a1 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_extensions_py310.py @@ -0,0 +1,22 @@ +"""Tests for super-init-not-called.""" +# pylint: disable=too-few-public-methods + +from typing_extensions import Protocol as ExtensionProtocol + + +class TestProto(ExtensionProtocol): + """A protocol without __init__ using Protocol from typing_extensions.""" + + +class TestParent(TestProto): + """An implementation.""" + + def __init__(self): + ... + + +class TestChild(TestParent): + """An implementation which should call the init of TestParent.""" + + def __init__(self): # [super-init-not-called] + ... diff --git a/tests/functional/s/super/super_init_not_called_extensions_py310.rc b/tests/functional/s/super/super_init_not_called_extensions_py310.rc new file mode 100644 index 0000000000..3ab4e08e91 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_extensions_py310.rc @@ -0,0 +1,4 @@ +[testoptions] +# Windows test environments on >= 3.10 don't have typing_extensions +exclude_platforms=win32 +min_pyver=3.10 diff --git a/tests/functional/s/super/super_init_not_called_extensions_py310.txt b/tests/functional/s/super/super_init_not_called_extensions_py310.txt new file mode 100644 index 0000000000..6a8ad14fe6 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_extensions_py310.txt @@ -0,0 +1 @@ +super-init-not-called:21:4:22:11:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE diff --git a/tests/functional/s/super/super_init_not_called_py38.py b/tests/functional/s/super/super_init_not_called_py38.py new file mode 100644 index 0000000000..458707da0a --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_py38.py @@ -0,0 +1,20 @@ +"""Tests for super-init-not-called with Protocol.""" +# pylint: disable=too-few-public-methods + +from abc import abstractmethod +from typing import Protocol + + +class MyProtocol(Protocol): + """A protocol.""" + + @abstractmethod + def __init__(self) -> None: + raise NotImplementedError + + +class ProtocolImplimentation(MyProtocol): + """An implementation.""" + + def __init__(self) -> None: + ... diff --git a/tests/functional/s/super/super_init_not_called_py38.rc b/tests/functional/s/super/super_init_not_called_py38.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called_py38.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 From 600e9b38cf4bec58fdc39534ff1af45805612e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 24 Jan 2022 21:21:38 +0100 Subject: [PATCH 165/357] Fix false positive ``super-init-not-called`` for inherited ``init`` (#5698) And remove useless suppressed messages --- ChangeLog | 5 ++++ doc/whatsnew/2.13.rst | 5 ++++ pylint/checkers/classes/class_checker.py | 12 ++++++-- pylint/checkers/imports.py | 4 +-- pylint/checkers/stdlib.py | 4 +-- .../s/super/super_init_not_called.py | 30 ++++++++++++++++++- .../s/super/super_init_not_called.txt | 1 + 7 files changed, 52 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 40e05883b1..0bc7f85f6d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -223,6 +223,11 @@ Release date: TBA Closes #4434 Closes #5370 +* Fix false positive ``super-init-not-called`` for classes that inherit their ``init`` from + a parent. + + Closes #4941 + * ``encoding`` can now be supplied as a positional argument to calls that open files without triggering ``unspecified-encoding``. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a31078850a..c9204109ec 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -128,6 +128,11 @@ Other Changes Closes #5483 +* Fix false positive ``super-init-not-called`` for classes that inherit their ``init`` from + a parent. + + Closes #4941 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index c39ecafe46..a33efb910c 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -52,7 +52,7 @@ """Classes checker for Python code""" import collections from itertools import chain, zip_longest -from typing import Dict, List, Pattern +from typing import Dict, List, Pattern, Set import astroid from astroid import bases, nodes @@ -1939,6 +1939,7 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No return to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) + parents_with_called_inits: Set[bases.UnboundMethod] = set() for stmt in node.nodes_of_class(nodes.Call): expr = stmt.func if not isinstance(expr, nodes.Attribute) or expr.attrname != "__init__": @@ -1971,7 +1972,9 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No if isinstance(klass, astroid.objects.Super): return try: - del not_called_yet[klass] + method = not_called_yet.pop(klass) + # Record that the class' init has been called + parents_with_called_inits.add(node_frame_class(method)) except KeyError: if klass not in to_call: self.add_message( @@ -1980,6 +1983,11 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No except astroid.InferenceError: continue for klass, method in not_called_yet.items(): + # Check if the init of the class that defines this init has already + # been called. + if node_frame_class(method) in parents_with_called_inits: + return + # Return if klass is protocol if klass.qname() in utils.TYPING_PROTOCOLS: return diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index b0daf4bd6e..951504324d 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -427,9 +427,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker): ), ) - def __init__( - self, linter: Optional["PyLinter"] = None - ): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941 + def __init__(self, linter: Optional["PyLinter"] = None) -> None: BaseChecker.__init__(self, linter) self.import_graph: collections.defaultdict = collections.defaultdict(set) self._imports_stack: List[Tuple[Any, Any]] = [] diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index a203f2d07c..11c755ecfd 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -463,9 +463,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): ), } - def __init__( - self, linter: Optional["PyLinter"] = None - ): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941 + def __init__(self, linter: Optional["PyLinter"] = None) -> None: BaseChecker.__init__(self, linter) self._deprecated_methods: Set[Any] = set() self._deprecated_methods.update(DEPRECATED_METHODS[0]) diff --git a/tests/functional/s/super/super_init_not_called.py b/tests/functional/s/super/super_init_not_called.py index d2e9cffd87..c8e9f09d1e 100644 --- a/tests/functional/s/super/super_init_not_called.py +++ b/tests/functional/s/super/super_init_not_called.py @@ -1,5 +1,5 @@ """Tests for super-init-not-called.""" -# pylint: disable=too-few-public-methods +# pylint: disable=too-few-public-methods, missing-class-docstring import ctypes @@ -20,3 +20,31 @@ class UninferableChild(UninferableParent): # [undefined-variable] def __init__(self): ... + + +# Tests for not calling the init of a parent that does not define one +# but inherits it. +class GrandParentWithInit: + def __init__(self): + print(self) + + +class ParentWithoutInit(GrandParentWithInit): + pass + + +class ChildOne(ParentWithoutInit, GrandParentWithInit): + """Since ParentWithoutInit calls GrandParentWithInit it doesn't need to be called.""" + + def __init__(self): + GrandParentWithInit.__init__(self) + + +class ChildTwo(ParentWithoutInit): + def __init__(self): + ParentWithoutInit.__init__(self) + + +class ChildThree(ParentWithoutInit): + def __init__(self): # [super-init-not-called] + ... diff --git a/tests/functional/s/super/super_init_not_called.txt b/tests/functional/s/super/super_init_not_called.txt index b453d8a856..b6a220c69f 100644 --- a/tests/functional/s/super/super_init_not_called.txt +++ b/tests/functional/s/super/super_init_not_called.txt @@ -1 +1,2 @@ undefined-variable:18:23:18:40:UninferableChild:Undefined variable 'UninferableParent':UNDEFINED +super-init-not-called:49:4:50:11:ChildThree.__init__:__init__ method from base class 'ParentWithoutInit' is not called:INFERENCE From ca24f7e74bcdfe1ad7b2df806e5e85c6c4bb798c Mon Sep 17 00:00:00 2001 From: orSolocate <38433858+orSolocate@users.noreply.github.com> Date: Tue, 25 Jan 2022 21:53:28 +0200 Subject: [PATCH 166/357] Add Or Bahari copyrite aliases (#5721) --- .copyrite_aliases | 5 +++++ CONTRIBUTORS.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.copyrite_aliases b/.copyrite_aliases index 773b12d61c..0645ec2fa1 100644 --- a/.copyrite_aliases +++ b/.copyrite_aliases @@ -113,5 +113,10 @@ "mails": ["tushar.sadhwani000@gmail.com", "tushar@deepsource.io"], "authoritative_mail": "tushar.sadhwani000@gmail.com", "name": "Tushar Sadhwani" + }, + { + "mails": ["or.ba402@gmail.com", "orbahari@mail.tau.ac.il"], + "name": "Or Bahari", + "authoritative_mail": "or.ba402@gmail.com" } ] diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3a380f040b..8bfe42a471 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -417,7 +417,7 @@ contributors: * Luigi Bertaco Cristofolini (luigibertaco): contributor -* Or Bahari +* Or Bahari: contributor * Joshua Cannon: contributor From f85fb8d975d17f411e5f91ed2d5f21ba999e121e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 26 Jan 2022 01:23:40 -0500 Subject: [PATCH 167/357] Fix false negative for `undefined-variable` (#5711) For unused type annotation after the same name appears multiple times in a comprehension Fix #5654 Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/variables.py | 12 ++++++++++-- .../u/undefined/undefined_variable_py38.py | 17 ++++++++++++++++- .../u/undefined/undefined_variable_py38.txt | 3 +++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0bc7f85f6d..f11a4b09b4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -175,6 +175,11 @@ Release date: TBA Closes #5326 +* Fix false negative for ``undefined-variable`` for a variable used multiple times + in a comprehension matching an unused outer scope type annotation. + + Closes #5654 + * Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the ``pylint.testutils.functional`` namespace directly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index c9204109ec..b84cb09cb3 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -219,6 +219,11 @@ Other Changes Closes #5326 +* Fix false negative for ``undefined-variable`` for a variable used multiple times + in a comprehension matching an unused outer scope type annotation. + + Closes #5654 + * Require Python ``3.6.2`` to run pylint. Closes #5065 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 8f97318c6f..9df08cece4 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1388,8 +1388,16 @@ def _check_consumer( # (like "if x" in "[x for x in expr() if x]") # https://github.com/PyCQA/pylint/issues/5586 and not ( - isinstance(node.parent.parent, nodes.Comprehension) - and node.parent in node.parent.parent.ifs + ( + isinstance(node.parent.parent, nodes.Comprehension) + and node.parent in node.parent.parent.ifs + ) + # Or homonyms against values to keyword arguments + # (like "var" in "[func(arg=var) for var in expr()]") + or ( + isinstance(node.scope(), nodes.ComprehensionScope) + and isinstance(node.parent, (nodes.Call, nodes.Keyword)) + ) ) ): self._check_late_binding_closure(node) diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index 4ecc251336..7fdee62dbf 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -123,5 +123,20 @@ def type_annotation_used_after_comprehension(): def type_annotation_unused_after_comprehension(): """https://github.com/PyCQA/pylint/issues/5326""" + my_int: int # [unused-variable] + _ = [print(sep=my_int, end=my_int) for my_int in range(10)] + + +def type_annotation_used_improperly_after_comprehension(): + # TO-DO follow up in https://github.com/PyCQA/pylint/issues/5713 + """https://github.com/PyCQA/pylint/issues/5654""" + my_int: int + _ = [print(sep=my_int, end=my_int) for my_int in range(10)] + print(my_int) # [undefined-variable] + + +def type_annotation_used_improperly_after_comprehension_2(): + """Same case as above but with positional arguments""" my_int: int - _ = [print(kwarg1=my_int, kwarg2=my_int) for my_int in range(10)] + _ = [print(my_int, my_int) for my_int in range(10)] + print(my_int) # [undefined-variable] diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index da69ce0099..1786a89646 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -2,3 +2,6 @@ undefined-variable:42:6:42:16::Undefined variable 'no_default':UNDEFINED undefined-variable:50:6:50:22::Undefined variable 'again_no_default':UNDEFINED undefined-variable:76:6:76:19::Undefined variable 'else_assign_1':UNDEFINED undefined-variable:99:6:99:19::Undefined variable 'else_assign_2':UNDEFINED +unused-variable:126:4:126:10:type_annotation_unused_after_comprehension:Unused variable 'my_int':UNDEFINED +undefined-variable:135:10:135:16:type_annotation_used_improperly_after_comprehension:Undefined variable 'my_int':UNDEFINED +undefined-variable:142:10:142:16:type_annotation_used_improperly_after_comprehension_2:Undefined variable 'my_int':UNDEFINED From 0fb6a120c6f0fbc94e305542fe29e790274be096 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 26 Jan 2022 01:26:48 -0500 Subject: [PATCH 168/357] Fix #5713: Emit `used-before-assignment` instead of `undefined-variable` when accessing unused type annotations (#5718) * Add confidence level HIGH to `used-before-assignment` --- ChangeLog | 5 ++ doc/whatsnew/2.13.rst | 5 ++ pylint/checkers/variables.py | 9 +- .../u/undefined/undefined_variable.py | 86 +----------------- .../u/undefined/undefined_variable.txt | 5 +- ...used_before_assignment_type_annotations.py | 90 +++++++++++++++++++ ...sed_before_assignment_type_annotations.txt | 3 + 7 files changed, 113 insertions(+), 90 deletions(-) create mode 100644 tests/functional/u/use/used_before_assignment_type_annotations.py create mode 100644 tests/functional/u/use/used_before_assignment_type_annotations.txt diff --git a/ChangeLog b/ChangeLog index f11a4b09b4..3ee22bbffe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -89,6 +89,11 @@ Release date: TBA * Pyreverse - add output in mermaidjs format +* Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting + to access unused type annotations. + + Closes #5713 + * ``used-before-assignment`` now considers that assignments in a try block may not have occurred when the except or finally blocks are executed. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index b84cb09cb3..d96776f2b7 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -124,6 +124,11 @@ Other Changes Closes #4798 Closes #5081 +* Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting + to access unused type annotations. + + Closes #5713 + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 9df08cece4..e9ec67d38f 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1551,7 +1551,14 @@ def _check_consumer( ) elif self._is_only_type_assignment(node, defstmt): - self.add_message("undefined-variable", args=node.name, node=node) + if node.scope().locals.get(node.name): + self.add_message( + "used-before-assignment", args=node.name, node=node, confidence=HIGH + ) + else: + self.add_message( + "undefined-variable", args=node.name, node=node, confidence=HIGH + ) return (VariableVisitConsumerAction.CONSUME, found_nodes) elif isinstance(defstmt, nodes.ClassDef): diff --git a/tests/functional/u/undefined/undefined_variable.py b/tests/functional/u/undefined/undefined_variable.py index f9961509a4..3ea7cf064e 100644 --- a/tests/functional/u/undefined/undefined_variable.py +++ b/tests/functional/u/undefined/undefined_variable.py @@ -4,7 +4,7 @@ from __future__ import print_function # pylint: disable=wrong-import-position -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING DEFINED = 1 @@ -340,40 +340,6 @@ def decorated4(x): from types import GenericAlias object().__class_getitem__ = classmethod(GenericAlias) -# Tests for annotation of variables and potentially undefinition - -def value_and_type_assignment(): - """The variable assigned a value and type""" - variable: int = 2 - print(variable) - - -def only_type_assignment(): - """The variable never gets assigned a value""" - variable: int - print(variable) # [undefined-variable] - - -def both_type_and_value_assignment(): - """The variable first gets a type and subsequently a value""" - variable: int - variable = 1 - print(variable) - - -def value_assignment_after_access(): - """The variable gets a value after it has been accessed""" - variable: int - print(variable) # [undefined-variable] - variable = 1 - - -def value_assignment_from_iterator(): - """The variables gets a value from an iterator""" - variable: int - for variable in (1, 2): - print(variable) - GLOBAL_VAR: int GLOBAL_VAR_TWO: int @@ -390,53 +356,3 @@ def global_var_mixed_assignment(): GLOBAL_VAR: int GLOBAL_VAR_TWO: int - - -def assignment_in_comprehension(): - """A previously typed variables gets used in a comprehension. Don't crash!""" - some_list: List[int] - some_list = [1, 2, 3] - some_list = [i * 2 for i in some_list] - - -def decorator_returning_function(): - """A decorator that returns a wrapper function with decoupled typing""" - def wrapper_with_decoupled_typing(): - print(var) - - var: int - var = 2 - return wrapper_with_decoupled_typing - - -def decorator_returning_incorrect_function(): - """A decorator that returns a wrapper function with decoupled typing""" - def wrapper_with_type_and_no_value(): - print(var) # [undefined-variable] - - var: int - return wrapper_with_type_and_no_value - - -def typing_and_value_assignment_with_tuple_assignment(): - """The typed variables get assigned with a tuple assignment""" - var_one: int - var_two: int - var_one, var_two = 1, 1 - print(var_one) - print(var_two) - - -def nested_class_as_return_annotation(): - """A namedtuple as a class attribute is used as a return annotation - - Taken from https://github.com/PyCQA/pylint/issues/5568""" - from collections import namedtuple - - class MyObject: - Coords = namedtuple('Point', ['x', 'y']) - - def my_method(self) -> Coords: - pass - - print(MyObject) diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt index bee5ab475a..03d50baf66 100644 --- a/tests/functional/u/undefined/undefined_variable.txt +++ b/tests/functional/u/undefined/undefined_variable.txt @@ -32,7 +32,4 @@ undefined-variable:293:27:293:28:undefined_annotation:Undefined variable 'x':UND used-before-assignment:294:7:294:8:undefined_annotation:Using variable 'x' before assignment:UNDEFINED undefined-variable:324:11:324:12:decorated3:Undefined variable 'x':UNDEFINED undefined-variable:329:19:329:20:decorated4:Undefined variable 'y':UNDEFINED -undefined-variable:354:10:354:18:only_type_assignment:Undefined variable 'variable':UNDEFINED -undefined-variable:367:10:367:18:value_assignment_after_access:Undefined variable 'variable':UNDEFINED -undefined-variable:384:10:384:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':UNDEFINED -undefined-variable:415:14:415:17:decorator_returning_incorrect_function.wrapper_with_type_and_no_value:Undefined variable 'var':UNDEFINED +undefined-variable:350:10:350:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':HIGH diff --git a/tests/functional/u/use/used_before_assignment_type_annotations.py b/tests/functional/u/use/used_before_assignment_type_annotations.py new file mode 100644 index 0000000000..1a03050c34 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_type_annotations.py @@ -0,0 +1,90 @@ +"""Tests for annotation of variables and potential use before assignment""" +# pylint: disable=too-few-public-methods, global-variable-not-assigned +from collections import namedtuple +from typing import List + +def value_and_type_assignment(): + """The variable assigned a value and type""" + variable: int = 2 + print(variable) + + +def only_type_assignment(): + """The variable never gets assigned a value""" + variable: int + print(variable) # [used-before-assignment] + + +def both_type_and_value_assignment(): + """The variable first gets a type and subsequently a value""" + variable: int + variable = 1 + print(variable) + + +def value_assignment_after_access(): + """The variable gets a value after it has been accessed""" + variable: int + print(variable) # [used-before-assignment] + variable = 1 + + +def value_assignment_from_iterator(): + """The variables gets a value from an iterator""" + variable: int + for variable in (1, 2): + print(variable) + + +def assignment_in_comprehension(): + """A previously typed variables gets used in a comprehension. Don't crash!""" + some_list: List[int] + some_list = [1, 2, 3] + some_list = [i * 2 for i in some_list] + + +def decorator_returning_function(): + """A decorator that returns a wrapper function with decoupled typing""" + def wrapper_with_decoupled_typing(): + print(var) + + var: int + var = 2 + return wrapper_with_decoupled_typing + + +def decorator_returning_incorrect_function(): + """A decorator that returns a wrapper function with decoupled typing""" + def wrapper_with_type_and_no_value(): + # This emits NameError rather than UnboundLocalError, so + # undefined-variable is okay, even though the traceback refers + # to "free variable 'var' referenced before assignment" + print(var) # [undefined-variable] + + var: int + return wrapper_with_type_and_no_value + + +def typing_and_value_assignment_with_tuple_assignment(): + """The typed variables get assigned with a tuple assignment""" + var_one: int + var_two: int + var_one, var_two = 1, 1 + print(var_one) + print(var_two) + + +def nested_class_as_return_annotation(): + """A namedtuple as a class attribute is used as a return annotation + + Taken from https://github.com/PyCQA/pylint/issues/5568""" + class MyObject: + """namedtuple as class attribute""" + Coords = namedtuple('Point', ['x', 'y']) + + def my_method(self) -> Coords: + """Return annotation is valid""" + # pylint: disable=unnecessary-pass + pass + + print(MyObject) diff --git a/tests/functional/u/use/used_before_assignment_type_annotations.txt b/tests/functional/u/use/used_before_assignment_type_annotations.txt new file mode 100644 index 0000000000..81e1646da8 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_type_annotations.txt @@ -0,0 +1,3 @@ +used-before-assignment:15:10:15:18:only_type_assignment:Using variable 'variable' before assignment:HIGH +used-before-assignment:28:10:28:18:value_assignment_after_access:Using variable 'variable' before assignment:HIGH +undefined-variable:62:14:62:17:decorator_returning_incorrect_function.wrapper_with_type_and_no_value:Undefined variable 'var':HIGH From fa2b6ceca796311a918e28770d101b53a43f84f3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 26 Jan 2022 08:30:01 -0500 Subject: [PATCH 169/357] Fix test conflicts after merging two valid MR incompatible together (#5726) After f85fb8d975d17f411e5f91ed2d5f21ba999e121e, certain unused type annotations raise `used-before-assignment` rather than `undefined-variable`. The subsequent commit `0fb6a120c6f0fbc94e305542fe29e790274be096` was drafted before the prior change was made, and it addressed a related false negative, so this commit just applies the intended change of message type. --- tests/functional/u/undefined/undefined_variable_py38.py | 4 ++-- tests/functional/u/undefined/undefined_variable_py38.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index 7fdee62dbf..6e640421b6 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -132,11 +132,11 @@ def type_annotation_used_improperly_after_comprehension(): """https://github.com/PyCQA/pylint/issues/5654""" my_int: int _ = [print(sep=my_int, end=my_int) for my_int in range(10)] - print(my_int) # [undefined-variable] + print(my_int) # [used-before-assignment] def type_annotation_used_improperly_after_comprehension_2(): """Same case as above but with positional arguments""" my_int: int _ = [print(my_int, my_int) for my_int in range(10)] - print(my_int) # [undefined-variable] + print(my_int) # [used-before-assignment] diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index 1786a89646..a193bb0835 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -3,5 +3,5 @@ undefined-variable:50:6:50:22::Undefined variable 'again_no_default':UNDEFINED undefined-variable:76:6:76:19::Undefined variable 'else_assign_1':UNDEFINED undefined-variable:99:6:99:19::Undefined variable 'else_assign_2':UNDEFINED unused-variable:126:4:126:10:type_annotation_unused_after_comprehension:Unused variable 'my_int':UNDEFINED -undefined-variable:135:10:135:16:type_annotation_used_improperly_after_comprehension:Undefined variable 'my_int':UNDEFINED -undefined-variable:142:10:142:16:type_annotation_used_improperly_after_comprehension_2:Undefined variable 'my_int':UNDEFINED +used-before-assignment:135:10:135:16:type_annotation_used_improperly_after_comprehension:Using variable 'my_int' before assignment:HIGH +used-before-assignment:142:10:142:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH From 44ad84a4332dfb89e810106fef2616a0bc7e47e4 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 26 Jan 2022 11:04:51 -0500 Subject: [PATCH 170/357] Add confidence level `CONTROL_FLOW` (#5709) --- ChangeLog | 3 ++ doc/whatsnew/2.13.rst | 3 ++ pylint/checkers/variables.py | 40 ++++++++++++++++--- pylint/interfaces.py | 6 ++- .../a/assign/assignment_expression.txt | 6 +-- tests/functional/c/class_scope.txt | 2 +- .../c/consider/consider_using_with_open.txt | 2 +- tests/functional/m/module___dict__.txt | 2 +- .../p/postponed_evaluation_not_activated.txt | 2 +- .../functional/r/redefined_except_handler.txt | 2 +- .../u/undefined/undefined_variable.txt | 24 +++++------ .../u/undefined/undefined_variable_py38.txt | 4 +- .../u/use/used_before_assignment.txt | 4 +- ...ent_except_handler_for_try_with_return.txt | 2 +- .../use/used_before_assignment_issue1081.txt | 6 +-- .../use/used_before_assignment_issue2615.txt | 6 +-- .../use/used_before_assignment_issue4761.txt | 2 +- .../u/use/used_before_assignment_issue626.txt | 4 +- .../u/use/used_before_assignment_issue85.txt | 20 +++++----- .../u/use/used_before_assignment_nonlocal.txt | 12 +++--- .../u/use/used_before_assignment_py37.txt | 2 +- .../u/use/used_before_assignment_typing.txt | 6 +-- .../functional/w/with_used_before_assign.txt | 2 +- 23 files changed, 100 insertions(+), 62 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3ee22bbffe..f58cbe3de6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -94,6 +94,9 @@ Release date: TBA Closes #5713 +* Added confidence level ``CONTROL_FLOW`` for warnings relying on assumptions + about control flow. + * ``used-before-assignment`` now considers that assignments in a try block may not have occurred when the except or finally blocks are executed. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index d96776f2b7..f5228d9db5 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -166,6 +166,9 @@ Other Changes Closes #5568 +* Added confidence level ``CONTROL_FLOW`` for warnings relying on assumptions + about control flow. + * ``used-before-assignment`` now considers that assignments in a try block may not have occurred when the except or finally blocks are executed. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index e9ec67d38f..edb4afe33f 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -82,7 +82,13 @@ from pylint.checkers import BaseChecker, utils from pylint.checkers.utils import is_postponed_evaluation_enabled from pylint.constants import PY39_PLUS -from pylint.interfaces import HIGH, INFERENCE, INFERENCE_FAILURE, IAstroidChecker +from pylint.interfaces import ( + CONTROL_FLOW, + HIGH, + INFERENCE, + INFERENCE_FAILURE, + IAstroidChecker, +) from pylint.utils import get_global_option if TYPE_CHECKING: @@ -1408,7 +1414,16 @@ def _check_consumer( if found_nodes is None: return (VariableVisitConsumerAction.CONTINUE, None) if not found_nodes: - self.add_message("used-before-assignment", args=node.name, node=node) + if node.name in current_consumer.consumed_uncertain: + confidence = CONTROL_FLOW + else: + confidence = HIGH + self.add_message( + "used-before-assignment", + args=node.name, + node=node, + confidence=confidence, + ) if current_consumer.consumed_uncertain[node.name]: # If there are nodes added to consumed_uncertain by # get_next_to_consume() because they might not have executed, @@ -1527,7 +1542,10 @@ def _check_consumer( and isinstance(stmt, (nodes.AnnAssign, nodes.FunctionDef)) ): self.add_message( - "used-before-assignment", args=node.name, node=node + "used-before-assignment", + args=node.name, + node=node, + confidence=HIGH, ) return (VariableVisitConsumerAction.CONSUME, found_nodes) @@ -1547,7 +1565,10 @@ def _check_consumer( and stmt.fromlineno <= defstmt.fromlineno ): self.add_message( - "used-before-assignment", args=node.name, node=node + "used-before-assignment", + args=node.name, + node=node, + confidence=HIGH, ) elif self._is_only_type_assignment(node, defstmt): @@ -1564,14 +1585,21 @@ def _check_consumer( elif isinstance(defstmt, nodes.ClassDef): is_first_level_ref = self._is_first_level_self_reference(node, defstmt) if is_first_level_ref == 2: - self.add_message("used-before-assignment", node=node, args=node.name) + self.add_message( + "used-before-assignment", node=node, args=node.name, confidence=HIGH + ) if is_first_level_ref: return (VariableVisitConsumerAction.RETURN, None) elif isinstance(defnode, nodes.NamedExpr): if isinstance(defnode.parent, nodes.IfExp): if self._is_never_evaluated(defnode, defnode.parent): - self.add_message("undefined-variable", args=node.name, node=node) + self.add_message( + "undefined-variable", + args=node.name, + node=node, + confidence=INFERENCE, + ) return (VariableVisitConsumerAction.CONSUME, found_nodes) return (VariableVisitConsumerAction.CONSUME, found_nodes) diff --git a/pylint/interfaces.py b/pylint/interfaces.py index 7670ad19aa..103eb207b4 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -34,6 +34,7 @@ "IReporter", "IChecker", "HIGH", + "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED", @@ -43,13 +44,16 @@ Confidence = namedtuple("Confidence", ["name", "description"]) # Warning Certainties HIGH = Confidence("HIGH", "Warning that is not based on inference result.") +CONTROL_FLOW = Confidence( + "CONTROL_FLOW", "Warning based on assumptions about control flow." +) INFERENCE = Confidence("INFERENCE", "Warning based on inference result.") INFERENCE_FAILURE = Confidence( "INFERENCE_FAILURE", "Warning based on inference with failures." ) UNDEFINED = Confidence("UNDEFINED", "Warning without any associated confidence level.") -CONFIDENCE_LEVELS = [HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED] +CONFIDENCE_LEVELS = [HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED] class Interface: diff --git a/tests/functional/a/assign/assignment_expression.txt b/tests/functional/a/assign/assignment_expression.txt index 35dd1e4fb5..23fb5c394e 100644 --- a/tests/functional/a/assign/assignment_expression.txt +++ b/tests/functional/a/assign/assignment_expression.txt @@ -1,3 +1,3 @@ -used-before-assignment:21:7:21:12::Using variable 'err_a' before assignment:UNDEFINED -used-before-assignment:22:6:22:11::Using variable 'err_b' before assignment:UNDEFINED -used-before-assignment:24:13:24:18::Using variable 'err_d' before assignment:UNDEFINED +used-before-assignment:21:7:21:12::Using variable 'err_a' before assignment:HIGH +used-before-assignment:22:6:22:11::Using variable 'err_b' before assignment:HIGH +used-before-assignment:24:13:24:18::Using variable 'err_d' before assignment:HIGH diff --git a/tests/functional/c/class_scope.txt b/tests/functional/c/class_scope.txt index d32dcfe6c2..d065c1b975 100644 --- a/tests/functional/c/class_scope.txt +++ b/tests/functional/c/class_scope.txt @@ -1,5 +1,5 @@ undefined-variable:11:39:11:46:Well.:Undefined variable 'revattr':UNDEFINED -used-before-assignment:11:30:11:37:Well.:Using variable 'revattr' before assignment:UNDEFINED +used-before-assignment:11:30:11:37:Well.:Using variable 'revattr' before assignment:HIGH undefined-variable:13:25:13:37:Well.:Undefined variable 'get_attr_bad':UNDEFINED undefined-variable:14:19:14:23:Well:Undefined variable 'attr':UNDEFINED undefined-variable:20:15:20:19:Well.Sub:Undefined variable 'Data':UNDEFINED diff --git a/tests/functional/c/consider/consider_using_with_open.txt b/tests/functional/c/consider/consider_using_with_open.txt index 5a91c439b6..ad112994ff 100644 --- a/tests/functional/c/consider/consider_using_with_open.txt +++ b/tests/functional/c/consider/consider_using_with_open.txt @@ -4,4 +4,4 @@ consider-using-with:46:4:46:33:test_open_outside_assignment:Consider using 'with consider-using-with:47:14:47:43:test_open_outside_assignment:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:52:8:52:37:test_open_inside_with_block:Consider using 'with' for resource-allocating operations:UNDEFINED consider-using-with:120:26:122:13:TestControlFlow.test_triggers_if_reassigned_after_if_else:Consider using 'with' for resource-allocating operations:UNDEFINED -used-before-assignment:141:12:141:23:TestControlFlow.test_defined_in_try_and_finally:Using variable 'file_handle' before assignment:UNDEFINED +used-before-assignment:141:12:141:23:TestControlFlow.test_defined_in_try_and_finally:Using variable 'file_handle' before assignment:CONTROL_FLOW diff --git a/tests/functional/m/module___dict__.txt b/tests/functional/m/module___dict__.txt index a465b1fe93..a62def231a 100644 --- a/tests/functional/m/module___dict__.txt +++ b/tests/functional/m/module___dict__.txt @@ -1 +1 @@ -used-before-assignment:5:6:5:14::Using variable '__dict__' before assignment:UNDEFINED +used-before-assignment:5:6:5:14::Using variable '__dict__' before assignment:HIGH diff --git a/tests/functional/p/postponed_evaluation_not_activated.txt b/tests/functional/p/postponed_evaluation_not_activated.txt index 30568e4a5e..8d9630e04c 100644 --- a/tests/functional/p/postponed_evaluation_not_activated.txt +++ b/tests/functional/p/postponed_evaluation_not_activated.txt @@ -1,2 +1,2 @@ undefined-variable:6:36:6:41:Class.from_string:Undefined variable 'Class':UNDEFINED -used-before-assignment:9:30:9:40:Class.validate_b:Using variable 'OtherClass' before assignment:UNDEFINED +used-before-assignment:9:30:9:40:Class.validate_b:Using variable 'OtherClass' before assignment:HIGH diff --git a/tests/functional/r/redefined_except_handler.txt b/tests/functional/r/redefined_except_handler.txt index 93338d09b5..1184bdd816 100644 --- a/tests/functional/r/redefined_except_handler.txt +++ b/tests/functional/r/redefined_except_handler.txt @@ -1,4 +1,4 @@ redefined-outer-name:11:4:12:12::Redefining name 'err' from outer scope (line 8):UNDEFINED redefined-outer-name:57:8:58:16::Redefining name 'err' from outer scope (line 51):UNDEFINED -used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:UNDEFINED +used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:HIGH redefined-outer-name:71:4:72:12:func:Redefining name 'CustomException' from outer scope (line 62):UNDEFINED diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt index 03d50baf66..ad857b09b8 100644 --- a/tests/functional/u/undefined/undefined_variable.txt +++ b/tests/functional/u/undefined/undefined_variable.txt @@ -8,28 +8,28 @@ undefined-variable:31:4:31:10:bad_default:Undefined variable 'augvar':UNDEFINED undefined-variable:32:8:32:14:bad_default:Undefined variable 'vardel':UNDEFINED undefined-variable:34:19:34:31::Undefined variable 'doesnotexist':UNDEFINED undefined-variable:35:23:35:24::Undefined variable 'z':UNDEFINED -used-before-assignment:38:4:38:9::Using variable 'POUET' before assignment:UNDEFINED -used-before-assignment:43:4:43:10::Using variable 'POUETT' before assignment:UNDEFINED -used-before-assignment:48:4:48:11::Using variable 'POUETTT' before assignment:UNDEFINED -used-before-assignment:56:4:56:9::Using variable 'PLOUF' before assignment:UNDEFINED -used-before-assignment:65:11:65:14:if_branch_test:Using variable 'xxx' before assignment:UNDEFINED -used-before-assignment:91:23:91:32:test_arguments:Using variable 'TestClass' before assignment:UNDEFINED -used-before-assignment:95:16:95:24:TestClass:Using variable 'Ancestor' before assignment:UNDEFINED -used-before-assignment:98:26:98:35:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment:UNDEFINED -used-before-assignment:105:36:105:41:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment:UNDEFINED +used-before-assignment:38:4:38:9::Using variable 'POUET' before assignment:CONTROL_FLOW +used-before-assignment:43:4:43:10::Using variable 'POUETT' before assignment:CONTROL_FLOW +used-before-assignment:48:4:48:11::Using variable 'POUETTT' before assignment:CONTROL_FLOW +used-before-assignment:56:4:56:9::Using variable 'PLOUF' before assignment:CONTROL_FLOW +used-before-assignment:65:11:65:14:if_branch_test:Using variable 'xxx' before assignment:HIGH +used-before-assignment:91:23:91:32:test_arguments:Using variable 'TestClass' before assignment:HIGH +used-before-assignment:95:16:95:24:TestClass:Using variable 'Ancestor' before assignment:HIGH +used-before-assignment:98:26:98:35:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment:HIGH +used-before-assignment:105:36:105:41:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment:HIGH undefined-variable:119:10:119:14:Self:Undefined variable 'Self':UNDEFINED undefined-variable:135:7:135:10::Undefined variable 'BAT':UNDEFINED -used-before-assignment:146:31:146:38:KeywordArgument.test1:Using variable 'enabled' before assignment:UNDEFINED +used-before-assignment:146:31:146:38:KeywordArgument.test1:Using variable 'enabled' before assignment:HIGH undefined-variable:149:32:149:40:KeywordArgument.test2:Undefined variable 'disabled':UNDEFINED undefined-variable:154:22:154:25:KeywordArgument.:Undefined variable 'arg':UNDEFINED undefined-variable:166:4:166:13::Undefined variable 'unicode_2':UNDEFINED undefined-variable:171:4:171:13::Undefined variable 'unicode_3':UNDEFINED undefined-variable:226:25:226:37:LambdaClass4.:Undefined variable 'LambdaClass4':UNDEFINED undefined-variable:234:25:234:37:LambdaClass5.:Undefined variable 'LambdaClass5':UNDEFINED -used-before-assignment:255:26:255:34:func_should_fail:Using variable 'datetime' before assignment:UNDEFINED +used-before-assignment:255:26:255:34:func_should_fail:Using variable 'datetime' before assignment:HIGH undefined-variable:282:18:282:24:not_using_loop_variable_accordingly:Undefined variable 'iteree':UNDEFINED undefined-variable:293:27:293:28:undefined_annotation:Undefined variable 'x':UNDEFINED -used-before-assignment:294:7:294:8:undefined_annotation:Using variable 'x' before assignment:UNDEFINED +used-before-assignment:294:7:294:8:undefined_annotation:Using variable 'x' before assignment:HIGH undefined-variable:324:11:324:12:decorated3:Undefined variable 'x':UNDEFINED undefined-variable:329:19:329:20:decorated4:Undefined variable 'y':UNDEFINED undefined-variable:350:10:350:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':HIGH diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index a193bb0835..8969423ad0 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -1,7 +1,7 @@ undefined-variable:42:6:42:16::Undefined variable 'no_default':UNDEFINED undefined-variable:50:6:50:22::Undefined variable 'again_no_default':UNDEFINED -undefined-variable:76:6:76:19::Undefined variable 'else_assign_1':UNDEFINED -undefined-variable:99:6:99:19::Undefined variable 'else_assign_2':UNDEFINED +undefined-variable:76:6:76:19::Undefined variable 'else_assign_1':INFERENCE +undefined-variable:99:6:99:19::Undefined variable 'else_assign_2':INFERENCE unused-variable:126:4:126:10:type_annotation_unused_after_comprehension:Unused variable 'my_int':UNDEFINED used-before-assignment:135:10:135:16:type_annotation_used_improperly_after_comprehension:Using variable 'my_int' before assignment:HIGH used-before-assignment:142:10:142:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH diff --git a/tests/functional/u/use/used_before_assignment.txt b/tests/functional/u/use/used_before_assignment.txt index d063aa000a..64fee9e556 100644 --- a/tests/functional/u/use/used_before_assignment.txt +++ b/tests/functional/u/use/used_before_assignment.txt @@ -1,2 +1,2 @@ -used-before-assignment:6:19:6:22::Using variable 'MSG' before assignment:UNDEFINED -used-before-assignment:8:20:8:24::Using variable 'MSG2' before assignment:UNDEFINED +used-before-assignment:6:19:6:22::Using variable 'MSG' before assignment:HIGH +used-before-assignment:8:20:8:24::Using variable 'MSG2' before assignment:HIGH diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt index 7ca99f115c..9f05408f14 100644 --- a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt @@ -1 +1 @@ -used-before-assignment:16:14:16:29:function:Using variable 'failure_message' before assignment:UNDEFINED +used-before-assignment:16:14:16:29:function:Using variable 'failure_message' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/use/used_before_assignment_issue1081.txt b/tests/functional/u/use/used_before_assignment_issue1081.txt index 0aa266877d..857c4826ba 100644 --- a/tests/functional/u/use/used_before_assignment_issue1081.txt +++ b/tests/functional/u/use/used_before_assignment_issue1081.txt @@ -1,7 +1,7 @@ -used-before-assignment:7:7:7:8:used_before_assignment_1:Using variable 'x' before assignment:UNDEFINED +used-before-assignment:7:7:7:8:used_before_assignment_1:Using variable 'x' before assignment:HIGH redefined-outer-name:8:12:8:13:used_before_assignment_1:Redefining name 'x' from outer scope (line 3):UNDEFINED -used-before-assignment:13:7:13:8:used_before_assignment_2:Using variable 'x' before assignment:UNDEFINED +used-before-assignment:13:7:13:8:used_before_assignment_2:Using variable 'x' before assignment:HIGH redefined-outer-name:15:4:15:5:used_before_assignment_2:Redefining name 'x' from outer scope (line 3):UNDEFINED -used-before-assignment:19:7:19:8:used_before_assignment_3:Using variable 'x' before assignment:UNDEFINED +used-before-assignment:19:7:19:8:used_before_assignment_3:Using variable 'x' before assignment:HIGH redefined-outer-name:21:12:21:13:used_before_assignment_3:Redefining name 'x' from outer scope (line 3):UNDEFINED redefined-outer-name:30:4:30:5:not_used_before_assignment_2:Redefining name 'x' from outer scope (line 3):UNDEFINED diff --git a/tests/functional/u/use/used_before_assignment_issue2615.txt b/tests/functional/u/use/used_before_assignment_issue2615.txt index 0da3892c96..567f562305 100644 --- a/tests/functional/u/use/used_before_assignment_issue2615.txt +++ b/tests/functional/u/use/used_before_assignment_issue2615.txt @@ -1,3 +1,3 @@ -used-before-assignment:12:14:12:17:main:Using variable 'res' before assignment:UNDEFINED -used-before-assignment:30:18:30:35:nested_except_blocks:Using variable 'more_bad_division' before assignment:UNDEFINED -used-before-assignment:31:18:31:21:nested_except_blocks:Using variable 'res' before assignment:UNDEFINED +used-before-assignment:12:14:12:17:main:Using variable 'res' before assignment:CONTROL_FLOW +used-before-assignment:30:18:30:35:nested_except_blocks:Using variable 'more_bad_division' before assignment:CONTROL_FLOW +used-before-assignment:31:18:31:21:nested_except_blocks:Using variable 'res' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/use/used_before_assignment_issue4761.txt b/tests/functional/u/use/used_before_assignment_issue4761.txt index cf8fd209a8..f6a34d67c2 100644 --- a/tests/functional/u/use/used_before_assignment_issue4761.txt +++ b/tests/functional/u/use/used_before_assignment_issue4761.txt @@ -1 +1 @@ -used-before-assignment:9:11:9:23:function:Using variable 'some_message' before assignment:UNDEFINED +used-before-assignment:9:11:9:23:function:Using variable 'some_message' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/use/used_before_assignment_issue626.txt b/tests/functional/u/use/used_before_assignment_issue626.txt index 43b86e2642..1ee575ba3e 100644 --- a/tests/functional/u/use/used_before_assignment_issue626.txt +++ b/tests/functional/u/use/used_before_assignment_issue626.txt @@ -1,5 +1,5 @@ unused-variable:5:4:6:12:main1:Unused variable 'e':UNDEFINED -used-before-assignment:8:10:8:11:main1:Using variable 'e' before assignment:UNDEFINED +used-before-assignment:8:10:8:11:main1:Using variable 'e' before assignment:HIGH unused-variable:21:4:22:12:main3:Unused variable 'e':UNDEFINED unused-variable:31:4:32:12:main4:Unused variable 'e':UNDEFINED -used-before-assignment:44:10:44:11:main4:Using variable 'e' before assignment:UNDEFINED +used-before-assignment:44:10:44:11:main4:Using variable 'e' before assignment:HIGH diff --git a/tests/functional/u/use/used_before_assignment_issue85.txt b/tests/functional/u/use/used_before_assignment_issue85.txt index 15af569be1..9f405c2c5c 100644 --- a/tests/functional/u/use/used_before_assignment_issue85.txt +++ b/tests/functional/u/use/used_before_assignment_issue85.txt @@ -1,10 +1,10 @@ -used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED -used-before-assignment:20:14:20:17:try_except_finally:Using variable 'res' before assignment:UNDEFINED -used-before-assignment:45:18:45:21:try_except_finally_nested_try_finally_in_try:Using variable 'res' before assignment:UNDEFINED -used-before-assignment:70:18:70:29:try_except_finally_nested_in_finally:Using variable 'outer_times' before assignment:UNDEFINED -used-before-assignment:84:18:84:29:try_except_finally_nested_in_finally_2:Using variable 'inner_times' before assignment:UNDEFINED -used-before-assignment:85:14:85:25:try_except_finally_nested_in_finally_2:Using variable 'outer_times' before assignment:UNDEFINED -used-before-assignment:100:18:100:29:try_except_finally_nested_in_finally_3:Using variable 'inner_times' before assignment:UNDEFINED -used-before-assignment:101:18:101:29:try_except_finally_nested_in_finally_3:Using variable 'outer_times' before assignment:UNDEFINED -used-before-assignment:122:22:122:33:try_except_finally_nested_in_finally_4:Using variable 'inner_times' before assignment:UNDEFINED -used-before-assignment:123:22:123:33:try_except_finally_nested_in_finally_4:Using variable 'outer_times' before assignment:UNDEFINED +used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:CONTROL_FLOW +used-before-assignment:20:14:20:17:try_except_finally:Using variable 'res' before assignment:CONTROL_FLOW +used-before-assignment:45:18:45:21:try_except_finally_nested_try_finally_in_try:Using variable 'res' before assignment:HIGH +used-before-assignment:70:18:70:29:try_except_finally_nested_in_finally:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:84:18:84:29:try_except_finally_nested_in_finally_2:Using variable 'inner_times' before assignment:CONTROL_FLOW +used-before-assignment:85:14:85:25:try_except_finally_nested_in_finally_2:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:100:18:100:29:try_except_finally_nested_in_finally_3:Using variable 'inner_times' before assignment:CONTROL_FLOW +used-before-assignment:101:18:101:29:try_except_finally_nested_in_finally_3:Using variable 'outer_times' before assignment:CONTROL_FLOW +used-before-assignment:122:22:122:33:try_except_finally_nested_in_finally_4:Using variable 'inner_times' before assignment:CONTROL_FLOW +used-before-assignment:123:22:123:33:try_except_finally_nested_in_finally_4:Using variable 'outer_times' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/use/used_before_assignment_nonlocal.txt b/tests/functional/u/use/used_before_assignment_nonlocal.txt index 95bc19b6cf..90525457e8 100644 --- a/tests/functional/u/use/used_before_assignment_nonlocal.txt +++ b/tests/functional/u/use/used_before_assignment_nonlocal.txt @@ -1,6 +1,6 @@ -used-before-assignment:18:14:18:17:test_fail.wrap:Using variable 'cnt' before assignment:UNDEFINED -used-before-assignment:27:14:27:17:test_fail2.wrap:Using variable 'cnt' before assignment:UNDEFINED -used-before-assignment:30:20:30:30:test_fail3:Using variable 'test_fail4' before assignment:UNDEFINED -used-before-assignment:34:22:34:32:test_fail4:Using variable 'test_fail5' before assignment:UNDEFINED -used-before-assignment:34:44:34:53:test_fail4:Using variable 'undefined' before assignment:UNDEFINED -used-before-assignment:40:18:40:28:test_fail5:Using variable 'undefined1' before assignment:UNDEFINED +used-before-assignment:18:14:18:17:test_fail.wrap:Using variable 'cnt' before assignment:HIGH +used-before-assignment:27:14:27:17:test_fail2.wrap:Using variable 'cnt' before assignment:HIGH +used-before-assignment:30:20:30:30:test_fail3:Using variable 'test_fail4' before assignment:HIGH +used-before-assignment:34:22:34:32:test_fail4:Using variable 'test_fail5' before assignment:HIGH +used-before-assignment:34:44:34:53:test_fail4:Using variable 'undefined' before assignment:HIGH +used-before-assignment:40:18:40:28:test_fail5:Using variable 'undefined1' before assignment:HIGH diff --git a/tests/functional/u/use/used_before_assignment_py37.txt b/tests/functional/u/use/used_before_assignment_py37.txt index f819720f38..72961f9d61 100644 --- a/tests/functional/u/use/used_before_assignment_py37.txt +++ b/tests/functional/u/use/used_before_assignment_py37.txt @@ -1 +1 @@ -used-before-assignment:17:20:17:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:UNDEFINED +used-before-assignment:17:20:17:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:HIGH diff --git a/tests/functional/u/use/used_before_assignment_typing.txt b/tests/functional/u/use/used_before_assignment_typing.txt index 7f73a9eb6e..a35ac263cb 100644 --- a/tests/functional/u/use/used_before_assignment_typing.txt +++ b/tests/functional/u/use/used_before_assignment_typing.txt @@ -1,3 +1,3 @@ -used-before-assignment:12:21:12:28:MyClass.incorrect_typing_method:Using variable 'MyClass' before assignment:UNDEFINED -used-before-assignment:17:26:17:33:MyClass.incorrect_nested_typing_method:Using variable 'MyClass' before assignment:UNDEFINED -used-before-assignment:22:20:22:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:UNDEFINED +used-before-assignment:12:21:12:28:MyClass.incorrect_typing_method:Using variable 'MyClass' before assignment:HIGH +used-before-assignment:17:26:17:33:MyClass.incorrect_nested_typing_method:Using variable 'MyClass' before assignment:HIGH +used-before-assignment:22:20:22:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:HIGH diff --git a/tests/functional/w/with_used_before_assign.txt b/tests/functional/w/with_used_before_assign.txt index 4ceca20ba4..0462099886 100644 --- a/tests/functional/w/with_used_before_assign.txt +++ b/tests/functional/w/with_used_before_assign.txt @@ -1,2 +1,2 @@ undefined-variable:10:39:10:42:do_nothing:Undefined variable 'ctx':UNDEFINED -used-before-assignment:11:8:11:15:do_nothing:Using variable 'context' before assignment:UNDEFINED +used-before-assignment:11:8:11:15:do_nothing:Using variable 'context' before assignment:HIGH From a8f81db85ebf84ce8524d1e027a1e5b69821b419 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 27 Jan 2022 10:58:43 -0500 Subject: [PATCH 171/357] Fix #5683: False positive ``used-before-assignment`` in loop `else` where the only non-break exit is via `except` (#5684) --- ChangeLog | 7 + doc/whatsnew/2.13.rst | 7 + pylint/checkers/variables.py | 98 +++++++++++ .../u/use/used_before_assignment_issue4761.py | 166 ++++++++++++++++++ .../use/used_before_assignment_issue4761.txt | 8 + 5 files changed, 286 insertions(+) diff --git a/ChangeLog b/ChangeLog index f58cbe3de6..83d674d3d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -157,6 +157,13 @@ Release date: TBA Closes #5500 +* When evaluating statements in the ``else`` clause of a loop, ``used-before-assignment`` + assumes that assignments in the except blocks took place if the + except handlers constituted the only ways for the loop to finish without + breaking early. + + Closes #5683 + * ``used-before-assignment`` now checks names in try blocks. * Fixed false positive with ``used-before-assignment`` for assignment expressions diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index f5228d9db5..b2e4a838d8 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -205,6 +205,13 @@ Other Changes Closes #5500 +* When evaluating statements in the ``else`` clause of a loop, ``used-before-assignment`` + assumes that assignments in the except blocks took place if the + except handlers constituted the only ways for the loop to finish without + breaking early. + + Closes #5683 + * ``used-before-assignment`` now checks names in try blocks. * Fixed false positive with ``used-before-assignment`` for assignment expressions diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index edb4afe33f..1dcaa14966 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -741,10 +741,108 @@ def _uncertain_nodes_in_except_blocks( # if one of the except blocks does not define the name in question, # raise, or return. See: https://github.com/PyCQA/pylint/issues/5524. continue + + if NamesConsumer._check_loop_finishes_via_except( + node, other_node_statement.parent.parent + ): + continue + # Passed all tests for uncertain execution uncertain_nodes.append(other_node) return uncertain_nodes + @staticmethod + def _check_loop_finishes_via_except( + node: nodes.NodeNG, other_node_try_except: nodes.TryExcept + ) -> bool: + """Check for a case described in https://github.com/PyCQA/pylint/issues/5683. + It consists of a specific control flow scenario where the only + non-break exit from a loop consists of the very except handler we are + examining, such that code in the `else` branch of the loop can depend on it + being assigned. + + Example: + + for _ in range(3): + try: + do_something() + except: + name = 1 <-- only non-break exit from loop + else: + break + else: + print(name) + """ + if not other_node_try_except.orelse: + return False + closest_loop: Optional[ + Union[nodes.For, nodes.While] + ] = utils.get_node_first_ancestor_of_type(node, (nodes.For, nodes.While)) + if closest_loop is None: + return False + if not any( + else_statement is node or else_statement.parent_of(node) + for else_statement in closest_loop.orelse + ): + # `node` not guarded by `else` + return False + for inner_else_statement in other_node_try_except.orelse: + if isinstance(inner_else_statement, nodes.Break): + break_stmt = inner_else_statement + break + else: + # No break statement + return False + + def _try_in_loop_body( + other_node_try_except: nodes.TryExcept, loop: Union[nodes.For, nodes.While] + ) -> bool: + """Return True if `other_node_try_except` is a descendant of `loop`.""" + return any( + loop_body_statement is other_node_try_except + or loop_body_statement.parent_of(other_node_try_except) + for loop_body_statement in loop.body + ) + + if not _try_in_loop_body(other_node_try_except, closest_loop): + for ancestor in closest_loop.node_ancestors(): + if isinstance(ancestor, (nodes.For, nodes.While)): + if _try_in_loop_body(other_node_try_except, ancestor): + break + else: + # `other_node_try_except` didn't have a shared ancestor loop + return False + + for loop_stmt in closest_loop.body: + if NamesConsumer._recursive_search_for_continue_before_break( + loop_stmt, break_stmt + ): + break + else: + # No continue found, so we arrived at our special case! + return True + return False + + @staticmethod + def _recursive_search_for_continue_before_break( + stmt: nodes.Statement, break_stmt: nodes.Break + ) -> bool: + """Return True if any Continue node can be found in descendants of `stmt` + before encountering `break_stmt`, ignoring any nested loops. + """ + if stmt is break_stmt: + return False + if isinstance(stmt, nodes.Continue): + return True + for child in stmt.get_children(): + if isinstance(stmt, (nodes.For, nodes.While)): + continue + if NamesConsumer._recursive_search_for_continue_before_break( + child, break_stmt + ): + return True + return False + @staticmethod def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( found_nodes: List[nodes.NodeNG], node_statement: nodes.Statement diff --git a/tests/functional/u/use/used_before_assignment_issue4761.py b/tests/functional/u/use/used_before_assignment_issue4761.py index ab6fc765b4..ad4cca0baf 100644 --- a/tests/functional/u/use/used_before_assignment_issue4761.py +++ b/tests/functional/u/use/used_before_assignment_issue4761.py @@ -10,3 +10,169 @@ def function(): return 1 return some_message + + +# Cases related to a specific control flow where +# the `else` of a loop can depend on a name only defined +# in a single except handler because that except handler is the +# only non-break exit branch. + +def valid_only_non_break_exit_from_loop_is_except_handler(): + """https://github.com/PyCQA/pylint/issues/5683""" + for _ in range(3): + try: + function() # not an exit branch because of `else` below + except ValueError as verr: + error = verr # < exit branch where error is defined + else: + break # < exit condition where error is *not* defined + # will skip else: raise error + print("retrying...") + else: + # This usage is valid because there is only one exit branch + raise error + + +def invalid_no_outer_else(): + """The reliance on the name is not guarded by else.""" + for _ in range(3): + try: + function() + except ValueError as verr: + error = verr + else: + break + print("retrying...") + raise error # [used-before-assignment] + + +def invalid_no_outer_else_2(): + """Same, but the raise is inside a loop.""" + for _ in range(3): + try: + function() + except ValueError as verr: + error = verr + else: + break + raise error # [used-before-assignment] + + +def invalid_no_inner_else(): + """No inner else statement.""" + for _ in range(3): + try: + function() + except ValueError as verr: + error = verr + print("retrying...") + if function(): + break + else: + raise error # [used-before-assignment] + + +def invalid_wrong_break_location(): + """The break is in the wrong location.""" + for _ in range(3): + try: + function() + break + except ValueError as verr: + error = verr + print("I give up") + else: + raise error # [used-before-assignment] + + +def invalid_no_break(): + """No break.""" + for _ in range(3): + try: + function() + except ValueError as verr: + error = verr + else: + pass + else: # pylint: disable=useless-else-on-loop + raise error # [used-before-assignment] + + +def invalid_other_non_break_exit_from_loop_besides_except_handler(): + """The continue creates another exit branch.""" + while function(): + if function(): + continue + try: + pass + except ValueError as verr: + error = verr + else: + break + else: + raise error # [used-before-assignment] + + +def valid_continue_does_not_matter(): + """This continue doesn't matter: still just one exit branch.""" + while function(): + try: + for _ in range(3): + if function(): + continue + print(1 / 0) + except ZeroDivisionError as zde: + error = zde + else: + break + else: + raise error + + +def invalid_conditional_continue_after_break(): + """The continue is another exit branch""" + while function(): + try: + if function(): + break + if not function(): + continue + except ValueError as verr: + error = verr + else: + break + else: + raise error # [used-before-assignment] + + +def invalid_unrelated_loops(): + """The loop else in question is not related to the try/except/else.""" + for _ in range(3): + try: + function() + except ValueError as verr: + error = verr + else: + break + while function(): + print('The time is:') + break + else: + raise error # [used-before-assignment] + + +def valid_nested_loops(): + """The name `error` is still available in a nested else.""" + for _ in range(3): + try: + function() + except ValueError as verr: + error = verr + else: + break + else: + while function(): + print('The time is:') + break + else: + raise error diff --git a/tests/functional/u/use/used_before_assignment_issue4761.txt b/tests/functional/u/use/used_before_assignment_issue4761.txt index f6a34d67c2..e3251cbd7b 100644 --- a/tests/functional/u/use/used_before_assignment_issue4761.txt +++ b/tests/functional/u/use/used_before_assignment_issue4761.txt @@ -1 +1,9 @@ used-before-assignment:9:11:9:23:function:Using variable 'some_message' before assignment:CONTROL_FLOW +used-before-assignment:46:10:46:15:invalid_no_outer_else:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:58:14:58:19:invalid_no_outer_else_2:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:72:14:72:19:invalid_no_inner_else:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:85:14:85:19:invalid_wrong_break_location:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:98:14:98:19:invalid_no_break:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:113:14:113:19:invalid_other_non_break_exit_from_loop_besides_except_handler:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:145:14:145:19:invalid_conditional_continue_after_break:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:161:14:161:19:invalid_unrelated_loops:Using variable 'error' before assignment:CONTROL_FLOW From 8ad121fc439162773cc6d9f8860d13913b08f822 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 27 Jan 2022 14:26:18 -0500 Subject: [PATCH 172/357] Fix crash when providing None to mode arg of open() (#5732) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/stdlib.py | 2 +- tests/functional/u/unspecified_encoding_py38.py | 3 +++ tests/functional/u/unspecified_encoding_py38.txt | 2 ++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 83d674d3d9..c59b3065bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -50,6 +50,11 @@ Release date: TBA Closes #3781 +* Fixed a crash in ``unspecified-encoding`` checker when providing ``None`` + to the ``mode`` argument of an ``open()`` call. + + Closes #5731 + * Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache`` on class methods. This is unrecommended as it creates memory leaks by never letting the instance getting garbage collected. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index b2e4a838d8..82903cf8b2 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -156,6 +156,11 @@ Other Changes Closes #5479 +* Fixed a crash in ``unspecified-encoding`` checker when providing ``None`` + to the ``mode`` argument of an ``open()`` call. + + Closes #5731 + * Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables or ``not in`` checks diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 11c755ecfd..6ec7b3af19 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -671,7 +671,7 @@ def _check_open_encoded(self, node: nodes.Call, open_module: str) -> None: if ( not mode_arg or isinstance(mode_arg, nodes.Const) - and "b" not in mode_arg.value + and (not mode_arg.value or "b" not in mode_arg.value) ): encoding_arg = None try: diff --git a/tests/functional/u/unspecified_encoding_py38.py b/tests/functional/u/unspecified_encoding_py38.py index 306f94e698..20233f0734 100644 --- a/tests/functional/u/unspecified_encoding_py38.py +++ b/tests/functional/u/unspecified_encoding_py38.py @@ -156,3 +156,6 @@ class IOArgs: Path(FILENAME).write_text("string", "utf-8") Path(FILENAME).write_text("string") # [unspecified-encoding] + +# Test for crash reported in https://github.com/PyCQA/pylint/issues/5731 +open(FILENAME, mode=None) # [bad-open-mode, unspecified-encoding] diff --git a/tests/functional/u/unspecified_encoding_py38.txt b/tests/functional/u/unspecified_encoding_py38.txt index 59a648a40a..f975ed5f20 100644 --- a/tests/functional/u/unspecified_encoding_py38.txt +++ b/tests/functional/u/unspecified_encoding_py38.txt @@ -27,3 +27,5 @@ unspecified-encoding:149:0:149:23::Using open without explicitly specifying an e unspecified-encoding:152:0:152:28::Using open without explicitly specifying an encoding:UNDEFINED unspecified-encoding:155:0:155:26::Using open without explicitly specifying an encoding:UNDEFINED unspecified-encoding:158:0:158:35::Using open without explicitly specifying an encoding:UNDEFINED +bad-open-mode:161:0:161:25::"""%s"" is not a valid mode for open.":UNDEFINED +unspecified-encoding:161:0:161:25::Using open without explicitly specifying an encoding:UNDEFINED From 3496e06573006563f144876ea4814b94a6f381e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 27 Jan 2022 21:21:30 +0100 Subject: [PATCH 173/357] Remove the ``check_docs`` extension and remove any references to it (#5730) --- ChangeLog | 5 +++++ doc/exts/pylint_extensions.py | 9 +------- doc/whatsnew/2.13.rst | 5 +++++ pylint/extensions/check_docs.py | 25 ----------------------- pylint/lint/run.py | 8 +------- tests/extensions/test_check_docs_utils.py | 4 +--- tests/test_self.py | 8 +------- 7 files changed, 14 insertions(+), 50 deletions(-) delete mode 100644 pylint/extensions/check_docs.py diff --git a/ChangeLog b/ChangeLog index c59b3065bc..3e7f483347 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,11 @@ Release date: TBA Closes #5648 +* Removed the deprecated ``check_docs`` extension. You can use the ``docparams`` checker + to get the checks previously included in ``check_docs``. + + Closes #5322 + * Added several checkers to deal with unicode security issues (see `Trojan Sources `_ and `PEP 672 `_ for details) that also diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py index 31678735d7..2d1e919480 100755 --- a/doc/exts/pylint_extensions.py +++ b/doc/exts/pylint_extensions.py @@ -14,13 +14,6 @@ from pylint.lint import PyLinter from pylint.utils import get_rst_title -# Some modules have been renamed and deprecated under their old names. -# Skip documenting these modules since: -# 1) They are deprecated, why document them moving forward? -# 2) We can't load the deprecated module and the newly renamed module at the -# same time without getting naming conflicts -DEPRECATED_MODULES = ["check_docs"] # ==> docparams - def builder_inited(app): """Output full documentation in ReST format for all extension modules""" @@ -34,7 +27,7 @@ def builder_inited(app): doc_files = {} for filename in os.listdir(ext_path): name, ext = os.path.splitext(filename) - if name[0] == "_" or name in DEPRECATED_MODULES: + if name[0] == "_": continue if ext == ".py": modules.append(f"pylint.extensions.{name}") diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 82903cf8b2..530d68b261 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -59,6 +59,11 @@ New checkers Removed checkers ================ +* Removed the deprecated ``check_docs`` extension. You can use the ``docparams`` checker + to get the checks previously included in ``check_docs``. + + Closes #5322 + Extensions ========== diff --git a/pylint/extensions/check_docs.py b/pylint/extensions/check_docs.py deleted file mode 100644 index 141f512987..0000000000 --- a/pylint/extensions/check_docs.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2014-2015 Bruno Daniel -# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa -# Copyright (c) 2016 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE - -import warnings -from typing import TYPE_CHECKING - -from pylint.extensions import docparams - -if TYPE_CHECKING: - from pylint.lint import PyLinter - - -def register(linter: "PyLinter") -> None: - warnings.warn( - "This plugin is deprecated, use pylint.extensions.docparams instead.", - DeprecationWarning, - ) - linter.register_checker(docparams.DocstringParameterChecker(linter)) diff --git a/pylint/lint/run.py b/pylint/lint/run.py index a7539cd6ac..0adba07236 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -456,13 +456,7 @@ def cb_verbose_mode(self, *args, **kwargs): def cb_enable_all_extensions(self, option_name: str, value: None) -> None: """Callback to load and enable all available extensions""" for filename in os.listdir(os.path.dirname(extensions.__file__)): - # pylint: disable=fixme - # TODO: Remove the check for deprecated check_docs after the extension has been removed - if ( - filename.endswith(".py") - and not filename.startswith("_") - and not filename.startswith("check_docs") - ): + if filename.endswith(".py") and not filename.startswith("_"): extension_name = f"pylint.extensions.{filename[:-3]}" if extension_name not in self._plugins: self._plugins.append(extension_name) diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py index 0414d05a4c..b8851a1ffc 100644 --- a/tests/extensions/test_check_docs_utils.py +++ b/tests/extensions/test_check_docs_utils.py @@ -12,9 +12,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Unit tests for the pylint checkers in :mod:`pylint.extensions.check_docs`, -in particular the parameter documentation checker `DocstringChecker` -""" +"""Unit tests for utils functions in :mod:`pylint.extensions._check_docs_utils`.""" import astroid import pytest diff --git a/tests/test_self.py b/tests/test_self.py index 444a5076b9..f4910168b1 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1258,13 +1258,7 @@ def test_enable_all_extensions() -> None: # Record all extensions plugins = [] for filename in os.listdir(os.path.dirname(extensions.__file__)): - # pylint: disable=fixme - # TODO: Remove the check for deprecated check_docs after the extension has been removed - if ( - filename.endswith(".py") - and not filename.startswith("_") - and not filename.startswith("check_docs") - ): + if filename.endswith(".py") and not filename.startswith("_"): plugins.append(f"pylint.extensions.{filename[:-3]}") # Check if they are loaded From 7e7c94fb3ce01fbb8ce20e67319d3f1110f5b4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 27 Jan 2022 21:25:32 +0100 Subject: [PATCH 174/357] Add ``DeprecationWarning`` for non-string ``current_name`` & ``file`` (#5580) And make ``current_name`` and ``current_file`` string-only --- pylint/checkers/similar.py | 11 ++++++++++- pylint/lint/parallel.py | 9 +++++++++ pylint/lint/pylinter.py | 9 +++++++++ tests/lint/unittest_lint.py | 12 ++++++++---- tests/test_check_parallel.py | 6 ++++-- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index ab72631355..ed85c34b37 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -47,6 +47,7 @@ import operator import re import sys +import warnings from collections import defaultdict from getopt import getopt from io import BufferedIOBase, BufferedReader, BytesIO @@ -829,8 +830,16 @@ def process_module(self, node: nodes.Module) -> None: stream must implement the readlines method """ + if self.linter.current_name is None: + warnings.warn( + ( + "In pylint 3.0 the current_name attribute of the linter object should be a string. " + "If unknown it should be initialized as an empty string." + ), + DeprecationWarning, + ) with node.stream() as stream: - self.append_stream(self.linter.current_name, stream, node.file_encoding) + self.append_stream(self.linter.current_name, stream, node.file_encoding) # type: ignore[arg-type] def close(self): """compute and display similarities on closing (i.e. end of parsing)""" diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index b382c42ad8..63b1e812b7 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -3,6 +3,7 @@ import collections import functools +import warnings from typing import ( TYPE_CHECKING, Any, @@ -85,6 +86,14 @@ def _worker_check_single_file( mapreduce_data[checker.name].append(data) msgs = [_get_new_args(m) for m in _worker_linter.reporter.messages] _worker_linter.reporter.reset() + if _worker_linter.current_name is None: + warnings.warn( + ( + "In pylint 3.0 the current_name attribute of the linter object should be a string. " + "If unknown it should be initialized as an empty string." + ), + DeprecationWarning, + ) return ( id(multiprocessing.current_process()), _worker_linter.current_name, diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 8b1993b4ea..8fd996d6d0 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1165,6 +1165,15 @@ def set_current_module(self, modname, filepath: Optional[str] = None): if not modname and filepath is None: return self.reporter.on_set_current_module(modname, filepath) + if modname is None: + warnings.warn( + ( + "In pylint 3.0 modname should be a string so that it can be used to " + "correctly set the current_name attribute of the linter instance. " + "If unknown it should be initialized as an empty string." + ), + DeprecationWarning, + ) self.current_name = modname self.current_file = filepath or modname self.stats.init_single_module(modname) diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 582e9faa8d..6ac33b830b 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -434,7 +434,8 @@ def test_set_unsupported_reporter(linter: PyLinter) -> None: linter.set_option("output-format", "missing.module.Class") -def test_set_option_1(linter: PyLinter) -> None: +def test_set_option_1(initialized_linter: PyLinter) -> None: + linter = initialized_linter linter.set_option("disable", "C0111,W0234") assert not linter.is_message_enabled("C0111") assert not linter.is_message_enabled("W0234") @@ -443,7 +444,8 @@ def test_set_option_1(linter: PyLinter) -> None: assert not linter.is_message_enabled("non-iterator-returned") -def test_set_option_2(linter: PyLinter) -> None: +def test_set_option_2(initialized_linter: PyLinter) -> None: + linter = initialized_linter linter.set_option("disable", ("C0111", "W0234")) assert not linter.is_message_enabled("C0111") assert not linter.is_message_enabled("W0234") @@ -459,7 +461,8 @@ def test_enable_checkers(linter: PyLinter) -> None: assert "design" in [c.name for c in linter.prepare_checkers()] -def test_errors_only(linter: PyLinter) -> None: +def test_errors_only(initialized_linter: PyLinter) -> None: + linter = initialized_linter linter.error_mode() checkers = linter.prepare_checkers() checker_names = {c.name for c in checkers} @@ -467,7 +470,8 @@ def test_errors_only(linter: PyLinter) -> None: assert set() == should_not & checker_names -def test_disable_similar(linter: PyLinter) -> None: +def test_disable_similar(initialized_linter: PyLinter) -> None: + linter = initialized_linter linter.set_option("disable", "RP0801") linter.set_option("disable", "R0801") assert not ("similarities" in [c.name for c in linter.prepare_checkers()]) diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 738b3061c2..bb6e150a3e 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -297,7 +297,6 @@ def test_sequential_checkers_work(self) -> None: # Add a sequential checker to ensure it records data against some streams linter.register_checker(SequentialTestChecker(linter)) - linter.enable("R9999") # Create a dummy file, the actual contents of which will be ignored by the # register test checkers, but it will trigger at least a single-job to be run. @@ -306,7 +305,10 @@ def test_sequential_checkers_work(self) -> None: # Invoke the lint process in a multiprocess way, although we only specify one # job. check_parallel( - linter, jobs=1, files=iter(single_file_container), arguments=None + linter, + jobs=1, + files=iter(single_file_container), + arguments=["--enable", "R9999"], ) assert len(linter.get_checkers()) == 2, ( "We should only have the 'master' and 'sequential-checker' " From 0a2bf37e78c1e98697c188a03325332cfce03a4d Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 28 Jan 2022 16:11:40 -0500 Subject: [PATCH 175/357] Fix typos in unit test docstrings (#5739) --- tests/functional/n/no/no_else_break.py | 2 +- tests/functional/n/no/no_else_continue.py | 2 +- tests/functional/n/no/no_else_raise.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/n/no/no_else_break.py b/tests/functional/n/no/no_else_break.py index cf23f313ac..d40f19403c 100644 --- a/tests/functional/n/no/no_else_break.py +++ b/tests/functional/n/no/no_else_break.py @@ -1,4 +1,4 @@ -""" Test that superfluous else return are detected. """ +""" Test that superfluous else break are detected. """ # pylint:disable=invalid-name,missing-docstring,unused-variable diff --git a/tests/functional/n/no/no_else_continue.py b/tests/functional/n/no/no_else_continue.py index 37f70f121b..a6599c3363 100644 --- a/tests/functional/n/no/no_else_continue.py +++ b/tests/functional/n/no/no_else_continue.py @@ -1,4 +1,4 @@ -""" Test that superfluous else return are detected. """ +""" Test that superfluous else continue are detected. """ # pylint:disable=invalid-name,missing-docstring,unused-variable diff --git a/tests/functional/n/no/no_else_raise.py b/tests/functional/n/no/no_else_raise.py index ae807fae34..9a54dfc9f3 100644 --- a/tests/functional/n/no/no_else_raise.py +++ b/tests/functional/n/no/no_else_raise.py @@ -1,4 +1,4 @@ -""" Test that superfluous else return are detected. """ +""" Test that superfluous else raise are detected. """ # pylint:disable=invalid-name,missing-docstring,unused-variable,raise-missing-from From 4c2eb2d2936229097544f413ddf3ebf7bb06ef3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 28 Jan 2022 23:00:40 +0100 Subject: [PATCH 176/357] Create separate documentation page for each message (#5396) Co-authored-by: Pierre Sassoulas --- .gitignore | 1 + doc/Makefile | 2 + doc/conf.py | 1 + doc/exts/pylint_messages.py | 226 ++++++++ doc/index.rst | 1 + doc/messages/index.rst | 11 + doc/messages/messages_introduction.rst | 15 + doc/messages/messages_list.rst | 498 ++++++++++++++++++ pylint/checkers/base.py | 2 +- pylint/checkers/non_ascii_names.py | 2 +- .../refactoring/refactoring_checker.py | 4 +- pylint/checkers/stdlib.py | 8 +- pylint/checkers/strings.py | 2 +- 13 files changed, 764 insertions(+), 9 deletions(-) create mode 100644 doc/exts/pylint_messages.py create mode 100644 doc/messages/index.rst create mode 100644 doc/messages/messages_introduction.rst create mode 100644 doc/messages/messages_list.rst diff --git a/.gitignore b/.gitignore index 7e7c9cc978..07ec6627cf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /pylint.egg-info/ .tox *.sw[a-z] +doc/messages/ doc/technical_reference/extensions.rst doc/technical_reference/features.rst pyve diff --git a/doc/Makefile b/doc/Makefile index 9d8ba83356..39266a91ea 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -150,6 +150,8 @@ features.rst: $(shell find ../pylint/checkers -type f -regex '.*\.py') rm -f features.rst PYTHONPATH=$(PYTHONPATH) $(PYTHON) ./exts/pylint_features.py +messages: $(PYTHONPATH) $(PYTHON) ./exts/pylint_messages.py + gen-examples: chmod u+w ../examples/pylintrc pylint --rcfile=/dev/null --generate-rcfile > ../examples/pylintrc diff --git a/doc/conf.py b/doc/conf.py index d76cc67e3b..1c44c89c67 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -36,6 +36,7 @@ extensions = [ "pylint_features", "pylint_extensions", + "pylint_messages", "sphinx.ext.autosectionlabel", "sphinx.ext.intersphinx", ] diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py new file mode 100644 index 0000000000..b737afbf40 --- /dev/null +++ b/doc/exts/pylint_messages.py @@ -0,0 +1,226 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +"""Script used to generate the messages files.""" + +import os +from collections import defaultdict +from pathlib import Path +from typing import DefaultDict, Dict, List, NamedTuple, Optional, Tuple + +from sphinx.application import Sphinx + +from pylint.checkers import initialize as initialize_checkers +from pylint.constants import MSG_TYPES +from pylint.extensions import initialize as initialize_extensions +from pylint.lint import PyLinter +from pylint.message import MessageDefinition +from pylint.utils import get_rst_title + +PYLINT_BASE_PATH = Path(__file__).resolve().parent.parent.parent +"""Base path to the project folder""" + +PYLINT_MESSAGES_PATH = PYLINT_BASE_PATH / "doc" / "messages" +"""Path to the messages documentation folder""" + + +MSG_TYPES_DOC = {k: v if v != "info" else "information" for k, v in MSG_TYPES.items()} + + +class MessageData(NamedTuple): + checker: str + id: str + name: str + definition: MessageDefinition + + +MessagesDict = Dict[str, List[MessageData]] +OldMessagesDict = Dict[str, DefaultDict[Tuple[str, str], List[Tuple[str, str]]]] +"""DefaultDict is indexed by tuples of (old name symbol, old name id) and values are +tuples of (new name symbol, new name category) +""" + + +def _register_all_checkers_and_extensions(linter: PyLinter) -> None: + """Registers all checkers and extensions found in the default folders.""" + initialize_checkers(linter) + initialize_extensions(linter) + + +def _get_all_messages( + linter: PyLinter, +) -> Tuple[MessagesDict, OldMessagesDict]: + """Get all messages registered to a linter and return a dictionary indexed by message + type. + Also return a dictionary of old message and the new messages they can be mapped to. + """ + messages_dict: MessagesDict = { + "fatal": [], + "error": [], + "warning": [], + "convention": [], + "refactor": [], + "information": [], + } + old_messages: OldMessagesDict = { + "fatal": defaultdict(list), + "error": defaultdict(list), + "warning": defaultdict(list), + "convention": defaultdict(list), + "refactor": defaultdict(list), + "information": defaultdict(list), + } + for message in linter.msgs_store.messages: + message_data = MessageData( + message.checker_name, message.msgid, message.symbol, message + ) + messages_dict[MSG_TYPES_DOC[message.msgid[0]]].append(message_data) + + if message.old_names: + for old_name in message.old_names: + category = MSG_TYPES_DOC[old_name[0][0]] + old_messages[category][(old_name[1], old_name[0])].append( + (message.symbol, MSG_TYPES_DOC[message.msgid[0]]) + ) + + return messages_dict, old_messages + + +def _write_message_page(messages_dict: MessagesDict) -> None: + """Create or overwrite the file for each message.""" + for category, messages in messages_dict.items(): + category_dir = PYLINT_MESSAGES_PATH / category + if not category_dir.exists(): + category_dir.mkdir(parents=True, exist_ok=True) + for message in messages: + messages_file = os.path.join(category_dir, f"{message.name}.rst") + with open(messages_file, "w", encoding="utf-8") as stream: + stream.write( + f""".. _{message.name}: + +{get_rst_title(f"{message.name} / {message.id}", "=")} +**Message emitted:** + +{message.definition.msg} + +**Description:** + +*{message.definition.description}* + +Created by ``{message.checker}`` checker +""" + ) + + +def _write_messages_list_page( + messages_dict: MessagesDict, old_messages_dict: OldMessagesDict +) -> None: + """Create or overwrite the page with the list of all messages.""" + messages_file = os.path.join(PYLINT_MESSAGES_PATH, "messages_list.rst") + with open(messages_file, "w", encoding="utf-8") as stream: + # Write header of file + stream.write( + f""".. _messages-list: + +{get_rst_title("Pylint Messages", "=")} +Pylint can emit the following messages: + +""" + ) + + # Iterate over tuple to keep same order + for category in ( + "fatal", + "error", + "warning", + "convention", + "refactor", + "information", + ): + messages = sorted(messages_dict[category], key=lambda item: item.name) + old_messages = sorted(old_messages_dict[category], key=lambda item: item[0]) + messages_string = "".join( + f" {category}/{message.name}.rst\n" for message in messages + ) + old_messages_string = "".join( + f" {category}/{old_message[0]}.rst\n" for old_message in old_messages + ) + + # Write list per category + stream.write( + f"""{get_rst_title(category.capitalize(), "-")} +All messages in the {category} category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + +{messages_string} +All renamed messages in the {category} category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + +{old_messages_string} + +""" + ) + + +def _write_redirect_pages(old_messages: OldMessagesDict) -> None: + """Create redirect pages for old-messages.""" + for category, old_names in old_messages.items(): + category_dir = PYLINT_MESSAGES_PATH / category + if not os.path.exists(category_dir): + os.makedirs(category_dir) + for old_name, new_names in old_names.items(): + old_name_file = os.path.join(category_dir, f"{old_name[0]}.rst") + with open(old_name_file, "w", encoding="utf-8") as stream: + new_names_string = "".join( + f" ../{new_name[1]}/{new_name[0]}.rst\n" for new_name in new_names + ) + stream.write( + f""".. _{old_name[0]}: + +{get_rst_title(" / ".join(old_name), "=")} +"{old_name[0]} has been renamed. The new message can be found at: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + +{new_names_string} +""" + ) + + +# pylint: disable-next=unused-argument +def build_messages_pages(app: Optional[Sphinx]) -> None: + """Overwrite messages files by printing the documentation to a stream. + Documentation is written in ReST format. + """ + # Create linter, register all checkers and extensions and get all messages + linter = PyLinter() + _register_all_checkers_and_extensions(linter) + messages, old_messages = _get_all_messages(linter) + + # Write message and category pages + _write_message_page(messages) + _write_messages_list_page(messages, old_messages) + + # Write redirect pages + _write_redirect_pages(old_messages) + + +def setup(app: Sphinx) -> None: + """Connects the extension to the Sphinx process""" + # Register callback at the builder-inited Sphinx event + # See https://www.sphinx-doc.org/en/master/extdev/appapi.html + app.connect("builder-inited", build_messages_pages) + + +if __name__ == "__main__": + pass + # Uncomment to allow running this script by your local python interpreter + # build_messages_pages(None) diff --git a/doc/index.rst b/doc/index.rst index 1508ff679e..ff2bfd6014 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -17,6 +17,7 @@ refactored and can offer you details about the code's complexity. user_guide/index.rst how_tos/index.rst + messages/index.rst technical_reference/index.rst development_guide/index.rst additional_commands/index.rst diff --git a/doc/messages/index.rst b/doc/messages/index.rst new file mode 100644 index 0000000000..aceca53f61 --- /dev/null +++ b/doc/messages/index.rst @@ -0,0 +1,11 @@ +.. _messages: + +Messages +=================== + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + messages_introduction + messages_list diff --git a/doc/messages/messages_introduction.rst b/doc/messages/messages_introduction.rst new file mode 100644 index 0000000000..97a807d0fd --- /dev/null +++ b/doc/messages/messages_introduction.rst @@ -0,0 +1,15 @@ +.. _messages-introduction: + +Pylint messages +================ + +Pylint can emit various messages. These are categorized according to categories:: + + Convention + Error + Fatal + Information + Refactor + Warning + +A list of these messages can be found here: :ref:`messages-list` diff --git a/doc/messages/messages_list.rst b/doc/messages/messages_list.rst new file mode 100644 index 0000000000..6dd6c5b39b --- /dev/null +++ b/doc/messages/messages_list.rst @@ -0,0 +1,498 @@ +.. _messages-list: + +Pylint Messages +=============== + +Pylint can emit the following messages: + +Fatal +----- + +All messages in the fatal category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + fatal/astroid-error.rst + fatal/config-parse-error.rst + fatal/fatal.rst + fatal/method-check-failed.rst + fatal/parse-error.rst + +All renamed messages in the fatal category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + fatal/old-import-error.rst + + +Error +----- + +All messages in the error category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + error/abstract-class-instantiated.rst + error/access-member-before-definition.rst + error/assigning-non-slot.rst + error/assignment-from-no-return.rst + error/assignment-from-none.rst + error/await-outside-async.rst + error/bad-configuration-section.rst + error/bad-except-order.rst + error/bad-exception-context.rst + error/bad-format-character.rst + error/bad-option-value.rst + error/bad-plugin-value.rst + error/bad-reversed-sequence.rst + error/bad-str-strip-call.rst + error/bad-string-format-type.rst + error/bad-super-call.rst + error/catching-non-exception.rst + error/class-variable-slots-conflict.rst + error/continue-in-finally.rst + error/dict-iter-missing-items.rst + error/duplicate-argument-name.rst + error/duplicate-bases.rst + error/format-needs-mapping.rst + error/function-redefined.rst + error/import-error.rst + error/inconsistent-mro.rst + error/inherit-non-class.rst + error/init-is-generator.rst + error/invalid-all-format.rst + error/invalid-all-object.rst + error/invalid-bool-returned.rst + error/invalid-bytes-returned.rst + error/invalid-class-object.rst + error/invalid-envvar-value.rst + error/invalid-format-returned.rst + error/invalid-getnewargs-ex-returned.rst + error/invalid-getnewargs-returned.rst + error/invalid-hash-returned.rst + error/invalid-index-returned.rst + error/invalid-length-hint-returned.rst + error/invalid-length-returned.rst + error/invalid-metaclass.rst + error/invalid-repr-returned.rst + error/invalid-sequence-index.rst + error/invalid-slice-index.rst + error/invalid-slots.rst + error/invalid-slots-object.rst + error/invalid-star-assignment-target.rst + error/invalid-str-returned.rst + error/invalid-unary-operand-type.rst + error/logging-format-truncated.rst + error/logging-too-few-args.rst + error/logging-too-many-args.rst + error/logging-unsupported-format.rst + error/method-hidden.rst + error/misplaced-bare-raise.rst + error/misplaced-format-function.rst + error/missing-format-string-key.rst + error/missing-kwoa.rst + error/mixed-format-string.rst + error/no-member.rst + error/no-method-argument.rst + error/no-name-in-module.rst + error/no-self-argument.rst + error/no-value-for-parameter.rst + error/non-iterator-returned.rst + error/nonexistent-operator.rst + error/nonlocal-and-global.rst + error/nonlocal-without-binding.rst + error/not-a-mapping.rst + error/not-an-iterable.rst + error/not-async-context-manager.rst + error/not-callable.rst + error/not-context-manager.rst + error/not-in-loop.rst + error/notimplemented-raised.rst + error/raising-bad-type.rst + error/raising-non-exception.rst + error/redundant-keyword-arg.rst + error/relative-beyond-top-level.rst + error/repeated-keyword.rst + error/return-arg-in-generator.rst + error/return-in-init.rst + error/return-outside-function.rst + error/star-needs-assignment-target.rst + error/syntax-error.rst + error/too-few-format-args.rst + error/too-many-format-args.rst + error/too-many-function-args.rst + error/too-many-star-expressions.rst + error/truncated-format-string.rst + error/undefined-all-variable.rst + error/undefined-variable.rst + error/unexpected-keyword-arg.rst + error/unexpected-special-method-signature.rst + error/unhashable-dict-key.rst + error/unpacking-non-sequence.rst + error/unrecognized-inline-option.rst + error/unsubscriptable-object.rst + error/unsupported-assignment-operation.rst + error/unsupported-binary-operation.rst + error/unsupported-delete-operation.rst + error/unsupported-membership-test.rst + error/used-before-assignment.rst + error/used-prior-global-declaration.rst + error/yield-inside-async-function.rst + error/yield-outside-function.rst + +All renamed messages in the error category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + error/bad-context-manager.rst + error/maybe-no-member.rst + error/old-non-iterator-returned-2.rst + error/old-unbalanced-tuple-unpacking.rst + + +Warning +------- + +All messages in the warning category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + warning/abstract-method.rst + warning/anomalous-backslash-in-string.rst + warning/anomalous-unicode-escape-in-string.rst + warning/arguments-differ.rst + warning/arguments-out-of-order.rst + warning/arguments-renamed.rst + warning/assert-on-string-literal.rst + warning/assert-on-tuple.rst + warning/assign-to-new-keyword.rst + warning/attribute-defined-outside-init.rst + warning/bad-builtin.rst + warning/bad-format-string.rst + warning/bad-format-string-key.rst + warning/bad-indentation.rst + warning/bad-open-mode.rst + warning/bad-staticmethod-argument.rst + warning/bad-thread-instantiation.rst + warning/bare-except.rst + warning/binary-op-exception.rst + warning/boolean-datetime.rst + warning/broad-except.rst + warning/cell-var-from-loop.rst + warning/comparison-with-callable.rst + warning/confusing-with-statement.rst + warning/consider-ternary-expression.rst + warning/dangerous-default-value.rst + warning/deprecated-argument.rst + warning/deprecated-class.rst + warning/deprecated-decorator.rst + warning/deprecated-method.rst + warning/deprecated-module.rst + warning/deprecated-typing-alias.rst + warning/differing-param-doc.rst + warning/differing-type-doc.rst + warning/duplicate-except.rst + warning/duplicate-key.rst + warning/duplicate-string-formatting-argument.rst + warning/eval-used.rst + warning/exec-used.rst + warning/expression-not-assigned.rst + warning/f-string-without-interpolation.rst + warning/fixme.rst + warning/forgotten-debug-statement.rst + warning/format-combined-specification.rst + warning/format-string-without-interpolation.rst + warning/global-at-module-level.rst + warning/global-statement.rst + warning/global-variable-not-assigned.rst + warning/global-variable-undefined.rst + warning/implicit-str-concat.rst + warning/import-self.rst + warning/inconsistent-quotes.rst + warning/invalid-envvar-default.rst + warning/invalid-format-index.rst + warning/invalid-overridden-method.rst + warning/isinstance-second-argument-not-valid-type.rst + warning/keyword-arg-before-vararg.rst + warning/logging-format-interpolation.rst + warning/logging-fstring-interpolation.rst + warning/logging-not-lazy.rst + warning/lost-exception.rst + warning/misplaced-future.rst + warning/missing-any-param-doc.rst + warning/missing-format-argument-key.rst + warning/missing-format-attribute.rst + warning/missing-param-doc.rst + warning/missing-parentheses-for-call-in-test.rst + warning/missing-raises-doc.rst + warning/missing-return-doc.rst + warning/missing-return-type-doc.rst + warning/missing-type-doc.rst + warning/missing-yield-doc.rst + warning/missing-yield-type-doc.rst + warning/multiple-constructor-doc.rst + warning/nan-comparison.rst + warning/no-init.rst + warning/non-parent-init-called.rst + warning/non-str-assignment-to-dunder-name.rst + warning/overlapping-except.rst + warning/overridden-final-method.rst + warning/pointless-statement.rst + warning/pointless-string-statement.rst + warning/possibly-unused-variable.rst + warning/preferred-module.rst + warning/protected-access.rst + warning/raise-missing-from.rst + warning/raising-format-tuple.rst + warning/redeclared-assigned-name.rst + warning/redefined-builtin.rst + warning/redefined-outer-name.rst + warning/redundant-returns-doc.rst + warning/redundant-u-string-prefix.rst + warning/redundant-unittest-assert.rst + warning/redundant-yields-doc.rst + warning/reimported.rst + warning/self-assigning-variable.rst + warning/self-cls-assignment.rst + warning/shallow-copy-environ.rst + warning/signature-differs.rst + warning/subclassed-final-class.rst + warning/subprocess-popen-preexec-fn.rst + warning/subprocess-run-check.rst + warning/super-init-not-called.rst + warning/too-many-try-statements.rst + warning/try-except-raise.rst + warning/unbalanced-tuple-unpacking.rst + warning/undefined-loop-variable.rst + warning/unnecessary-lambda.rst + warning/unnecessary-pass.rst + warning/unnecessary-semicolon.rst + warning/unreachable.rst + warning/unspecified-encoding.rst + warning/unused-argument.rst + warning/unused-format-string-argument.rst + warning/unused-format-string-key.rst + warning/unused-import.rst + warning/unused-private-member.rst + warning/unused-variable.rst + warning/unused-wildcard-import.rst + warning/useless-else-on-loop.rst + warning/useless-param-doc.rst + warning/useless-super-delegation.rst + warning/useless-type-doc.rst + warning/useless-with-lock.rst + warning/using-constant-test.rst + warning/using-f-string-in-unsupported-version.rst + warning/using-final-decorator-in-unsupported-version.rst + warning/while-used.rst + warning/wildcard-import.rst + warning/wrong-exception-operation.rst + +All renamed messages in the warning category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + warning/implicit-str-concat-in-sequence.rst + warning/old-assignment-from-none.rst + warning/old-empty-docstring.rst + warning/old-missing-param-doc.rst + warning/old-missing-returns-doc.rst + warning/old-missing-type-doc.rst + warning/old-missing-yields-doc.rst + warning/old-non-iterator-returned-1.rst + warning/old-unidiomatic-typecheck.rst + warning/old-unpacking-non-sequence.rst + + +Convention +---------- + +All messages in the convention category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + convention/bad-classmethod-argument.rst + convention/bad-docstring-quotes.rst + convention/bad-mcs-classmethod-argument.rst + convention/bad-mcs-method-argument.rst + convention/compare-to-empty-string.rst + convention/compare-to-zero.rst + convention/consider-iterating-dictionary.rst + convention/consider-using-any-or-all.rst + convention/consider-using-dict-items.rst + convention/consider-using-enumerate.rst + convention/consider-using-f-string.rst + convention/disallowed-name.rst + convention/docstring-first-line-empty.rst + convention/empty-docstring.rst + convention/import-outside-toplevel.rst + convention/invalid-characters-in-docstring.rst + convention/invalid-name.rst + convention/line-too-long.rst + convention/misplaced-comparison-constant.rst + convention/missing-class-docstring.rst + convention/missing-final-newline.rst + convention/missing-function-docstring.rst + convention/missing-module-docstring.rst + convention/mixed-line-endings.rst + convention/multiple-imports.rst + convention/multiple-statements.rst + convention/non-ascii-name.rst + convention/single-string-used-for-slots.rst + convention/singleton-comparison.rst + convention/superfluous-parens.rst + convention/too-many-lines.rst + convention/trailing-newlines.rst + convention/trailing-whitespace.rst + convention/unexpected-line-ending-format.rst + convention/ungrouped-imports.rst + convention/unidiomatic-typecheck.rst + convention/unneeded-not.rst + convention/use-implicit-booleaness-not-comparison.rst + convention/use-implicit-booleaness-not-len.rst + convention/use-maxsplit-arg.rst + convention/use-sequence-for-iteration.rst + convention/useless-import-alias.rst + convention/wrong-import-order.rst + convention/wrong-import-position.rst + convention/wrong-spelling-in-comment.rst + convention/wrong-spelling-in-docstring.rst + +All renamed messages in the convention category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + convention/blacklisted-name.rst + convention/len-as-condition.rst + convention/missing-docstring.rst + convention/old-misplaced-comparison-constant.rst + + +Refactor +-------- + +All messages in the refactor category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + refactor/chained-comparison.rst + refactor/comparison-with-itself.rst + refactor/condition-evals-to-constant.rst + refactor/confusing-consecutive-elif.rst + refactor/consider-alternative-union-syntax.rst + refactor/consider-merging-isinstance.rst + refactor/consider-swap-variables.rst + refactor/consider-using-alias.rst + refactor/consider-using-assignment-expr.rst + refactor/consider-using-dict-comprehension.rst + refactor/consider-using-from-import.rst + refactor/consider-using-generator.rst + refactor/consider-using-get.rst + refactor/consider-using-in.rst + refactor/consider-using-join.rst + refactor/consider-using-max-builtin.rst + refactor/consider-using-min-builtin.rst + refactor/consider-using-namedtuple-or-dataclass.rst + refactor/consider-using-set-comprehension.rst + refactor/consider-using-sys-exit.rst + refactor/consider-using-ternary.rst + refactor/consider-using-tuple.rst + refactor/consider-using-with.rst + refactor/cyclic-import.rst + refactor/duplicate-code.rst + refactor/else-if-used.rst + refactor/empty-comment.rst + refactor/inconsistent-return-statements.rst + refactor/literal-comparison.rst + refactor/no-classmethod-decorator.rst + refactor/no-else-break.rst + refactor/no-else-continue.rst + refactor/no-else-raise.rst + refactor/no-else-return.rst + refactor/no-self-use.rst + refactor/no-staticmethod-decorator.rst + refactor/property-with-parameters.rst + refactor/redefined-argument-from-local.rst + refactor/redefined-variable-type.rst + refactor/simplifiable-condition.rst + refactor/simplifiable-if-expression.rst + refactor/simplifiable-if-statement.rst + refactor/simplify-boolean-expression.rst + refactor/stop-iteration-return.rst + refactor/super-with-arguments.rst + refactor/too-complex.rst + refactor/too-few-public-methods.rst + refactor/too-many-ancestors.rst + refactor/too-many-arguments.rst + refactor/too-many-boolean-expressions.rst + refactor/too-many-branches.rst + refactor/too-many-instance-attributes.rst + refactor/too-many-locals.rst + refactor/too-many-nested-blocks.rst + refactor/too-many-public-methods.rst + refactor/too-many-return-statements.rst + refactor/too-many-statements.rst + refactor/trailing-comma-tuple.rst + refactor/unnecessary-comprehension.rst + refactor/unnecessary-dict-index-lookup.rst + refactor/use-a-generator.rst + refactor/use-dict-literal.rst + refactor/use-list-literal.rst + refactor/use-set-for-membership.rst + refactor/useless-object-inheritance.rst + refactor/useless-return.rst + +All renamed messages in the refactor category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + refactor/old-simplifiable-if-statement.rst + refactor/old-too-many-nested-blocks.rst + + +Information +----------- + +All messages in the information category: + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + information/bad-inline-option.rst + information/c-extension-no-member.rst + information/deprecated-pragma.rst + information/file-ignored.rst + information/locally-disabled.rst + information/raw-checker-failed.rst + information/suppressed-message.rst + information/use-symbolic-message-instead.rst + information/useless-suppression.rst + +All renamed messages in the information category: + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + information/deprecated-disable-all.rst diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index febc7026be..c14d63c5a4 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -1009,7 +1009,7 @@ class BasicChecker(_BasicChecker): 'Used when you use the "eval" function, to discourage its ' "usage. Consider using `ast.literal_eval` for safely evaluating " "strings containing Python expressions " - "from untrusted sources. ", + "from untrusted sources.", ), "W0150": ( "%s statement in finally block may swallow exception", diff --git a/pylint/checkers/non_ascii_names.py b/pylint/checkers/non_ascii_names.py index 6ad0df556b..1ff2b67e80 100644 --- a/pylint/checkers/non_ascii_names.py +++ b/pylint/checkers/non_ascii_names.py @@ -74,7 +74,7 @@ class NonAsciiNameChecker(base_checker.BaseChecker): "recommended for interoperability. Further reading:\n" "- https://www.python.org/dev/peps/pep-0489/#export-hook-name\n" "- https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers\n" - "- https://bugs.python.org/issue20485\n" + "- https://bugs.python.org/issue20485" ), ), # First %s will always be "module" diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 2387595d39..7c922f6d44 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -227,7 +227,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): "R1703": ( "The if statement can be replaced with %s", "simplifiable-if-statement", - "Used when an if statement can be replaced with 'bool(test)'. ", + "Used when an if statement can be replaced with 'bool(test)'.", {"old_names": [("R0102", "old-simplifiable-if-statement")]}, ), "R1704": ( @@ -336,7 +336,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): "R1719": ( "The if expression can be replaced with %s", "simplifiable-if-expression", - "Used when an if expression can be replaced with 'bool(test)'. ", + "Used when an if expression can be replaced with 'bool(test)'.", ), "R1720": ( 'Unnecessary "%s" after "raise"', diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 6ec7b3af19..bc9283031f 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -386,27 +386,27 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "bad-thread-instantiation", "The warning is emitted when a threading.Thread class " "is instantiated without the target function being passed. " - "By default, the first parameter is the group param, not the target param. ", + "By default, the first parameter is the group param, not the target param.", ), "W1507": ( "Using copy.copy(os.environ). Use os.environ.copy() instead. ", "shallow-copy-environ", "os.environ is not a dict object but proxy object, so " "shallow copy has still effects on original object. " - "See https://bugs.python.org/issue15373 for reference. ", + "See https://bugs.python.org/issue15373 for reference.", ), "E1507": ( "%s does not support %s type argument", "invalid-envvar-value", "Env manipulation functions support only string type arguments. " - "See https://docs.python.org/3/library/os.html#os.getenv. ", + "See https://docs.python.org/3/library/os.html#os.getenv.", ), "W1508": ( "%s default type is %s. Expected str or None.", "invalid-envvar-default", "Env manipulation functions return None or str values. " "Supplying anything different as a default may cause bugs. " - "See https://docs.python.org/3/library/os.html#os.getenv. ", + "See https://docs.python.org/3/library/os.html#os.getenv.", ), "W1509": ( "Using preexec_fn keyword which may be unsafe in the presence " diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 458c96fe64..e512e457d4 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -152,7 +152,7 @@ "E1310": ( "Suspicious argument in %s.%s call", "bad-str-strip-call", - "The argument to a str.{l,r,}strip call contains a duplicate character, ", + "The argument to a str.{l,r,}strip call contains a duplicate character,", ), "W1302": ( "Invalid format string", From dc4c709e9bca44952decdb74d76cf4e62799890a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Jan 2022 00:49:50 -0500 Subject: [PATCH 177/357] Improved `bad-open-mode` message when providing ``None`` to the ``mode`` argument of an `open()` call (#5742) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/stdlib.py | 6 +++++- tests/functional/u/unspecified_encoding_py38.txt | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3e7f483347..1c552fbc92 100644 --- a/ChangeLog +++ b/ChangeLog @@ -60,6 +60,11 @@ Release date: TBA Closes #5731 +* Improved ``bad-open-mode`` message when providing ``None`` to the ``mode`` + argument of an ``open()`` call. + + Closes #5733 + * Added ``lru-cache-decorating-method`` checker with checks for the use of ``functools.lru_cache`` on class methods. This is unrecommended as it creates memory leaks by never letting the instance getting garbage collected. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 530d68b261..597a800789 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -166,6 +166,11 @@ Other Changes Closes #5731 +* Improved ``bad-open-mode`` message when providing ``None`` to the ``mode`` + argument of an ``open()`` call. + + Closes #5733 + * Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables or ``not in`` checks diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index bc9283031f..1a0fab57ea 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -648,7 +648,11 @@ def _check_open_mode(self, node): if isinstance(mode_arg, nodes.Const) and not _check_mode_str( mode_arg.value ): - self.add_message("bad-open-mode", node=node, args=mode_arg.value) + self.add_message( + "bad-open-mode", + node=node, + args=mode_arg.value or str(mode_arg.value), + ) def _check_open_encoded(self, node: nodes.Call, open_module: str) -> None: """Check that the encoded argument of an open call is valid.""" diff --git a/tests/functional/u/unspecified_encoding_py38.txt b/tests/functional/u/unspecified_encoding_py38.txt index f975ed5f20..b3371299f5 100644 --- a/tests/functional/u/unspecified_encoding_py38.txt +++ b/tests/functional/u/unspecified_encoding_py38.txt @@ -27,5 +27,5 @@ unspecified-encoding:149:0:149:23::Using open without explicitly specifying an e unspecified-encoding:152:0:152:28::Using open without explicitly specifying an encoding:UNDEFINED unspecified-encoding:155:0:155:26::Using open without explicitly specifying an encoding:UNDEFINED unspecified-encoding:158:0:158:35::Using open without explicitly specifying an encoding:UNDEFINED -bad-open-mode:161:0:161:25::"""%s"" is not a valid mode for open.":UNDEFINED +bad-open-mode:161:0:161:25::"""None"" is not a valid mode for open.":UNDEFINED unspecified-encoding:161:0:161:25::Using open without explicitly specifying an encoding:UNDEFINED From 40ef1bebe9ee47ef0beef9b3ec53628d954710fa Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Jan 2022 13:35:23 -0500 Subject: [PATCH 178/357] Correct reference to `argv` in documentation for `run_pylint()` (#5741) --- ChangeLog | 2 +- doc/user_guide/run.rst | 2 +- doc/whatsnew/2.13.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1c552fbc92..4e7eced6ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -238,7 +238,7 @@ Release date: TBA Closes #5504 * When invoking ``pylint``, ``epylint``, ``symilar`` or ``pyreverse`` by importing them in a python file - you can now pass an ``arguments`` keyword besides patching ``sys.argv``. + you can now pass an ``argv`` keyword besides patching ``sys.argv``. Closes #5320 diff --git a/doc/user_guide/run.rst b/doc/user_guide/run.rst index de28a44136..ace2009866 100644 --- a/doc/user_guide/run.rst +++ b/doc/user_guide/run.rst @@ -57,7 +57,7 @@ called by the command line. You can either patch ``sys.argv`` or supply argument # Or: - pylint.run_pylint(arguments=["your_file"]) + pylint.run_pylint(argv=["your_file"]) To silently run Pylint on a ``module_name.py`` module, and get its standard output and error: diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 597a800789..f2bf3062f6 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -287,7 +287,7 @@ Other Changes Closes #5557 * When invoking ``pylint``, ``epylint``, ``symilar`` or ``pyreverse`` by importing them in a python file - you can now pass an ``arguments`` keyword besides patching ``sys.argv``. + you can now pass an ``argv`` keyword besides patching ``sys.argv``. Closes #5320 From e0dbce1992e6306624e5c0dccb6436a7e8ec9315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 29 Jan 2022 20:22:53 +0100 Subject: [PATCH 179/357] Emit the crash issue template for more exceptions and crashes (#5743) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/lint/pylinter.py | 17 +++++++++++++---- tests/lint/test_utils.py | 17 +++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4e7eced6ba..389d93b6f0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -71,6 +71,11 @@ Release date: TBA Closes #5670 +* The issue template for crashes is now created for crashes which were previously not covered + by this mechanism. + + Closes #5668 + * Rewrote checker for ``non-ascii-name``. It now ensures __all__ Python names are ASCII and also properly checks the names of imports (``non-ascii-module-import``) as diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index f2bf3062f6..9faaadcdc0 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -120,6 +120,11 @@ Other Changes Closes #4790 +* The issue template for crashes is now created for crashes which were previously not covered + by this mechanism. + + Closes #5668 + * An astroid issue where symlinks were not being taken into account was fixed diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 8fd996d6d0..92733a39cb 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1212,7 +1212,9 @@ def _astroid_module_checker(self): for checker in reversed(_checkers): checker.close() - def get_ast(self, filepath, modname, data=None): + def get_ast( + self, filepath: str, modname: str, data: Optional[str] = None + ) -> nodes.Module: """Return an ast(roid) representation of a module or a string. :param str filepath: path to checked file. @@ -1220,6 +1222,7 @@ def get_ast(self, filepath, modname, data=None): :param str data: optional contents of the checked file. :returns: the AST :rtype: astroid.nodes.Module + :raises AstroidBuildingError: Whenever we encounter an unexpected exception """ try: if data is None: @@ -1235,11 +1238,17 @@ def get_ast(self, filepath, modname, data=None): col_offset=getattr(ex.error, "offset", None), args=str(ex.error), ) - except astroid.AstroidBuildingException as ex: + except astroid.AstroidBuildingError as ex: self.add_message("parse-error", args=ex) - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: traceback.print_exc() - self.add_message("astroid-error", args=(ex.__class__, ex)) + # We raise BuildingError here as this is essentially an astroid issue + # Creating an issue template and adding the 'astroid-error' message is handled + # by caller: _check_files + raise astroid.AstroidBuildingError( + "Building error when trying to create ast representation of module '{modname}'", + modname=modname, + ) from ex return None def check_astroid_module(self, ast_node, walker, rawcheckers, tokencheckers): diff --git a/tests/lint/test_utils.py b/tests/lint/test_utils.py index 44703982e1..a1fa536140 100644 --- a/tests/lint/test_utils.py +++ b/tests/lint/test_utils.py @@ -1,5 +1,9 @@ +import unittest.mock from pathlib import Path, PosixPath +import pytest + +from pylint.lint import Run from pylint.lint.utils import get_fatal_error_message, prepare_crash_report @@ -31,3 +35,16 @@ def test_get_fatal_error_message() -> None: assert python_path in msg assert crash_path in msg assert "open an issue" in msg + + +def test_issue_template_on_fatal_errors(capsys: pytest.CaptureFixture) -> None: + """Test that we also create an issue template if the offending exception isn't from astroid.""" + with pytest.raises(SystemExit): + with unittest.mock.patch( + "astroid.MANAGER.ast_from_file", side_effect=RecursionError() + ): + Run([__file__]) + captured = capsys.readouterr() + assert "Fatal error while checking" in captured.out + assert "Please open an issue" in captured.out + assert "Traceback" in captured.err From 6ad1ff3f98e75a4d36d5ef7785141071f528221e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Jan 2022 16:53:03 -0500 Subject: [PATCH 180/357] Fix #5399: Fix false negatives for further variable messages for invalid type annotations or default arguments (#5727) --- ChangeLog | 6 ++ doc/whatsnew/2.13.rst | 6 ++ pylint/checkers/variables.py | 62 ++++++++----------- .../u/undefined/undefined_variable.py | 11 +++- .../u/undefined/undefined_variable.txt | 4 ++ .../u/use/used_before_assignment_py37.py | 2 +- .../u/use/used_before_assignment_py37.txt | 2 +- .../u/use/used_before_assignment_typing.py | 6 +- .../u/use/used_before_assignment_typing.txt | 6 +- 9 files changed, 61 insertions(+), 44 deletions(-) diff --git a/ChangeLog b/ChangeLog index 389d93b6f0..3674ef5113 100644 --- a/ChangeLog +++ b/ChangeLog @@ -107,6 +107,12 @@ Release date: TBA Closes #5568 +* Fix false negative for ``undefined-variable`` and related variable messages + when the same undefined variable is used as a type annotation and is + accessed multiple times, or is used as a default argument to a function. + + Closes #5399 + * Pyreverse - add output in mermaidjs format * Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 9faaadcdc0..3c419a5c66 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -134,6 +134,12 @@ Other Changes Closes #4798 Closes #5081 +* Fix false negative for ``undefined-variable`` and related variable messages + when the same undefined variable is used as a type annotation and is + accessed multiple times, or is used as a default argument to a function. + + Closes #5399 + * Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting to access unused type annotations. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1dcaa14966..e6d6f5a632 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -170,7 +170,8 @@ class VariableVisitConsumerAction(Enum): - """Used after _visit_consumer to determine the action to be taken + """Reported by _check_consumer() and its sub-methods to determine the + subsequent action to take in _undefined_and_used_before_checker(). Continue -> continue loop to next consumer Return -> return and thereby break the loop @@ -182,6 +183,18 @@ class VariableVisitConsumerAction(Enum): CONSUME = 2 +VariableVisitConsumerActionAndOptionalNodesType = Union[ + Tuple[ + Union[ + Literal[VariableVisitConsumerAction.CONTINUE], + Literal[VariableVisitConsumerAction.RETURN], + ], + None, + ], + Tuple[Literal[VariableVisitConsumerAction.CONSUME], List[nodes.NodeNG]], +] + + def _is_from_future_import(stmt, name): """Check if the name is a future import from another module.""" try: @@ -1469,16 +1482,7 @@ def _check_consumer( current_consumer: NamesConsumer, consumer_level: int, base_scope_type: Any, - ) -> Union[ - Tuple[ - Union[ - Literal[VariableVisitConsumerAction.CONTINUE], - Literal[VariableVisitConsumerAction.RETURN], - ], - None, - ], - Tuple[Literal[VariableVisitConsumerAction.CONSUME], List[nodes.NodeNG]], - ]: + ) -> VariableVisitConsumerActionAndOptionalNodesType: """Checks a consumer for conditions that should trigger messages""" # If the name has already been consumed, only check it's not a loop # variable used outside the loop. @@ -1626,10 +1630,9 @@ def _check_consumer( ) and node.name in node.root().locals ): - self.add_message( - "undefined-variable", args=node.name, node=node - ) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + if defined_by_stmt: + current_consumer.mark_as_consumed(node.name, [node]) + return (VariableVisitConsumerAction.CONTINUE, None) elif base_scope_type != "lambda": # E0601 may *not* occurs in lambda scope. @@ -1681,13 +1684,7 @@ def _check_consumer( return (VariableVisitConsumerAction.CONSUME, found_nodes) elif isinstance(defstmt, nodes.ClassDef): - is_first_level_ref = self._is_first_level_self_reference(node, defstmt) - if is_first_level_ref == 2: - self.add_message( - "used-before-assignment", node=node, args=node.name, confidence=HIGH - ) - if is_first_level_ref: - return (VariableVisitConsumerAction.RETURN, None) + return self._is_first_level_self_reference(node, defstmt, found_nodes) elif isinstance(defnode, nodes.NamedExpr): if isinstance(defnode.parent, nodes.IfExp): @@ -2080,31 +2077,26 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool @staticmethod def _is_first_level_self_reference( - node: nodes.Name, defstmt: nodes.ClassDef - ) -> Literal[0, 1, 2]: + node: nodes.Name, defstmt: nodes.ClassDef, found_nodes: List[nodes.NodeNG] + ) -> VariableVisitConsumerActionAndOptionalNodesType: """Check if a first level method's annotation or default values - refers to its own class. - - Return values correspond to: - 0 = Continue - 1 = Break - 2 = Break + emit message + refers to its own class, and return a consumer action """ if node.frame(future=True).parent == defstmt and node.statement( future=True ) == node.frame(future=True): # Check if used as type annotation - # Break but don't emit message if postponed evaluation is enabled + # Break if postponed evaluation is enabled if utils.is_node_in_type_annotation_context(node): if not utils.is_postponed_evaluation_enabled(node): - return 2 - return 1 + return (VariableVisitConsumerAction.CONTINUE, None) + return (VariableVisitConsumerAction.RETURN, None) # Check if used as default value by calling the class if isinstance(node.parent, nodes.Call) and isinstance( node.parent.parent, nodes.Arguments ): - return 2 - return 0 + return (VariableVisitConsumerAction.CONTINUE, None) + return (VariableVisitConsumerAction.CONSUME, found_nodes) @staticmethod def _is_never_evaluated( diff --git a/tests/functional/u/undefined/undefined_variable.py b/tests/functional/u/undefined/undefined_variable.py index 3ea7cf064e..22595b42c7 100644 --- a/tests/functional/u/undefined/undefined_variable.py +++ b/tests/functional/u/undefined/undefined_variable.py @@ -133,7 +133,7 @@ class Ancestor1(object): """ No op """ NANA = BAT # [undefined-variable] -del BAT +del BAT # [undefined-variable] class KeywordArgument(object): @@ -356,3 +356,12 @@ def global_var_mixed_assignment(): GLOBAL_VAR: int GLOBAL_VAR_TWO: int + + +class RepeatedReturnAnnotations: + def x(self, o: RepeatedReturnAnnotations) -> bool: # [undefined-variable] + pass + def y(self) -> RepeatedReturnAnnotations: # [undefined-variable] + pass + def z(self) -> RepeatedReturnAnnotations: # [undefined-variable] + pass diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt index ad857b09b8..74ec800885 100644 --- a/tests/functional/u/undefined/undefined_variable.txt +++ b/tests/functional/u/undefined/undefined_variable.txt @@ -19,6 +19,7 @@ used-before-assignment:98:26:98:35:TestClass.MissingAncestor:Using variable 'Anc used-before-assignment:105:36:105:41:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment:HIGH undefined-variable:119:10:119:14:Self:Undefined variable 'Self':UNDEFINED undefined-variable:135:7:135:10::Undefined variable 'BAT':UNDEFINED +undefined-variable:136:4:136:7::Undefined variable 'BAT':UNDEFINED used-before-assignment:146:31:146:38:KeywordArgument.test1:Using variable 'enabled' before assignment:HIGH undefined-variable:149:32:149:40:KeywordArgument.test2:Undefined variable 'disabled':UNDEFINED undefined-variable:154:22:154:25:KeywordArgument.:Undefined variable 'arg':UNDEFINED @@ -33,3 +34,6 @@ used-before-assignment:294:7:294:8:undefined_annotation:Using variable 'x' befor undefined-variable:324:11:324:12:decorated3:Undefined variable 'x':UNDEFINED undefined-variable:329:19:329:20:decorated4:Undefined variable 'y':UNDEFINED undefined-variable:350:10:350:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':HIGH +undefined-variable:362:19:362:44:RepeatedReturnAnnotations.x:Undefined variable 'RepeatedReturnAnnotations':UNDEFINED +undefined-variable:364:19:364:44:RepeatedReturnAnnotations.y:Undefined variable 'RepeatedReturnAnnotations':UNDEFINED +undefined-variable:366:19:366:44:RepeatedReturnAnnotations.z:Undefined variable 'RepeatedReturnAnnotations':UNDEFINED diff --git a/tests/functional/u/use/used_before_assignment_py37.py b/tests/functional/u/use/used_before_assignment_py37.py index e17c345a90..08a585a95b 100644 --- a/tests/functional/u/use/used_before_assignment_py37.py +++ b/tests/functional/u/use/used_before_assignment_py37.py @@ -14,7 +14,7 @@ def second_correct_typing_method(self, other: List[MyClass]) -> bool: return self == other[0] def incorrect_default_method( - self, other=MyClass() # [used-before-assignment] + self, other=MyClass() # [undefined-variable] ) -> bool: return self == other diff --git a/tests/functional/u/use/used_before_assignment_py37.txt b/tests/functional/u/use/used_before_assignment_py37.txt index 72961f9d61..ad06560e21 100644 --- a/tests/functional/u/use/used_before_assignment_py37.txt +++ b/tests/functional/u/use/used_before_assignment_py37.txt @@ -1 +1 @@ -used-before-assignment:17:20:17:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:HIGH +undefined-variable:17:20:17:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED diff --git a/tests/functional/u/use/used_before_assignment_typing.py b/tests/functional/u/use/used_before_assignment_typing.py index 33d81356e6..9be01d7706 100644 --- a/tests/functional/u/use/used_before_assignment_typing.py +++ b/tests/functional/u/use/used_before_assignment_typing.py @@ -9,17 +9,17 @@ class MyClass: """Type annotation or default values for first level methods can't refer to their own class""" def incorrect_typing_method( - self, other: MyClass # [used-before-assignment] + self, other: MyClass # [undefined-variable] ) -> bool: return self == other def incorrect_nested_typing_method( - self, other: List[MyClass] # [used-before-assignment] + self, other: List[MyClass] # [undefined-variable] ) -> bool: return self == other[0] def incorrect_default_method( - self, other=MyClass() # [used-before-assignment] + self, other=MyClass() # [undefined-variable] ) -> bool: return self == other diff --git a/tests/functional/u/use/used_before_assignment_typing.txt b/tests/functional/u/use/used_before_assignment_typing.txt index a35ac263cb..ae05b23f38 100644 --- a/tests/functional/u/use/used_before_assignment_typing.txt +++ b/tests/functional/u/use/used_before_assignment_typing.txt @@ -1,3 +1,3 @@ -used-before-assignment:12:21:12:28:MyClass.incorrect_typing_method:Using variable 'MyClass' before assignment:HIGH -used-before-assignment:17:26:17:33:MyClass.incorrect_nested_typing_method:Using variable 'MyClass' before assignment:HIGH -used-before-assignment:22:20:22:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:HIGH +undefined-variable:12:21:12:28:MyClass.incorrect_typing_method:Undefined variable 'MyClass':UNDEFINED +undefined-variable:17:26:17:33:MyClass.incorrect_nested_typing_method:Undefined variable 'MyClass':UNDEFINED +undefined-variable:22:20:22:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED From 8b0b573bd8d82822834e7a16ae5546561819aa97 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 30 Jan 2022 01:09:40 -0500 Subject: [PATCH 181/357] Remove moot TODO (#5747) Changing the message type occurred in 0fb6a120c6f0fbc94e305542fe29e790274be096 and updating this unit test to match took place in fa2b6ceca796311a918e28770d101b53a43f84f3. --- tests/functional/u/undefined/undefined_variable_py38.py | 3 +-- tests/functional/u/undefined/undefined_variable_py38.txt | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index 6e640421b6..f234135431 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -14,7 +14,7 @@ def typing_and_assignment_expression(): def typing_and_self_referencing_assignment_expression(): """The variable gets assigned in an assignment expression that references itself""" var: int - if (var := var ** 2): # false negative--walrus operator! + if (var := var ** 2): # false negative: https://github.com/PyCQA/pylint/issues/5653 print(var) @@ -128,7 +128,6 @@ def type_annotation_unused_after_comprehension(): def type_annotation_used_improperly_after_comprehension(): - # TO-DO follow up in https://github.com/PyCQA/pylint/issues/5713 """https://github.com/PyCQA/pylint/issues/5654""" my_int: int _ = [print(sep=my_int, end=my_int) for my_int in range(10)] diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index 8969423ad0..43a2b68290 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -3,5 +3,5 @@ undefined-variable:50:6:50:22::Undefined variable 'again_no_default':UNDEFINED undefined-variable:76:6:76:19::Undefined variable 'else_assign_1':INFERENCE undefined-variable:99:6:99:19::Undefined variable 'else_assign_2':INFERENCE unused-variable:126:4:126:10:type_annotation_unused_after_comprehension:Unused variable 'my_int':UNDEFINED -used-before-assignment:135:10:135:16:type_annotation_used_improperly_after_comprehension:Using variable 'my_int' before assignment:HIGH -used-before-assignment:142:10:142:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH +used-before-assignment:134:10:134:16:type_annotation_used_improperly_after_comprehension:Using variable 'my_int' before assignment:HIGH +used-before-assignment:141:10:141:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH From c2a3e84f32559c434208ae1eede65ad5ca767b8d Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 30 Jan 2022 08:24:28 +0100 Subject: [PATCH 182/357] Clearer error message for useless-else-x type messages (#5736) * Clearer error message for useless-else-x type message See https://github.com/PyCQA/pylint/pull/5614#issuecomment-1023614434 for rationale. Closes #5598 Co-authored-by: Jacob Walls --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/base.py | 3 ++- .../checkers/refactoring/refactoring_checker.py | 17 +++++++++-------- pylint/extensions/check_elif.py | 2 +- tests/functional/ext/check_elif/check_elif.txt | 8 ++++---- tests/functional/n/no/no_else_break.txt | 14 +++++++------- tests/functional/n/no/no_else_continue.txt | 14 +++++++------- tests/functional/n/no/no_else_raise.txt | 14 +++++++------- tests/functional/n/no/no_else_return.txt | 14 +++++++------- .../u/useless/useless_else_on_loop.txt | 12 ++++++------ 11 files changed, 58 insertions(+), 48 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3674ef5113..7148e16067 100644 --- a/ChangeLog +++ b/ChangeLog @@ -42,6 +42,10 @@ Release date: TBA Closes #5281 +* Better warning messages for useless else or elif when a function returns early. + + Closes #5614 + * Fixed false positive ``consider-using-dict-comprehension`` when creating a dict using a list of tuples where key AND value vary depending on the same condition. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 3c419a5c66..e783e32faf 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -91,6 +91,10 @@ Other Changes Closes #5648 +* Better warning messages for useless else or elif when a function returns early. + + Closes #5614 + * Fixed false positive ``consider-using-dict-comprehension`` when creating a dict using a list of tuples where key AND value vary depending on the same condition. diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index c14d63c5a4..97e5941a62 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -521,7 +521,8 @@ class BasicErrorChecker(_BasicChecker): "has abstract methods and is instantiated.", ), "W0120": ( - "Else clause on loop without a break statement", + "Else clause on loop without a break statement, remove the else and" + " de-indent all the code inside it", "useless-else-on-loop", "Loops should only have an else clause if they can exit early " "with a break statement, otherwise the statements under else " diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 7c922f6d44..25313494bb 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -239,7 +239,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): "with statement assignment and exception handler assignment.", ), "R1705": ( - 'Unnecessary "%s" after "return"', + 'Unnecessary "%s" after "return", %s', "no-else-return", "Used in order to highlight an unnecessary block of " "code following an if containing a return statement. " @@ -339,7 +339,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): "Used when an if expression can be replaced with 'bool(test)'.", ), "R1720": ( - 'Unnecessary "%s" after "raise"', + 'Unnecessary "%s" after "raise", %s', "no-else-raise", "Used in order to highlight an unnecessary block of " "code following an if containing a raise statement. " @@ -360,7 +360,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): "Instead of using exit() or quit(), consider using the sys.exit().", ), "R1723": ( - 'Unnecessary "%s" after "break"', + 'Unnecessary "%s" after "break", %s', "no-else-break", "Used in order to highlight an unnecessary block of " "code following an if containing a break statement. " @@ -369,7 +369,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): "break statement.", ), "R1724": ( - 'Unnecessary "%s" after "continue"', + 'Unnecessary "%s" after "continue", %s', "no-else-continue", "Used in order to highlight an unnecessary block of " "code following an if containing a continue statement. " @@ -667,10 +667,11 @@ def _check_superfluous_else(self, node, msg_id, returning_node_class): if _if_statement_is_always_returning(node, returning_node_class): orelse = node.orelse[0] - followed_by_elif = (orelse.lineno, orelse.col_offset) in self._elifs - self.add_message( - msg_id, node=node, args="elif" if followed_by_elif else "else" - ) + if (orelse.lineno, orelse.col_offset) in self._elifs: + args = ("elif", 'remove the leading "el" from "elif"') + else: + args = ("else", 'remove the "else" and de-indent the code inside it') + self.add_message(msg_id, node=node, args=args) def _check_superfluous_else_return(self, node): return self._check_superfluous_else( diff --git a/pylint/extensions/check_elif.py b/pylint/extensions/check_elif.py index b4658577e0..1fb6323b26 100644 --- a/pylint/extensions/check_elif.py +++ b/pylint/extensions/check_elif.py @@ -31,7 +31,7 @@ class ElseifUsedChecker(BaseTokenChecker): name = "else_if_used" msgs = { "R5501": ( - 'Consider using "elif" instead of "else if"', + 'Consider using "elif" instead of "else" then "if" to remove one indentation level', "else-if-used", "Used when an else statement is immediately followed by " "an if statement and does not contain statements that " diff --git a/tests/functional/ext/check_elif/check_elif.txt b/tests/functional/ext/check_elif/check_elif.txt index 7da5aa8845..2874795e58 100644 --- a/tests/functional/ext/check_elif/check_elif.txt +++ b/tests/functional/ext/check_elif/check_elif.txt @@ -1,4 +1,4 @@ -else-if-used:13:8:30:25:my_function:"Consider using ""elif"" instead of ""else if""":HIGH -else-if-used:25:20:26:28:my_function:"Consider using ""elif"" instead of ""else if""":HIGH -else-if-used:44:8:48:26:_if_in_fstring_comprehension_with_elif:"Consider using ""elif"" instead of ""else if""":HIGH -else-if-used:47:12:48:26:_if_in_fstring_comprehension_with_elif:"Consider using ""elif"" instead of ""else if""":HIGH +else-if-used:13:8:30:25:my_function:"Consider using ""elif"" instead of ""else"" then ""if"" to remove one indentation level":HIGH +else-if-used:25:20:26:28:my_function:"Consider using ""elif"" instead of ""else"" then ""if"" to remove one indentation level":HIGH +else-if-used:44:8:48:26:_if_in_fstring_comprehension_with_elif:"Consider using ""elif"" instead of ""else"" then ""if"" to remove one indentation level":HIGH +else-if-used:47:12:48:26:_if_in_fstring_comprehension_with_elif:"Consider using ""elif"" instead of ""else"" then ""if"" to remove one indentation level":HIGH diff --git a/tests/functional/n/no/no_else_break.txt b/tests/functional/n/no/no_else_break.txt index 1eb2ccbbea..cf045f367b 100644 --- a/tests/functional/n/no/no_else_break.txt +++ b/tests/functional/n/no/no_else_break.txt @@ -1,7 +1,7 @@ -no-else-break:8:8:11:17:foo1:"Unnecessary ""else"" after ""break""":UNDEFINED -no-else-break:16:8:21:17:foo2:"Unnecessary ""elif"" after ""break""":UNDEFINED -no-else-break:28:12:33:21:foo3:"Unnecessary ""else"" after ""break""":UNDEFINED -no-else-break:41:8:48:17:foo4:"Unnecessary ""else"" after ""break""":UNDEFINED -no-else-break:54:8:63:17:foo5:"Unnecessary ""elif"" after ""break""":UNDEFINED -no-else-break:70:12:74:21:foo6:"Unnecessary ""else"" after ""break""":UNDEFINED -no-else-break:110:8:116:21:bar4:"Unnecessary ""else"" after ""break""":UNDEFINED +no-else-break:8:8:11:17:foo1:"Unnecessary ""else"" after ""break"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-break:16:8:21:17:foo2:"Unnecessary ""elif"" after ""break"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-break:28:12:33:21:foo3:"Unnecessary ""else"" after ""break"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-break:41:8:48:17:foo4:"Unnecessary ""else"" after ""break"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-break:54:8:63:17:foo5:"Unnecessary ""elif"" after ""break"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-break:70:12:74:21:foo6:"Unnecessary ""else"" after ""break"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-break:110:8:116:21:bar4:"Unnecessary ""else"" after ""break"", remove the ""else"" and de-indent the code inside it":UNDEFINED diff --git a/tests/functional/n/no/no_else_continue.txt b/tests/functional/n/no/no_else_continue.txt index ad22f0e82e..f0e813e405 100644 --- a/tests/functional/n/no/no_else_continue.txt +++ b/tests/functional/n/no/no_else_continue.txt @@ -1,7 +1,7 @@ -no-else-continue:8:8:11:17:foo1:"Unnecessary ""else"" after ""continue""":UNDEFINED -no-else-continue:16:8:21:17:foo2:"Unnecessary ""elif"" after ""continue""":UNDEFINED -no-else-continue:28:12:33:24:foo3:"Unnecessary ""else"" after ""continue""":UNDEFINED -no-else-continue:41:8:48:17:foo4:"Unnecessary ""else"" after ""continue""":UNDEFINED -no-else-continue:54:8:63:17:foo5:"Unnecessary ""elif"" after ""continue""":UNDEFINED -no-else-continue:70:12:74:21:foo6:"Unnecessary ""else"" after ""continue""":UNDEFINED -no-else-continue:110:8:116:24:bar4:"Unnecessary ""else"" after ""continue""":UNDEFINED +no-else-continue:8:8:11:17:foo1:"Unnecessary ""else"" after ""continue"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-continue:16:8:21:17:foo2:"Unnecessary ""elif"" after ""continue"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-continue:28:12:33:24:foo3:"Unnecessary ""else"" after ""continue"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-continue:41:8:48:17:foo4:"Unnecessary ""else"" after ""continue"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-continue:54:8:63:17:foo5:"Unnecessary ""elif"" after ""continue"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-continue:70:12:74:21:foo6:"Unnecessary ""else"" after ""continue"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-continue:110:8:116:24:bar4:"Unnecessary ""else"" after ""continue"", remove the ""else"" and de-indent the code inside it":UNDEFINED diff --git a/tests/functional/n/no/no_else_raise.txt b/tests/functional/n/no/no_else_raise.txt index 3906c34040..72f8f83766 100644 --- a/tests/functional/n/no/no_else_raise.txt +++ b/tests/functional/n/no/no_else_raise.txt @@ -1,7 +1,7 @@ -no-else-raise:6:4:11:26:foo1:"Unnecessary ""else"" after ""raise""":UNDEFINED -no-else-raise:15:4:23:26:foo2:"Unnecessary ""elif"" after ""raise""":UNDEFINED -no-else-raise:29:8:34:30:foo3:"Unnecessary ""else"" after ""raise""":UNDEFINED -no-else-raise:41:4:48:13:foo4:"Unnecessary ""else"" after ""raise""":UNDEFINED -no-else-raise:53:4:62:13:foo5:"Unnecessary ""elif"" after ""raise""":UNDEFINED -no-else-raise:68:8:72:17:foo6:"Unnecessary ""else"" after ""raise""":UNDEFINED -no-else-raise:104:4:110:33:bar4:"Unnecessary ""else"" after ""raise""":UNDEFINED +no-else-raise:6:4:11:26:foo1:"Unnecessary ""else"" after ""raise"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-raise:15:4:23:26:foo2:"Unnecessary ""elif"" after ""raise"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-raise:29:8:34:30:foo3:"Unnecessary ""else"" after ""raise"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-raise:41:4:48:13:foo4:"Unnecessary ""else"" after ""raise"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-raise:53:4:62:13:foo5:"Unnecessary ""elif"" after ""raise"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-raise:68:8:72:17:foo6:"Unnecessary ""else"" after ""raise"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-raise:104:4:110:33:bar4:"Unnecessary ""else"" after ""raise"", remove the ""else"" and de-indent the code inside it":UNDEFINED diff --git a/tests/functional/n/no/no_else_return.txt b/tests/functional/n/no/no_else_return.txt index 7b5f0e0f36..c73b177da5 100644 --- a/tests/functional/n/no/no_else_return.txt +++ b/tests/functional/n/no/no_else_return.txt @@ -1,7 +1,7 @@ -no-else-return:6:4:11:16:foo1:"Unnecessary ""else"" after ""return""":UNDEFINED -no-else-return:15:4:23:16:foo2:"Unnecessary ""elif"" after ""return""":UNDEFINED -no-else-return:29:8:34:20:foo3:"Unnecessary ""else"" after ""return""":UNDEFINED -no-else-return:41:4:48:13:foo4:"Unnecessary ""else"" after ""return""":UNDEFINED -no-else-return:53:4:62:13:foo5:"Unnecessary ""elif"" after ""return""":UNDEFINED -no-else-return:68:8:72:17:foo6:"Unnecessary ""else"" after ""return""":UNDEFINED -no-else-return:104:4:110:23:bar4:"Unnecessary ""else"" after ""return""":UNDEFINED +no-else-return:6:4:11:16:foo1:"Unnecessary ""else"" after ""return"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-return:15:4:23:16:foo2:"Unnecessary ""elif"" after ""return"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-return:29:8:34:20:foo3:"Unnecessary ""else"" after ""return"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-return:41:4:48:13:foo4:"Unnecessary ""else"" after ""return"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-return:53:4:62:13:foo5:"Unnecessary ""elif"" after ""return"", remove the leading ""el"" from ""elif""":UNDEFINED +no-else-return:68:8:72:17:foo6:"Unnecessary ""else"" after ""return"", remove the ""else"" and de-indent the code inside it":UNDEFINED +no-else-return:104:4:110:23:bar4:"Unnecessary ""else"" after ""return"", remove the ""else"" and de-indent the code inside it":UNDEFINED diff --git a/tests/functional/u/useless/useless_else_on_loop.txt b/tests/functional/u/useless/useless_else_on_loop.txt index ac8117ac72..938f24cc51 100644 --- a/tests/functional/u/useless/useless_else_on_loop.txt +++ b/tests/functional/u/useless/useless_else_on_loop.txt @@ -1,6 +1,6 @@ -useless-else-on-loop:10:4:11:31:test_return_for:Else clause on loop without a break statement:UNDEFINED -useless-else-on-loop:18:4:19:31:test_return_while:Else clause on loop without a break statement:UNDEFINED -useless-else-on-loop:28:0:29:21::Else clause on loop without a break statement:UNDEFINED -useless-else-on-loop:35:0:36:21::Else clause on loop without a break statement:UNDEFINED -useless-else-on-loop:40:0:43:13::Else clause on loop without a break statement:UNDEFINED -useless-else-on-loop:87:4:88:19:test_break_in_orelse_deep2:Else clause on loop without a break statement:UNDEFINED +useless-else-on-loop:10:4:11:31:test_return_for:Else clause on loop without a break statement, remove the else and de-indent all the code inside it:UNDEFINED +useless-else-on-loop:18:4:19:31:test_return_while:Else clause on loop without a break statement, remove the else and de-indent all the code inside it:UNDEFINED +useless-else-on-loop:28:0:29:21::Else clause on loop without a break statement, remove the else and de-indent all the code inside it:UNDEFINED +useless-else-on-loop:35:0:36:21::Else clause on loop without a break statement, remove the else and de-indent all the code inside it:UNDEFINED +useless-else-on-loop:40:0:43:13::Else clause on loop without a break statement, remove the else and de-indent all the code inside it:UNDEFINED +useless-else-on-loop:87:4:88:19:test_break_in_orelse_deep2:Else clause on loop without a break statement, remove the else and de-indent all the code inside it:UNDEFINED From d31ae0b7b622e3617c853c1ca3617ebadf2a5608 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:13:28 +0100 Subject: [PATCH 183/357] Bump black from 21.12b0 to 22.1.0 (#5750) Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits/22.1.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test_pre_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 37ad883f5d..15ab3bd945 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,6 +1,6 @@ # Everything in this file should reflect the pre-commit configuration # in .pre-commit-config.yaml -black==21.12b0 +black==22.1.0 flake8==4.0.1 flake8-typing-imports==1.12.0 isort==5.10.1 From 1d2c51cb0d9a43a63eb78414ae0bddc3e9f9f855 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 31 Jan 2022 21:28:22 +0100 Subject: [PATCH 184/357] Add timeouts for CI jobs (#5752) --- .github/workflows/ci.yaml | 11 +++++++++++ .github/workflows/primer-test.yaml | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 56ff5a1c21..e9c4fdaeb0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,6 +17,7 @@ jobs: prepare-base: name: Prepare base dependencies runs-on: ubuntu-latest + timeout-minutes: 5 outputs: python-key: ${{ steps.generate-python-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} @@ -76,6 +77,7 @@ jobs: formatting: name: checks / pre-commit runs-on: ubuntu-latest + timeout-minutes: 5 needs: prepare-base steps: - name: Check out code from GitHub @@ -118,6 +120,7 @@ jobs: spelling: name: checks / spelling runs-on: ubuntu-latest + timeout-minutes: 5 needs: prepare-base steps: - name: Check out code from GitHub @@ -148,6 +151,7 @@ jobs: prepare-tests-linux: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 5 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -190,6 +194,7 @@ jobs: pytest-linux: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 10 needs: prepare-tests-linux strategy: fail-fast: false @@ -229,6 +234,7 @@ jobs: coverage: name: tests / process / coverage runs-on: ubuntu-latest + timeout-minutes: 5 needs: ["prepare-tests-linux", "pytest-linux"] strategy: matrix: @@ -273,6 +279,7 @@ jobs: benchmark-linux: name: tests / run benchmark / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 5 needs: prepare-tests-linux strategy: fail-fast: false @@ -324,6 +331,7 @@ jobs: prepare-tests-windows: name: tests / prepare / ${{ matrix.python-version }} / Windows runs-on: windows-latest + timeout-minutes: 5 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -366,6 +374,7 @@ jobs: pytest-windows: name: tests / run / ${{ matrix.python-version }} / Windows runs-on: windows-latest + timeout-minutes: 10 needs: prepare-tests-windows strategy: fail-fast: false @@ -404,6 +413,7 @@ jobs: prepare-tests-pypy: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 5 strategy: matrix: python-version: ["pypy3"] @@ -446,6 +456,7 @@ jobs: pytest-pypy: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 10 needs: prepare-tests-pypy strategy: fail-fast: false diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index 44ee898b94..de23fe9078 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -24,6 +24,7 @@ jobs: prepare-tests-linux: name: prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 5 strategy: matrix: python-version: [3.8, 3.9, "3.10"] @@ -66,6 +67,7 @@ jobs: pytest-primer-stdlib: name: run on stdlib / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 10 needs: prepare-tests-linux strategy: matrix: @@ -100,6 +102,7 @@ jobs: pytest-primer-external-batch-one: name: run on batch one / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 60 needs: prepare-tests-linux strategy: matrix: @@ -134,6 +137,7 @@ jobs: pytest-primer-external-batch-two: name: run on batch two / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest + timeout-minutes: 60 needs: prepare-tests-linux strategy: matrix: From 1c884efd0ddcd2657e7b2d77c50d36f436a8f0ac Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 31 Jan 2022 22:53:15 +0100 Subject: [PATCH 185/357] Improve error message arguments formatting [redefined-slots] (#5754) --- pylint/checkers/classes/class_checker.py | 2 +- tests/functional/r/redefined_slots.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index a33efb910c..e8c8ad607c 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1397,7 +1397,7 @@ def _check_redefined_slots( if redefined_slots: self.add_message( "redefined-slots-in-subclass", - args=", ".join(name for name in slots_names if name in redefined_slots), + args=([name for name in slots_names if name in redefined_slots],), node=slots_node, ) diff --git a/tests/functional/r/redefined_slots.txt b/tests/functional/r/redefined_slots.txt index 48cb9ea983..f5d7ee2620 100644 --- a/tests/functional/r/redefined_slots.txt +++ b/tests/functional/r/redefined_slots.txt @@ -1,2 +1,2 @@ -redefined-slots-in-subclass:15:16:15:47:Subclass1:Redefined slots 'a, deque' in subclass:UNDEFINED -redefined-slots-in-subclass:33:16:33:61:Subclass3:Redefined slots 'a, b, i, j, k' in subclass:UNDEFINED +redefined-slots-in-subclass:15:16:15:47:Subclass1:Redefined slots ['a', 'deque'] in subclass:UNDEFINED +redefined-slots-in-subclass:33:16:33:61:Subclass3:Redefined slots ['a', 'b', 'i', 'j', 'k'] in subclass:UNDEFINED From ade493aadfe7c62ca1cbdf0cb6e00e49dfdb4497 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 31 Jan 2022 23:02:15 +0100 Subject: [PATCH 186/357] Increase timeouts (#5755) --- .github/workflows/ci.yaml | 20 ++++++++++---------- .github/workflows/primer-test.yaml | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e9c4fdaeb0..a007ca9476 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: prepare-base: name: Prepare base dependencies runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 15 outputs: python-key: ${{ steps.generate-python-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} @@ -77,7 +77,7 @@ jobs: formatting: name: checks / pre-commit runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 needs: prepare-base steps: - name: Check out code from GitHub @@ -120,7 +120,7 @@ jobs: spelling: name: checks / spelling runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 needs: prepare-base steps: - name: Check out code from GitHub @@ -151,7 +151,7 @@ jobs: prepare-tests-linux: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 15 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -194,7 +194,7 @@ jobs: pytest-linux: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 30 needs: prepare-tests-linux strategy: fail-fast: false @@ -279,7 +279,7 @@ jobs: benchmark-linux: name: tests / run benchmark / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 20 needs: prepare-tests-linux strategy: fail-fast: false @@ -331,7 +331,7 @@ jobs: prepare-tests-windows: name: tests / prepare / ${{ matrix.python-version }} / Windows runs-on: windows-latest - timeout-minutes: 5 + timeout-minutes: 15 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -374,7 +374,7 @@ jobs: pytest-windows: name: tests / run / ${{ matrix.python-version }} / Windows runs-on: windows-latest - timeout-minutes: 10 + timeout-minutes: 30 needs: prepare-tests-windows strategy: fail-fast: false @@ -413,7 +413,7 @@ jobs: prepare-tests-pypy: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 15 strategy: matrix: python-version: ["pypy3"] @@ -456,7 +456,7 @@ jobs: pytest-pypy: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 30 needs: prepare-tests-pypy strategy: fail-fast: false diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index de23fe9078..f3f02e904d 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -24,7 +24,7 @@ jobs: prepare-tests-linux: name: prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 15 strategy: matrix: python-version: [3.8, 3.9, "3.10"] @@ -67,7 +67,7 @@ jobs: pytest-primer-stdlib: name: run on stdlib / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 needs: prepare-tests-linux strategy: matrix: @@ -102,7 +102,7 @@ jobs: pytest-primer-external-batch-one: name: run on batch one / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 90 needs: prepare-tests-linux strategy: matrix: @@ -137,7 +137,7 @@ jobs: pytest-primer-external-batch-two: name: run on batch two / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 90 needs: prepare-tests-linux strategy: matrix: From 1a8de8145386e7ee839123cf1d2c4ab23df7a2e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Feb 2022 07:55:25 +0100 Subject: [PATCH 187/357] [pre-commit.ci] pre-commit autoupdate (#5758) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 21.12b0 → 22.1.0](https://github.com/psf/black/compare/21.12b0...22.1.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 2 +- pylint/checkers/classes/class_checker.py | 12 ++++------ pylint/checkers/imports.py | 2 +- pylint/checkers/misc.py | 4 ++-- pylint/checkers/spelling.py | 2 +- pylint/extensions/_check_docs_utils.py | 28 ++++++++++++------------ pylint/pyreverse/dot_printer.py | 2 +- pylint/pyreverse/utils.py | 2 +- pylint/pyreverse/vcg_printer.py | 10 ++++----- pylint/utils/pragma_parser.py | 2 +- tests/pyreverse/test_utils.py | 2 +- tests/test_self.py | 2 +- 12 files changed, 33 insertions(+), 37 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab2d8a9d1b..ad7a5a1c2f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.1.0 hooks: - id: black args: [--safe, --quiet] diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index e8c8ad607c..682c79a16f 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -979,14 +979,10 @@ def _check_unused_private_attributes(self, node: nodes.ClassDef) -> None: if self._is_type_self_call(attribute.expr): continue - if ( - assign_attr.expr.name - in { - "cls", - node.name, - } - and attribute.expr.name in {"cls", "self", node.name} - ): + if assign_attr.expr.name in { + "cls", + node.name, + } and attribute.expr.name in {"cls", "self", node.name}: # If assigned to cls or class name, can be accessed by cls/self/class name break diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 951504324d..3f639eef0c 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -162,7 +162,7 @@ def _repr_tree_defs(data, indent_str=None): lines.append(f"{mod} {files}") sub_indent_str = " " else: - lines.append(fr"{indent_str}\-{mod} {files}") + lines.append(rf"{indent_str}\-{mod} {files}") if i == len(nodes_items) - 1: sub_indent_str = f"{indent_str} " else: diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index 4eb52336db..5c3a9ab64f 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -121,9 +121,9 @@ def open(self): notes = "|".join(re.escape(note) for note in self.config.notes) if self.config.notes_rgx: - regex_string = fr"#\s*({notes}|{self.config.notes_rgx})\b" + regex_string = rf"#\s*({notes}|{self.config.notes_rgx})\b" else: - regex_string = fr"#\s*({notes})\b" + regex_string = rf"#\s*({notes})\b" self._fixme_pattern = re.compile(regex_string, re.I) diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index 752eb83e3c..777153c64d 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -409,7 +409,7 @@ def _check_spelling(self, msgid, line, line_num): suggestions = self.spelling_dict.suggest(word) del suggestions[self.config.max_spelling_suggestions :] line_segment = line[word_start_at:] - match = re.search(fr"(\W|^)({word})(\W|$)", line_segment) + match = re.search(rf"(\W|^)({word})(\W|$)", line_segment) if match: # Start position of second group in regex. col = match.regs[2][0] diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index f6fa347240..59f5dd9928 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -256,7 +256,7 @@ class SphinxDocstring(Docstring): \w(?:\w|\.[^\.])* # Valid python name """ - re_simple_container_type = fr""" + re_simple_container_type = rf""" {re_type} # a container type [\(\[] [^\n\s]+ [\)\]] # with the contents of the container """ @@ -268,12 +268,12 @@ class SphinxDocstring(Docstring): type=re_type, container_type=re_simple_container_type ) - re_xref = fr""" + re_xref = rf""" (?::\w+:)? # optional tag `{re_type}` # what to reference """ - re_param_raw = fr""" + re_param_raw = rf""" : # initial colon (?: # Sphinx keywords param|parameter| @@ -293,7 +293,7 @@ class SphinxDocstring(Docstring): """ re_param_in_docstring = re.compile(re_param_raw, re.X | re.S) - re_type_raw = fr""" + re_type_raw = rf""" :type # Sphinx keyword \s+ # whitespace ({re_multiple_simple_type}) # Parameter name @@ -302,14 +302,14 @@ class SphinxDocstring(Docstring): """ re_type_in_docstring = re.compile(re_type_raw, re.X | re.S) - re_property_type_raw = fr""" + re_property_type_raw = rf""" :type: # Sphinx keyword \s+ # whitespace {re_multiple_simple_type} # type declaration """ re_property_type_in_docstring = re.compile(re_property_type_raw, re.X | re.S) - re_raise_raw = fr""" + re_raise_raw = rf""" : # initial colon (?: # Sphinx keyword raises?| @@ -456,7 +456,7 @@ class GoogleDocstring(Docstring): re_xref = SphinxDocstring.re_xref - re_container_type = fr""" + re_container_type = rf""" (?:{re_type}|{re_xref}) # a container type [\(\[] [^\n]+ [\)\]] # with the contents of the container """ @@ -484,7 +484,7 @@ class GoogleDocstring(Docstring): ) re_param_line = re.compile( - fr""" + rf""" \s* (\*{{0,2}}\w+) # identifier potentially with asterisks \s* ( [(] {re_multiple_type} @@ -500,7 +500,7 @@ class GoogleDocstring(Docstring): ) re_raise_line = re.compile( - fr""" + rf""" \s* ({re_multiple_type}) \s* : # identifier \s* (.*) # beginning of optional description """, @@ -512,7 +512,7 @@ class GoogleDocstring(Docstring): ) re_returns_line = re.compile( - fr""" + rf""" \s* ({re_multiple_type}:)? # identifier \s* (.*) # beginning of description """, @@ -520,7 +520,7 @@ class GoogleDocstring(Docstring): ) re_property_returns_line = re.compile( - fr""" + rf""" ^{re_multiple_type}: # identifier \s* (.*) # Summary line / description """, @@ -740,7 +740,7 @@ class NumpyDocstring(GoogleDocstring): ) re_param_line = re.compile( - fr""" + rf""" \s* (\*{{0,2}}\w+)(\s?(:|\n)) # identifier with potential asterisks \s* (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?\n)? # optional type declaration \s* (.*) # optional description @@ -753,7 +753,7 @@ class NumpyDocstring(GoogleDocstring): ) re_raise_line = re.compile( - fr""" + rf""" \s* ({GoogleDocstring.re_type})$ # type declaration \s* (.*) # optional description """, @@ -765,7 +765,7 @@ class NumpyDocstring(GoogleDocstring): ) re_returns_line = re.compile( - fr""" + rf""" \s* (?:\w+\s+:\s+)? # optional name ({GoogleDocstring.re_multiple_type})$ # type declaration \s* (.*) # optional description diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index fb7f3d8bc2..59865a0ab5 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -105,7 +105,7 @@ def _build_label_for_node( methods: List[nodes.FunctionDef] = properties.methods or [] for func in methods: args = self._get_method_arguments(func) - label += fr"{func.name}({', '.join(args)})" + label += rf"{func.name}({', '.join(args)})" if func.returns: label += ": " + get_annotation_label(func.returns) label += r"\l" diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 454b9dfc9e..594c52c5b6 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -258,7 +258,7 @@ def get_annotation( label = get_annotation_label(ann) if ann: label = ( - fr"Optional[{label}]" + rf"Optional[{label}]" if getattr(default, "value", "value") is None and not label.startswith("Optional") else label diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index bbc131760d..ccaa553212 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -228,7 +228,7 @@ def emit_node( @staticmethod def _build_label_for_node(properties: NodeProperties) -> str: fontcolor = "\f09" if properties.fontcolor == "red" else "" - label = fr"\fb{fontcolor}{properties.label}\fn" + label = rf"\fb{fontcolor}{properties.label}\fn" if properties.attrs is None and properties.methods is None: # return a compact form which only displays the classname in a box return label @@ -238,13 +238,13 @@ def _build_label_for_node(properties: NodeProperties) -> str: # box width for UML like diagram maxlen = max(len(name) for name in [properties.label] + method_names + attrs) line = "_" * (maxlen + 2) - label = fr"{label}\n\f{line}" + label = rf"{label}\n\f{line}" for attr in attrs: - label = fr"{label}\n\f08{attr}" + label = rf"{label}\n\f08{attr}" if attrs: - label = fr"{label}\n\f{line}" + label = rf"{label}\n\f{line}" for func in method_names: - label = fr"{label}\n\f10{func}()" + label = rf"{label}\n\f10{func}()" return label def emit_edge( diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index df506465db..5ef4ef481a 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -40,7 +40,7 @@ TOKEN_SPECIFICATION = [ - ("KEYWORD", fr"\b({ALL_KEYWORDS:s})\b"), + ("KEYWORD", rf"\b({ALL_KEYWORDS:s})\b"), ("MESSAGE_STRING", r"[0-9A-Za-z\-\_]{2,}"), # Identifiers ("ASSIGN", r"="), # Assignment operator ("MESSAGE_NUMBER", r"[CREIWF]{1}\d*"), diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index 94ee858e09..c79d43b77e 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -68,7 +68,7 @@ def test_get_annotation_annassign(assign, label): ) def test_get_annotation_assignattr(init_method, label): """AssignAttr""" - assign = fr""" + assign = rf""" class A: {init_method} """ diff --git a/tests/test_self.py b/tests/test_self.py index f4910168b1..5703cacfe3 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -246,7 +246,7 @@ def test_generate_config_disable_symbolic_names(self) -> None: output = out.getvalue() # Get rid of the pesky messages that pylint emits if the # configuration file is not found. - pattern = fr"\[{MAIN_CHECKER_NAME.upper()}" + pattern = rf"\[{MAIN_CHECKER_NAME.upper()}" master = re.search(pattern, output) assert master is not None, f"{pattern} not found in {output}" out = StringIO(output[master.start() :]) From f7ba0dcfce82dbbd3ef16e3c3a63e67f5f2d823d Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 1 Feb 2022 13:50:20 -0500 Subject: [PATCH 188/357] Remove VariableVisitConsumerAction.CONSUME (#5745) --- pylint/checkers/variables.py | 68 ++++++++++++------------------------ 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index e6d6f5a632..cf4af384cd 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -60,7 +60,6 @@ import itertools import os import re -import sys from enum import Enum from functools import lru_cache from typing import ( @@ -94,11 +93,6 @@ if TYPE_CHECKING: from pylint.lint import PyLinter -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") FUTURE = "__future__" # regexp for ignored argument name @@ -175,24 +169,10 @@ class VariableVisitConsumerAction(Enum): Continue -> continue loop to next consumer Return -> return and thereby break the loop - Consume -> consume the found nodes (second return value) and return """ CONTINUE = 0 RETURN = 1 - CONSUME = 2 - - -VariableVisitConsumerActionAndOptionalNodesType = Union[ - Tuple[ - Union[ - Literal[VariableVisitConsumerAction.CONTINUE], - Literal[VariableVisitConsumerAction.RETURN], - ], - None, - ], - Tuple[Literal[VariableVisitConsumerAction.CONSUME], List[nodes.NodeNG]], -] def _is_from_future_import(stmt, name): @@ -1398,22 +1378,19 @@ def _undefined_and_used_before_checker( if self._should_node_be_skipped(node, current_consumer, i == start_index): continue - action, found_nodes = self._check_consumer( + action, nodes_to_consume = self._check_consumer( node, stmt, frame, current_consumer, i, base_scope_type ) - if action is VariableVisitConsumerAction.CONTINUE: - continue - if action is VariableVisitConsumerAction.CONSUME: + if nodes_to_consume: # Any nodes added to consumed_uncertain by get_next_to_consume() # should be added back so that they are marked as used. # They will have already had a chance to emit used-before-assignment. # We check here instead of before every single return in _check_consumer() - found_nodes += current_consumer.consumed_uncertain[node.name] # type: ignore[operator] - current_consumer.mark_as_consumed(node.name, found_nodes) - if action in { - VariableVisitConsumerAction.RETURN, - VariableVisitConsumerAction.CONSUME, - }: + nodes_to_consume += current_consumer.consumed_uncertain[node.name] + current_consumer.mark_as_consumed(node.name, nodes_to_consume) + if action is VariableVisitConsumerAction.CONTINUE: + continue + if action is VariableVisitConsumerAction.RETURN: return # we have not found the name, if it isn't a builtin, that's an @@ -1482,7 +1459,7 @@ def _check_consumer( current_consumer: NamesConsumer, consumer_level: int, base_scope_type: Any, - ) -> VariableVisitConsumerActionAndOptionalNodesType: + ) -> Tuple[VariableVisitConsumerAction, Optional[List[nodes.NodeNG]]]: """Checks a consumer for conditions that should trigger messages""" # If the name has already been consumed, only check it's not a loop # variable used outside the loop. @@ -1526,13 +1503,12 @@ def _check_consumer( node=node, confidence=confidence, ) - if current_consumer.consumed_uncertain[node.name]: - # If there are nodes added to consumed_uncertain by - # get_next_to_consume() because they might not have executed, - # return a CONSUME action so that _undefined_and_used_before_checker() - # will mark them as used - return (VariableVisitConsumerAction.CONSUME, found_nodes) - return (VariableVisitConsumerAction.RETURN, None) + # Mark for consumption any nodes added to consumed_uncertain by + # get_next_to_consume() because they might not have executed. + return ( + VariableVisitConsumerAction.RETURN, + current_consumer.consumed_uncertain[node.name], + ) self._check_late_binding_closure(node) @@ -1540,7 +1516,7 @@ def _check_consumer( self._is_undefined_variable_enabled or self._is_used_before_assignment_enabled ): - return (VariableVisitConsumerAction.CONSUME, found_nodes) + return (VariableVisitConsumerAction.RETURN, found_nodes) defnode = utils.assign_parent(found_nodes[0]) defstmt = defnode.statement(future=True) @@ -1631,7 +1607,7 @@ def _check_consumer( and node.name in node.root().locals ): if defined_by_stmt: - current_consumer.mark_as_consumed(node.name, [node]) + return (VariableVisitConsumerAction.CONTINUE, [node]) return (VariableVisitConsumerAction.CONTINUE, None) elif base_scope_type != "lambda": @@ -1648,7 +1624,7 @@ def _check_consumer( node=node, confidence=HIGH, ) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + return (VariableVisitConsumerAction.RETURN, found_nodes) elif base_scope_type == "lambda": # E0601 can occur in class-level scope in lambdas, as in @@ -1681,7 +1657,7 @@ def _check_consumer( self.add_message( "undefined-variable", args=node.name, node=node, confidence=HIGH ) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + return (VariableVisitConsumerAction.RETURN, found_nodes) elif isinstance(defstmt, nodes.ClassDef): return self._is_first_level_self_reference(node, defstmt, found_nodes) @@ -1695,9 +1671,9 @@ def _check_consumer( node=node, confidence=INFERENCE, ) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + return (VariableVisitConsumerAction.RETURN, found_nodes) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + return (VariableVisitConsumerAction.RETURN, found_nodes) @utils.check_messages("no-name-in-module") def visit_import(self, node: nodes.Import) -> None: @@ -2078,7 +2054,7 @@ def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool @staticmethod def _is_first_level_self_reference( node: nodes.Name, defstmt: nodes.ClassDef, found_nodes: List[nodes.NodeNG] - ) -> VariableVisitConsumerActionAndOptionalNodesType: + ) -> Tuple[VariableVisitConsumerAction, Optional[List[nodes.NodeNG]]]: """Check if a first level method's annotation or default values refers to its own class, and return a consumer action """ @@ -2096,7 +2072,7 @@ def _is_first_level_self_reference( node.parent.parent, nodes.Arguments ): return (VariableVisitConsumerAction.CONTINUE, None) - return (VariableVisitConsumerAction.CONSUME, found_nodes) + return (VariableVisitConsumerAction.RETURN, found_nodes) @staticmethod def _is_never_evaluated( From 2d560189f1e2050ce022ec48981cce1f7a88b4d9 Mon Sep 17 00:00:00 2001 From: orSolocate <38433858+orSolocate@users.noreply.github.com> Date: Wed, 2 Feb 2022 00:01:21 +0200 Subject: [PATCH 189/357] Add `iterating-modified-list` checker for modified lists (#5628) --- ChangeLog | 5 + doc/whatsnew/2.13.rst | 6 + pylint/checkers/base.py | 2 +- pylint/checkers/modified_iterating_checker.py | 154 ++++++++++++++++++ tests/checkers/unittest_base.py | 2 +- .../c/consider/consider_using_dict_items.py | 2 +- tests/functional/m/modified_iterating.py | 57 +++++++ tests/functional/m/modified_iterating.txt | 10 ++ 8 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 pylint/checkers/modified_iterating_checker.py create mode 100644 tests/functional/m/modified_iterating.py create mode 100644 tests/functional/m/modified_iterating.txt diff --git a/ChangeLog b/ChangeLog index 7148e16067..996675cdeb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,11 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Add ``modified-iterating-list``, ``modified-iterating-dict`` and ``modified-iterating-set``, + emitted when items are added to or removed from respectively a list, dictionary or + set being iterated through. + + Closes #5348 * Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were defined outside the top level of a class. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index e783e32faf..326717417c 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -37,6 +37,12 @@ New checkers Closes #5460 +* Add ``modified-iterating-list``, ``modified-iterating-dict``, and ``modified-iterating-set``, + emitted when items are added to or removed from respectively a list, dictionary or + set being iterated through. + + Closes #5348 + * Add checker ``redefined-slots-in-subclass``: Emitted when a slot is redefined in a subclass. Closes #5617 diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 97e5941a62..8ed43810f1 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -63,7 +63,7 @@ # Copyright (c) 2021 Lorena B <46202743+lorena-b@users.noreply.github.com> # Copyright (c) 2021 David Liu # Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Or Bahari +# Copyright (c) 2021-2022 Or Bahari # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE diff --git a/pylint/checkers/modified_iterating_checker.py b/pylint/checkers/modified_iterating_checker.py new file mode 100644 index 0000000000..711d37bb0e --- /dev/null +++ b/pylint/checkers/modified_iterating_checker.py @@ -0,0 +1,154 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +from typing import TYPE_CHECKING, Union + +from astroid import nodes + +from pylint import checkers, interfaces +from pylint.checkers import utils + +if TYPE_CHECKING: + from pylint.lint import PyLinter + + +_LIST_MODIFIER_METHODS = {"append", "remove"} +_SET_MODIFIER_METHODS = {"add", "remove"} + + +class ModifiedIterationChecker(checkers.BaseChecker): + """Checks for modified iterators in for loops iterations. + + Currently supports `for` loops for Sets, Dictionaries and Lists. + """ + + __implements__ = interfaces.IAstroidChecker + + name = "modified_iteration" + + msgs = { + "W4701": ( + "Iterated list '%s' is being modified inside for loop body, consider iterating through a copy of it " + "instead.", + "modified-iterating-list", + "Emitted when items are added or removed to a list being iterated through. " + "Doing so can result in unexpected behaviour, that is why it is preferred to use a copy of the list.", + ), + "E4702": ( + "Iterated dict '%s' is being modified inside for loop body, iterate through a copy of it instead.", + "modified-iterating-dict", + "Emitted when items are added or removed to a dict being iterated through. " + "Doing so raises a RuntimeError.", + ), + "E4703": ( + "Iterated set '%s' is being modified inside for loop body, iterate through a copy of it instead.", + "modified-iterating-set", + "Emitted when items are added or removed to a set being iterated through. " + "Doing so raises a RuntimeError.", + ), + } + + options = () + priority = -2 + + @utils.check_messages( + "modified-iterating-list", "modified-iterating-dict", "modified-iterating-set" + ) + def visit_for(self, node: nodes.For) -> None: + iter_obj = node.iter + for body_node in node.body: + self._modified_iterating_check_on_node_and_children(body_node, iter_obj) + + def _modified_iterating_check_on_node_and_children( + self, body_node: nodes.NodeNG, iter_obj: nodes.NodeNG + ) -> None: + """See if node or any of its children raises modified iterating messages.""" + self._modified_iterating_check(body_node, iter_obj) + for child in body_node.get_children(): + self._modified_iterating_check_on_node_and_children(child, iter_obj) + + def _modified_iterating_check( + self, node: nodes.NodeNG, iter_obj: nodes.NodeNG + ) -> None: + msg_id = None + if self._modified_iterating_list_cond(node, iter_obj): + msg_id = "modified-iterating-list" + elif self._modified_iterating_dict_cond(node, iter_obj): + msg_id = "modified-iterating-dict" + elif self._modified_iterating_set_cond(node, iter_obj): + msg_id = "modified-iterating-set" + if msg_id: + self.add_message( + msg_id, + node=node, + args=(iter_obj.name,), + confidence=interfaces.INFERENCE, + ) + + @staticmethod + def _is_node_expr_that_calls_attribute_name(node: nodes.NodeNG) -> bool: + return ( + isinstance(node, nodes.Expr) + and isinstance(node.value, nodes.Call) + and isinstance(node.value.func, nodes.Attribute) + and isinstance(node.value.func.expr, nodes.Name) + ) + + @staticmethod + def _common_cond_list_set( + node: nodes.Expr, + iter_obj: nodes.NodeNG, + infer_val: Union[nodes.List, nodes.Set], + ) -> bool: + return (infer_val == utils.safe_infer(iter_obj)) and ( + node.value.func.expr.name == iter_obj.name + ) + + @staticmethod + def _is_node_assigns_subscript_name(node: nodes.NodeNG) -> bool: + return isinstance(node, nodes.Assign) and ( + isinstance(node.targets[0], nodes.Subscript) + and (isinstance(node.targets[0].value, nodes.Name)) + ) + + def _modified_iterating_list_cond( + self, node: nodes.NodeNG, iter_obj: nodes.NodeNG + ) -> bool: + if not self._is_node_expr_that_calls_attribute_name(node): + return False + infer_val = utils.safe_infer(node.value.func.expr) + if not isinstance(infer_val, nodes.List): + return False + return ( + self._common_cond_list_set(node, iter_obj, infer_val) + and node.value.func.attrname in _LIST_MODIFIER_METHODS + ) + + def _modified_iterating_dict_cond( + self, node: nodes.NodeNG, iter_obj: nodes.NodeNG + ) -> bool: + if not self._is_node_assigns_subscript_name(node): + return False + infer_val = utils.safe_infer(node.targets[0].value) + if not isinstance(infer_val, nodes.Dict): + return False + if infer_val != utils.safe_infer(iter_obj): + return False + return node.targets[0].value.name == iter_obj.name + + def _modified_iterating_set_cond( + self, node: nodes.NodeNG, iter_obj: nodes.NodeNG + ) -> bool: + if not self._is_node_expr_that_calls_attribute_name(node): + return False + infer_val = utils.safe_infer(node.value.func.expr) + if not isinstance(infer_val, nodes.Set): + return False + return ( + self._common_cond_list_set(node, iter_obj, infer_val) + and node.value.func.attrname in _SET_MODIFIER_METHODS + ) + + +def register(linter: "PyLinter") -> None: + linter.register_checker(ModifiedIterationChecker(linter)) diff --git a/tests/checkers/unittest_base.py b/tests/checkers/unittest_base.py index 3dddd859b9..fe46efcb4e 100644 --- a/tests/checkers/unittest_base.py +++ b/tests/checkers/unittest_base.py @@ -19,7 +19,7 @@ # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> # Copyright (c) 2021 Yilei "Dolee" Yang # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Or Bahari +# Copyright (c) 2021-2022 Or Bahari # Copyright (c) 2021 David Gilman # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html diff --git a/tests/functional/c/consider/consider_using_dict_items.py b/tests/functional/c/consider/consider_using_dict_items.py index 32e92e8034..7ffea608f3 100644 --- a/tests/functional/c/consider/consider_using_dict_items.py +++ b/tests/functional/c/consider/consider_using_dict_items.py @@ -1,5 +1,5 @@ """Emit a message for iteration through dict keys and subscripting dict with key.""" -# pylint: disable=line-too-long,missing-docstring,unsubscriptable-object,too-few-public-methods,redefined-outer-name,use-dict-literal +# pylint: disable=line-too-long,missing-docstring,unsubscriptable-object,too-few-public-methods,redefined-outer-name,use-dict-literal,modified-iterating-dict def bad(): a_dict = {1: 1, 2: 2, 3: 3} diff --git a/tests/functional/m/modified_iterating.py b/tests/functional/m/modified_iterating.py new file mode 100644 index 0000000000..32155fbc80 --- /dev/null +++ b/tests/functional/m/modified_iterating.py @@ -0,0 +1,57 @@ +"""Tests for iterating-modified messages""" +# pylint: disable=not-callable,unnecessary-comprehension + +import copy + +item_list = [1, 2, 3] +for item in item_list: + item_list.append(item) # [modified-iterating-list] + +for item in item_list: + item_list.remove(item) # [modified-iterating-list] + +for item in item_list.copy(): + item_list.append(item) + item_list.remove(item) + +for item in copy(item_list): + item_list.append(item) + item_list.remove(item) + +for item in [k for k in item_list]: + item_list.append(item) + item_list.remove(item) + +my_dict = {"1": 1, "2": 2, "3": 3} +i = 1 +for item in my_dict: + item_list[0] = i # for coverage, see reference at /pull/5628#discussion_r792181642 + my_dict[i] = 1 # [modified-iterating-dict] + i += 1 + +i = 1 +for item in my_dict.copy(): + my_dict[i] = 1 + i += 1 + +item_set = {1, 2, 3} +for item in item_set: + item_set.add(item + 10) # [modified-iterating-set] + +for item in item_set.copy(): + item_set.add(item + 10) + +for l in item_list: + for s in item_set: + item_list.append(1) # [modified-iterating-list] + item_set.remove(4) # [modified-iterating-set] + item_list.remove(1) # [modified-iterating-list] + +# Check for nested for loops and changes to iterators +for l in item_list: + item_list.append(1) # [modified-iterating-list] + for _ in []: + for _ in []: + item_list.remove(1) # [modified-iterating-list] + for _ in []: + item_list.append(1) # [modified-iterating-list] diff --git a/tests/functional/m/modified_iterating.txt b/tests/functional/m/modified_iterating.txt new file mode 100644 index 0000000000..77001b1720 --- /dev/null +++ b/tests/functional/m/modified_iterating.txt @@ -0,0 +1,10 @@ +modified-iterating-list:8:4:8:26::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE +modified-iterating-list:11:4:11:26::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE +modified-iterating-dict:29:4:29:18::Iterated dict 'my_dict' is being modified inside for loop body, iterate through a copy of it instead.:INFERENCE +modified-iterating-set:39:4:39:27::Iterated set 'item_set' is being modified inside for loop body, iterate through a copy of it instead.:INFERENCE +modified-iterating-list:46:8:46:27::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE +modified-iterating-set:47:8:47:26::Iterated set 'item_set' is being modified inside for loop body, iterate through a copy of it instead.:INFERENCE +modified-iterating-list:48:4:48:23::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE +modified-iterating-list:52:4:52:23::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE +modified-iterating-list:55:12:55:31::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE +modified-iterating-list:57:16:57:35::Iterated list 'item_list' is being modified inside for loop body, consider iterating through a copy of it instead.:INFERENCE From 7e1eec27925f19f10934f5f4426de047c9b02597 Mon Sep 17 00:00:00 2001 From: orSolocate <38433858+orSolocate@users.noreply.github.com> Date: Wed, 2 Feb 2022 09:42:06 +0200 Subject: [PATCH 190/357] Add pip install -e to docs before testing (#5687) --- doc/development_guide/testing.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/development_guide/testing.rst b/doc/development_guide/testing.rst index 85d1043ef8..c75293d27e 100644 --- a/doc/development_guide/testing.rst +++ b/doc/development_guide/testing.rst @@ -12,6 +12,14 @@ Test your code! Pylint is very well tested and has a high code coverage. New contributions are not accepted unless they include tests. + +Before you start testing your code, you need to install your source-code package locally. +To set up your environment for testing, open a terminal outside of your forked repository and run: + + pip install -e + +This ensures your testing environment is similar to Pylint's testing environment on GitHub. + Pylint uses two types of tests: unittests and functional tests. - The unittests can be found in the ``/pylint/test`` directory and they can From e177b19032172655cac65bc86ad7627cb7489c2e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 2 Feb 2022 02:43:46 -0500 Subject: [PATCH 191/357] Fix false positive `used-before-assignment` for named expressions in ternary operators (#5748) --- ChangeLog | 6 ++++++ doc/whatsnew/2.13.rst | 6 ++++++ pylint/checkers/variables.py | 19 +++++++++++++++++-- .../u/undefined/undefined_variable_py38.py | 16 ++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 996675cdeb..f39064bc00 100644 --- a/ChangeLog +++ b/ChangeLog @@ -111,6 +111,12 @@ Release date: TBA Closes #5569 +* Fix false positives for ``used-before-assignment`` from using named + expressions in a ternary operator test and using that expression as + a call argument. + + Closes #5177, #5212 + * Fix false positive for ``undefined-variable`` when ``namedtuple`` class attributes are used as return annotations. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 326717417c..87c3e6e624 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -164,6 +164,12 @@ Other Changes Closes #4941 +* Fix false positives for ``used-before-assignment`` from using named + expressions in a ternary operator test and using that expression as + a call argument. + + Closes #5177, #5212 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index cf4af384cd..5bacd6c268 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1941,8 +1941,23 @@ def _is_variable_violation( ) and ( isinstance(defstmt.value, nodes.IfExp) - or isinstance(defstmt.value, nodes.Lambda) - and isinstance(defstmt.value.body, nodes.IfExp) + or ( + isinstance(defstmt.value, nodes.Lambda) + and isinstance(defstmt.value.body, nodes.IfExp) + ) + or ( + isinstance(defstmt.value, nodes.Call) + and ( + any( + isinstance(kwarg.value, nodes.IfExp) + for kwarg in defstmt.value.keywords + ) + or any( + isinstance(arg, nodes.IfExp) + for arg in defstmt.value.args + ) + ) + ) ) and frame is defframe and defframe.parent_of(node) diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index f234135431..924eb4a431 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -139,3 +139,19 @@ def type_annotation_used_improperly_after_comprehension_2(): my_int: int _ = [print(my_int, my_int) for my_int in range(10)] print(my_int) # [used-before-assignment] + + +# Tests for named expressions (walrus operator) + +# Expression in ternary operator: positional argument +print(sep=colon if (colon := ":") else None) + + +class Dummy: + """Expression in ternary operator: keyword argument""" + # pylint: disable=too-few-public-methods + def __init__(self, value): + self.value = value + + +dummy = Dummy(value=val if (val := 'something') else 'anything') From 1d47b630920ac07043ba414af0506e3bf70300fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 2 Feb 2022 08:45:05 +0100 Subject: [PATCH 192/357] Remove unnecessary ``try...except`` from ``frame()`` call (#5664) --- pylint/checkers/classes/class_checker.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 682c79a16f..513b27308f 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1700,10 +1700,7 @@ def _check_protected_attribute_access(self, node: nodes.Attribute): @staticmethod def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: """Returns true if the node is located inside a special (aka dunder) method""" - try: - frame_name = node.frame(future=True).name - except AttributeError: - return False + frame_name = node.frame(future=True).name return frame_name and frame_name in PYMETHODS def _is_type_self_call(self, expr: nodes.NodeNG) -> bool: From ef67460370ce9b4bb69c539a6af7a0323e6ebc0d Mon Sep 17 00:00:00 2001 From: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Date: Wed, 2 Feb 2022 08:51:57 +0100 Subject: [PATCH 193/357] Fix a false negative with tuple unpacking (#5708) Closes #5707 Co-authored-by: Pierre Sassoulas --- ChangeLog | 12 ++-- doc/whatsnew/2.13.rst | 8 +++ pylint/checkers/variables.py | 22 ++++--- .../u/unbalanced_tuple_unpacking.py | 65 ++++++++++++++----- .../u/unbalanced_tuple_unpacking.txt | 15 +++-- 5 files changed, 84 insertions(+), 38 deletions(-) diff --git a/ChangeLog b/ChangeLog index f39064bc00..2fe4c95c5e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -47,6 +47,14 @@ Release date: TBA Closes #5281 +* Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. + + Closes #5312 + +* Fixed false negative ``unpacking-non-sequence`` when value is an empty list. + + Closes #5707 + * Better warning messages for useless else or elif when a function returns early. Closes #5614 @@ -305,10 +313,6 @@ Release date: TBA Closes #2399 -* Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. - - Closes #5312 - * Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables or ``not in`` checks diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 87c3e6e624..fb7f9023d3 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -350,6 +350,14 @@ Other Changes Closes #3781 +* Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. + + Closes #5312 + +* Fixed false negative ``unpacking-non-sequence`` when value is an empty list. + + Closes #5707 + * Fixed false positive for ``global-variable-not-assigned`` when the ``del`` statement is used Closes #5333 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 5bacd6c268..cbe7b4f137 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2524,15 +2524,8 @@ def _check_unpacking(self, inferred, node, targets): return # Attempt to check unpacking is properly balanced - values: Optional[List] = None - if isinstance(inferred, (nodes.Tuple, nodes.List)): - values = inferred.itered() - elif isinstance(inferred, astroid.Instance) and any( - ancestor.qname() == "typing.NamedTuple" for ancestor in inferred.ancestors() - ): - values = [i for i in inferred.values() if isinstance(i, nodes.AssignName)] - - if values: + values = self._nodes_to_unpack(inferred) + if values is not None: if len(targets) != len(values): # Check if we have starred nodes. if any(isinstance(target, nodes.Starred) for target in targets): @@ -2554,6 +2547,17 @@ def _check_unpacking(self, inferred, node, targets): args=(_get_unpacking_extra_info(node, inferred),), ) + @staticmethod + def _nodes_to_unpack(node: nodes.NodeNG) -> Optional[List[nodes.NodeNG]]: + """Return the list of values of the `Assign` node""" + if isinstance(node, (nodes.Tuple, nodes.List)): + return node.itered() + if isinstance(node, astroid.Instance) and any( + ancestor.qname() == "typing.NamedTuple" for ancestor in node.ancestors() + ): + return [i for i in node.values() if isinstance(i, nodes.AssignName)] + return None + def _check_module_attrs(self, node, module, module_names): """check that module_names (list of string) are accessible through the given module diff --git a/tests/functional/u/unbalanced_tuple_unpacking.py b/tests/functional/u/unbalanced_tuple_unpacking.py index 4deb6ce37a..20e3c29288 100644 --- a/tests/functional/u/unbalanced_tuple_unpacking.py +++ b/tests/functional/u/unbalanced_tuple_unpacking.py @@ -5,54 +5,64 @@ # pylint: disable=missing-class-docstring, missing-function-docstring, using-constant-test, useless-object-inheritance,import-outside-toplevel + def do_stuff(): """This is not right.""" - first, second = 1, 2, 3 # [unbalanced-tuple-unpacking] + first, second = 1, 2, 3 # [unbalanced-tuple-unpacking] return first + second + def do_stuff1(): """This is not right.""" - first, second = [1, 2, 3] # [unbalanced-tuple-unpacking] + first, second = [1, 2, 3] # [unbalanced-tuple-unpacking] return first + second + def do_stuff2(): """This is not right.""" - (first, second) = 1, 2, 3 # [unbalanced-tuple-unpacking] + (first, second) = 1, 2, 3 # [unbalanced-tuple-unpacking] return first + second + def do_stuff3(): """This is not right.""" first, second = range(100) return first + second + def do_stuff4(): - """ This is right """ + """This is right""" first, second = 1, 2 return first + second + def do_stuff5(): - """ This is also right """ + """This is also right""" first, second = (1, 2) return first + second + def do_stuff6(): - """ This is right """ + """This is right""" (first, second) = (1, 2) return first + second + def temp(): - """ This is not weird """ + """This is not weird""" if True: return [1, 2] return [2, 3, 4] + def do_stuff7(): - """ This is not right, but we're not sure """ + """This is not right, but we're not sure""" first, second = temp() return first + second + def temp2(): - """ This is weird, but correct """ + """This is weird, but correct""" if True: return (1, 2) @@ -60,30 +70,34 @@ def temp2(): return (2, 3) return (4, 5) + def do_stuff8(): - """ This is correct """ + """This is correct""" first, second = temp2() return first + second + def do_stuff9(): - """ This is not correct """ - first, second = unpack() # [unbalanced-tuple-unpacking] + """This is not correct""" + first, second = unpack() # [unbalanced-tuple-unpacking] return first + second + class UnbalancedUnpacking(object): - """ Test unbalanced tuple unpacking in instance attributes. """ + """Test unbalanced tuple unpacking in instance attributes.""" + # pylint: disable=attribute-defined-outside-init, invalid-name, too-few-public-methods def test(self): - """ unpacking in instance attributes """ + """unpacking in instance attributes""" # we're not sure if temp() returns two or three values # so we shouldn't emit an error self.a, self.b = temp() self.a, self.b = temp2() - self.a, self.b = unpack() # [unbalanced-tuple-unpacking] + self.a, self.b = unpack() # [unbalanced-tuple-unpacking] def issue329(*args): - """ Don't emit unbalanced tuple unpacking if the + """Don't emit unbalanced tuple unpacking if the rhs of the assignment is a variable-length argument, because we don't know the actual length of the tuple. """ @@ -97,6 +111,7 @@ def test_decimal(): See astroid https://bitbucket.org/logilab/astroid/issues/92/ """ from decimal import Decimal + dec = Decimal(2) first, second, third = dec.as_tuple() return first, second, third @@ -105,6 +120,7 @@ def test_decimal(): def test_issue_559(): """Test that we don't have a false positive wrt to issue #559.""" from ctypes import c_int + root_x, root_y, win_x, win_y = [c_int()] * 4 return root_x, root_y, win_x, win_y @@ -121,10 +137,23 @@ def my_sum(self): def sum_unpack_3_into_4(self): """Attempt to unpack 3 variables into 4""" - first, second, third, fourth = self # [unbalanced-tuple-unpacking] + first, second, third, fourth = self # [unbalanced-tuple-unpacking] return first + second + third + fourth def sum_unpack_3_into_2(self): """Attempt to unpack 3 variables into 2""" - first, second = self # [unbalanced-tuple-unpacking] + first, second = self # [unbalanced-tuple-unpacking] return first + second + + +def my_function(mystring): + """The number of items on the right-hand-side of the assignment to this function is not known""" + mylist = [] + for item in mystring: + mylist.append(item) + return mylist + + +a, b = my_function("12") # [unbalanced-tuple-unpacking] +c = my_function("12") +d, *_ = my_function("12") diff --git a/tests/functional/u/unbalanced_tuple_unpacking.txt b/tests/functional/u/unbalanced_tuple_unpacking.txt index c64c1c2ad1..d2df38cfd7 100644 --- a/tests/functional/u/unbalanced_tuple_unpacking.txt +++ b/tests/functional/u/unbalanced_tuple_unpacking.txt @@ -1,7 +1,8 @@ -unbalanced-tuple-unpacking:10:4:10:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:15:4:15:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:20:4:20:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:70:4:70:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:82:8:82:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:124:8:124:43:MyClass.sum_unpack_3_into_4:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 4 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:129:8:129:28:MyClass.sum_unpack_3_into_2:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:11:4:11:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:17:4:17:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:23:4:23:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:82:4:82:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:96:8:96:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:140:8:140:43:MyClass.sum_unpack_3_into_4:"Possible unbalanced tuple unpacking with sequence defined at line 128: left side has 4 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:145:8:145:28:MyClass.sum_unpack_3_into_2:"Possible unbalanced tuple unpacking with sequence defined at line 128: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:157:0:157:24::"Possible unbalanced tuple unpacking with sequence defined at line 151: left side has 2 label(s), right side has 0 value(s)":UNDEFINED From b7a9a0a650c10ee19fb42aadc4ad07b0d402b695 Mon Sep 17 00:00:00 2001 From: orSolocate <38433858+orSolocate@users.noreply.github.com> Date: Wed, 2 Feb 2022 10:04:17 +0200 Subject: [PATCH 194/357] Fix assignment-from-none false negative case using list.sort() (#5738) Co-authored-by: Pierre Sassoulas --- ChangeLog | 4 ++ doc/whatsnew/2.13.rst | 4 ++ pylint/checkers/typecheck.py | 56 +++++++++++++------ .../a/assign/assignment_from_no_return_2.py | 2 + .../a/assign/assignment_from_no_return_2.txt | 1 + 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2fe4c95c5e..5059142991 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,10 @@ Release date: TBA Closes #5348 +* Fix false-negative for ``assignment-from-none`` checker using list.sort() method. + + closes #5722 + * Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were defined outside the top level of a class. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index fb7f9023d3..1486b135cc 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -92,6 +92,10 @@ Extensions Other Changes ============= +* Fix false-negative for ``assignment-from-none`` checker with list.sort() method. + + Closes #5722 + * Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were defined outside the top level of a class. diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index bb9c3de52b..825c380826 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1141,10 +1141,8 @@ def visit_assign(self, node: nodes.Assign) -> None: self._check_assignment_from_function_call(node) self._check_dundername_is_string(node) - def _check_assignment_from_function_call(self, node): - """check that if assigning to a function call, the function is - possibly returning something valuable - """ + def _check_assignment_from_function_call(self, node: nodes.Assign) -> None: + """When assigning to a function call, check that the function returns a valid value.""" if not isinstance(node.value, nodes.Call): return @@ -1153,7 +1151,7 @@ def _check_assignment_from_function_call(self, node): if not isinstance(function_node, funcs): return - # Unwrap to get the actual function object + # Unwrap to get the actual function node object if isinstance(function_node, astroid.BoundMethod) and isinstance( function_node._proxied, astroid.UnboundMethod ): @@ -1161,35 +1159,57 @@ def _check_assignment_from_function_call(self, node): # Make sure that it's a valid function that we can analyze. # Ordered from less expensive to more expensive checks. - # pylint: disable=too-many-boolean-expressions if ( not function_node.is_function - or isinstance(function_node, nodes.AsyncFunctionDef) or function_node.decorators - or function_node.is_generator() - or function_node.is_abstract(pass_is_abstract=False) - or utils.is_error(function_node) - or not function_node.root().fully_defined() + or self._is_ignored_function(function_node) ): return - returns = list( + # Fix a false-negative for list.sort(), see issue #5722 + if self._is_list_sort_method(node.value): + self.add_message("assignment-from-none", node=node, confidence=INFERENCE) + return + + if not function_node.root().fully_defined(): + return + + return_nodes = list( function_node.nodes_of_class(nodes.Return, skip_klass=nodes.FunctionDef) ) - if not returns: + if not return_nodes: self.add_message("assignment-from-no-return", node=node) else: - for rnode in returns: + for ret_node in return_nodes: if not ( - isinstance(rnode.value, nodes.Const) - and rnode.value.value is None - or rnode.value is None + isinstance(ret_node.value, nodes.Const) + and ret_node.value.value is None + or ret_node.value is None ): break else: self.add_message("assignment-from-none", node=node) - def _check_dundername_is_string(self, node): + @staticmethod + def _is_ignored_function( + function_node: Union[nodes.FunctionDef, bases.UnboundMethod] + ) -> bool: + return ( + isinstance(function_node, nodes.AsyncFunctionDef) + or utils.is_error(function_node) + or function_node.is_generator() + or function_node.is_abstract(pass_is_abstract=False) + ) + + @staticmethod + def _is_list_sort_method(node: nodes.Call) -> bool: + return ( + isinstance(node.func, nodes.Attribute) + and node.func.attrname == "sort" + and isinstance(utils.safe_infer(node.func.expr), nodes.List) + ) + + def _check_dundername_is_string(self, node) -> None: """Check a string is assigned to self.__name__""" # Check the left-hand side of the assignment is .__name__ diff --git a/tests/functional/a/assign/assignment_from_no_return_2.py b/tests/functional/a/assign/assignment_from_no_return_2.py index f177f6a9a6..fd1489dbc5 100644 --- a/tests/functional/a/assign/assignment_from_no_return_2.py +++ b/tests/functional/a/assign/assignment_from_no_return_2.py @@ -32,6 +32,8 @@ def func_implicit_return_none(): A = func_implicit_return_none() # [assignment-from-none] +lst = [3, 2] +A = lst.sort() # [assignment-from-none] def func_return_none_and_smth(): """function returning none and something else""" diff --git a/tests/functional/a/assign/assignment_from_no_return_2.txt b/tests/functional/a/assign/assignment_from_no_return_2.txt index 31a878b6db..b6aec14dad 100644 --- a/tests/functional/a/assign/assignment_from_no_return_2.txt +++ b/tests/functional/a/assign/assignment_from_no_return_2.txt @@ -1,3 +1,4 @@ assignment-from-no-return:18:0:18:20::Assigning result of a function call, where the function has no return:UNDEFINED assignment-from-none:26:0:26:22::Assigning result of a function call, where the function returns None:UNDEFINED assignment-from-none:33:0:33:31::Assigning result of a function call, where the function returns None:UNDEFINED +assignment-from-none:36:0:36:14::Assigning result of a function call, where the function returns None:INFERENCE From c5c944119fdbe9724f50fa47183e7e474e3005e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 2 Feb 2022 16:28:43 +0100 Subject: [PATCH 195/357] Upgrade flake8-typing-import to 1.12.0 [pre-commit] (#5760) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad7a5a1c2f..d625135926 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: rev: 4.0.1 hooks: - id: flake8 - additional_dependencies: [flake8-typing-imports==1.10.1] + additional_dependencies: [flake8-typing-imports==1.12.0] exclude: *fixtures - repo: local hooks: From d7013f6799d28a69dbbf3329da7867a8afacce82 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Fri, 4 Feb 2022 00:40:01 +0530 Subject: [PATCH 196/357] Avoid using get on a defaultdict (#5766) --- pylint/lint/pylinter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 92733a39cb..a4611039b8 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1659,7 +1659,7 @@ def _get_messages_to_set( else: category_id_formatted = category_id if category_id_formatted is not None: - for _msgid in self.msgs_store._msgs_by_category.get(category_id_formatted): + for _msgid in self.msgs_store._msgs_by_category[category_id_formatted]: message_definitions.extend( self._get_messages_to_set(_msgid, enable, ignore_unknown) ) From 2c6eadcfb292475a93e8410783d8bd9489e3bd37 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Sun, 6 Feb 2022 15:10:22 +0100 Subject: [PATCH 197/357] Lint all files in a directory by expanding arguments (#5682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added --recursive=y/n option and a mention in FAQ and user guide Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Jacob Walls Co-authored-by: Pierre Sassoulas --- ChangeLog | 6 ++ doc/faq.rst | 21 ++++++ doc/user_guide/run.rst | 4 ++ doc/whatsnew/2.13.rst | 6 ++ pylint/lint/pylinter.py | 38 ++++++++++ .../directory/package/__init__.py | 0 .../regrtest_data/directory/package/module.py | 0 .../directory/package/subpackage/__init__.py | 0 .../directory/package/subpackage/module.py | 0 .../directory/subdirectory/module.py | 0 .../subdirectory/subsubdirectory/module.py | 0 tests/test_self.py | 72 ++++++++++++++++--- 12 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 tests/regrtest_data/directory/package/__init__.py create mode 100644 tests/regrtest_data/directory/package/module.py create mode 100644 tests/regrtest_data/directory/package/subpackage/__init__.py create mode 100644 tests/regrtest_data/directory/package/subpackage/module.py create mode 100644 tests/regrtest_data/directory/subdirectory/module.py create mode 100644 tests/regrtest_data/directory/subdirectory/subsubdirectory/module.py diff --git a/ChangeLog b/ChangeLog index 5059142991..c10a70c3b8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,12 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Add ``--recursive`` option to allow recursive discovery of all modules and packages in subtree. Running pylint with + ``--recursive=y`` option will check all discovered ``.py`` files and packages found inside subtree of directory provided + as parameter to pylint. + + Closes #352 + * Add ``modified-iterating-list``, ``modified-iterating-dict`` and ``modified-iterating-set``, emitted when items are added to or removed from respectively a list, dictionary or set being iterated through. diff --git a/doc/faq.rst b/doc/faq.rst index 47dc9b501c..f5ca1a9997 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -116,6 +116,27 @@ For example:: Much probably. Read :ref:`ide-integration` +3.5 I need to run pylint over all modules and packages in my project directory. +------------------------------------------------------------------------------- + +By default the ``pylint`` command only accepts a list of python modules and packages. Using a +directory which is not a package results in an error:: + + pylint mydir + ************* Module mydir + mydir/__init__.py:1:0: F0010: error while code parsing: Unable to load file mydir/__init__.py: + [Errno 2] No such file or directory: 'mydir/__init__.py' (parse-error) + +To execute pylint over all modules and packages under the directory, the ``--recursive=y`` option must +be provided. This option makes ``pylint`` attempt to discover all modules (files ending with ``.py`` extension) +and all packages (all directories containing a ``__init__.py`` file). +Those modules and packages are then analyzed:: + + pylint --recursive=y mydir + +When ``--recursive=y`` option is used, modules and packages are also accepted as parameters:: + + pylint --recursive=y mydir mymodule mypackage 4. Message Control ================== diff --git a/doc/user_guide/run.rst b/doc/user_guide/run.rst index ace2009866..5501e23065 100644 --- a/doc/user_guide/run.rst +++ b/doc/user_guide/run.rst @@ -33,6 +33,10 @@ will work if ``directory`` is a python package (i.e. has an __init__.py file or it is an implicit namespace package) or if "directory" is in the python path. +By default, pylint will exit with an error when one of the arguments is a directory which is not +a python package. In order to run pylint over all modules and packages within the provided +subtree of a directory, the ``--recursive=y`` option must be provided. + For more details on this see the :ref:`faq`. It is also possible to call Pylint from another Python program, diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 1486b135cc..9d5330c437 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -92,6 +92,12 @@ Extensions Other Changes ============= +* Add ``--recursive`` option to allow recursive discovery of all modules and packages in subtree. Running pylint with + ``--recursive=y`` option will check all discovered ``.py`` files and packages found inside subtree of directory provided + as parameter to pylint. + + Closes #352 + * Fix false-negative for ``assignment-from-none`` checker with list.sort() method. Closes #5722 diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index a4611039b8..379fefe040 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -515,6 +515,15 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]: ), }, ), + ( + "recursive", + { + "type": "yn", + "metavar": "", + "default": False, + "help": "Discover python modules and packages in the file system subtree.", + }, + ), ( "py-version", { @@ -1005,6 +1014,33 @@ 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. + + Returns iterator of paths to discovered modules and packages. + """ + for something in files_or_modules: + if os.path.isdir(something) and not os.path.isfile( + os.path.join(something, "__init__.py") + ): + skip_subtrees: List[str] = [] + for root, _, files in os.walk(something): + if any(root.startswith(s) for s in skip_subtrees): + # Skip subtree of already discovered package. + continue + if "__init__.py" in files: + skip_subtrees.append(root) + yield root + else: + yield from ( + os.path.join(root, file) + for file in files + if file.endswith(".py") + ) + else: + yield something + def check(self, files_or_modules: Union[Sequence[str], str]) -> None: """main checking entry: check a list of files or modules from their name. @@ -1019,6 +1055,8 @@ def check(self, files_or_modules: Union[Sequence[str], str]) -> None: DeprecationWarning, ) files_or_modules = (files_or_modules,) # type: ignore[assignment] + if self.config.recursive: + files_or_modules = tuple(self._discover_files(files_or_modules)) if self.config.from_stdin: if len(files_or_modules) != 1: raise exceptions.InvalidArgsError( diff --git a/tests/regrtest_data/directory/package/__init__.py b/tests/regrtest_data/directory/package/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/directory/package/module.py b/tests/regrtest_data/directory/package/module.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/directory/package/subpackage/__init__.py b/tests/regrtest_data/directory/package/subpackage/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/directory/package/subpackage/module.py b/tests/regrtest_data/directory/package/subpackage/module.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/directory/subdirectory/module.py b/tests/regrtest_data/directory/subdirectory/module.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/directory/subdirectory/subsubdirectory/module.py b/tests/regrtest_data/directory/subdirectory/subsubdirectory/module.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_self.py b/tests/test_self.py index 5703cacfe3..8b41654d20 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -100,6 +100,24 @@ def _configure_lc_ctype(lc_ctype: str) -> Iterator: os.environ[lc_ctype_env] = original_lctype +@contextlib.contextmanager +def _test_sys_path() -> Generator[None, None, None]: + original_path = sys.path + try: + yield + finally: + sys.path = original_path + + +@contextlib.contextmanager +def _test_cwd() -> Generator[None, None, None]: + original_dir = os.getcwd() + try: + yield + finally: + os.chdir(original_dir) + + class MultiReporter(BaseReporter): def __init__(self, reporters: List[BaseReporter]) -> None: # pylint: disable=super-init-not-called @@ -810,14 +828,6 @@ def test_fail_on_edge_case(self, opts, out): @staticmethod def test_modify_sys_path() -> None: - @contextlib.contextmanager - def test_sys_path() -> Generator[None, None, None]: - original_path = sys.path - try: - yield - finally: - sys.path = original_path - @contextlib.contextmanager def test_environ_pythonpath( new_pythonpath: Optional[str], @@ -837,7 +847,7 @@ def test_environ_pythonpath( # Only delete PYTHONPATH if new_pythonpath wasn't None del os.environ["PYTHONPATH"] - with test_sys_path(), patch("os.getcwd") as mock_getcwd: + with _test_sys_path(), patch("os.getcwd") as mock_getcwd: cwd = "/tmp/pytest-of-root/pytest-0/test_do_not_import_files_from_0" mock_getcwd.return_value = cwd default_paths = [ @@ -1284,3 +1294,47 @@ def test_regex_paths_csv_validator() -> None: with pytest.raises(SystemExit) as ex: Run(["--ignore-paths", "test", join(HERE, "regrtest_data", "empty.py")]) assert ex.value.code == 0 + + def test_regression_recursive(self): + self._test_output( + [join(HERE, "regrtest_data", "directory", "subdirectory"), "--recursive=n"], + expected_output="No such file or directory", + ) + + def test_recursive(self): + self._runtest( + [join(HERE, "regrtest_data", "directory", "subdirectory"), "--recursive=y"], + code=0, + ) + + def test_recursive_current_dir(self): + with _test_sys_path(): + # pytest is including directory HERE/regrtest_data to sys.path which causes + # astroid to believe that directory is a package. + sys.path = [ + path + for path in sys.path + if not os.path.basename(path) == "regrtest_data" + ] + with _test_cwd(): + os.chdir(join(HERE, "regrtest_data", "directory")) + self._runtest( + [".", "--recursive=y"], + code=0, + ) + + def test_regression_recursive_current_dir(self): + with _test_sys_path(): + # pytest is including directory HERE/regrtest_data to sys.path which causes + # astroid to believe that directory is a package. + sys.path = [ + path + for path in sys.path + if not os.path.basename(path) == "regrtest_data" + ] + with _test_cwd(): + os.chdir(join(HERE, "regrtest_data", "directory")) + self._test_output( + ["."], + expected_output="No such file or directory", + ) From 9d3a9c390fcb3ad173a6f204fbfa5221852adca4 Mon Sep 17 00:00:00 2001 From: Robin Tweedie <70587124+robin-wayve@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:47:14 +0200 Subject: [PATCH 198/357] Document the fact that wrong-import-position is equivalent to pycodestyle E402 (#5774) --- doc/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.rst b/doc/faq.rst index f5ca1a9997..fe41466307 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -222,7 +222,7 @@ You can see the plugin you need to explicitly `load in the technical reference`_ 4.8 I am using another popular linter alongside pylint. Which messages should I disable to avoid duplicates? ------------------------------------------------------------------------------------------------------------ -pycodestyle_: unneeded-not, line-too-long, unnecessary-semicolon, trailing-whitespace, missing-final-newline, bad-indentation, multiple-statements, bare-except +pycodestyle_: unneeded-not, line-too-long, unnecessary-semicolon, trailing-whitespace, missing-final-newline, bad-indentation, multiple-statements, bare-except, wrong-import-position pyflakes_: undefined-variable, unused-import, unused-variable From 3d167ab01b07a12fa29fac1c076763508e88534b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 6 Feb 2022 13:50:54 -0500 Subject: [PATCH 199/357] Fix crash in `use-maxsplit-arg` checker where `sep` given by keyword (#5772) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/refactoring/recommendation_checker.py | 4 ++-- tests/functional/u/use/use_maxsplit_arg.py | 4 ++++ tests/functional/u/use/use_maxsplit_arg.txt | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index c10a70c3b8..f786a21f93 100644 --- a/ChangeLog +++ b/ChangeLog @@ -244,6 +244,11 @@ Release date: TBA Closes #5461 +* Fixed crash in ``use-maxsplit-arg`` checker when providing the ``sep`` argument + to ``str.split()`` by keyword. + + Closes #5737 + * Fix false positive for ``unused-variable`` for a comprehension variable matching an outer scope type annotation. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 9d5330c437..7daac870d0 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -281,6 +281,11 @@ Other Changes Closes #5461 +* Fixed crash in ``use-maxsplit-arg`` checker when providing the ``sep`` argument + to ``str.split()`` by keyword. + + Closes #5737 + * Fix false positive for ``unused-variable`` for a comprehension variable matching an outer scope type annotation. diff --git a/pylint/checkers/refactoring/recommendation_checker.py b/pylint/checkers/refactoring/recommendation_checker.py index 785e37fddf..d517cfd1b5 100644 --- a/pylint/checkers/refactoring/recommendation_checker.py +++ b/pylint/checkers/refactoring/recommendation_checker.py @@ -113,7 +113,7 @@ def _check_use_maxsplit_arg(self, node: nodes.Call) -> None: return try: - utils.get_argument_from_call(node, 0, "sep") + sep = utils.get_argument_from_call(node, 0, "sep") except utils.NoSuchArgumentError: return @@ -154,7 +154,7 @@ def _check_use_maxsplit_arg(self, node: nodes.Call) -> None: new_name = ( node.func.as_string().rsplit(fn_name, maxsplit=1)[0] + new_fn - + f"({node.args[0].as_string()}, maxsplit=1)[{subscript_value}]" + + f"({sep.as_string()}, maxsplit=1)[{subscript_value}]" ) self.add_message("use-maxsplit-arg", node=node, args=(new_name,)) diff --git a/tests/functional/u/use/use_maxsplit_arg.py b/tests/functional/u/use/use_maxsplit_arg.py index 396da3adc2..d0d43c2b98 100644 --- a/tests/functional/u/use/use_maxsplit_arg.py +++ b/tests/functional/u/use/use_maxsplit_arg.py @@ -90,3 +90,7 @@ class Bar(): for j in range(5): print(source.split('.')[i]) i = i + 1 + +# Test for crash when sep is given by keyword +# https://github.com/PyCQA/pylint/issues/5737 +get_last = SEQ.split(sep=None)[-1] # [use-maxsplit-arg] diff --git a/tests/functional/u/use/use_maxsplit_arg.txt b/tests/functional/u/use/use_maxsplit_arg.txt index d9c108446d..a583dfccaf 100644 --- a/tests/functional/u/use/use_maxsplit_arg.txt +++ b/tests/functional/u/use/use_maxsplit_arg.txt @@ -19,3 +19,4 @@ use-maxsplit-arg:79:6:79:27::Use Bar.split.rsplit(',', maxsplit=1)[-1] instead:U use-maxsplit-arg:82:4:82:23::Use '1,2,3'.split('\n', maxsplit=1)[0] instead:UNDEFINED use-maxsplit-arg:83:4:83:26::Use '1,2,3'.rsplit('split', maxsplit=1)[-1] instead:UNDEFINED use-maxsplit-arg:84:4:84:28::Use '1,2,3'.split('rsplit', maxsplit=1)[0] instead:UNDEFINED +use-maxsplit-arg:96:11:96:30::Use SEQ.rsplit(None, maxsplit=1)[-1] instead:UNDEFINED From 84821ccb4817d7778985c8a3cd5bc0842182ae05 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 6 Feb 2022 19:57:05 +0100 Subject: [PATCH 200/357] Fix unsafe utils.safe_infer (#5775) * Add additional isinstance check * Originally added in #5409 --- pylint/checkers/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index a73ee2fa9d..f34faecc8a 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1263,6 +1263,7 @@ def safe_infer(node: nodes.NodeNG, context=None) -> Optional[nodes.NodeNG]: if ( isinstance(inferred, nodes.FunctionDef) and inferred.args.args is not None + and isinstance(value, nodes.FunctionDef) and value.args.args is not None and len(inferred.args.args) != len(value.args.args) ): From 86cb7cf681af4c64cffa2f88c893179abab55d86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 15:05:05 +0100 Subject: [PATCH 201/357] Bump actions/setup-python from 2.3.1 to 2.3.2 (#5777) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.1...v2.3.2) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 22 +++++++++++----------- .github/workflows/primer-test.yaml | 8 ++++---- .github/workflows/release.yml | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a007ca9476..ec9d771feb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Generate partial Python venv restore key @@ -84,7 +84,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment @@ -164,7 +164,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -205,7 +205,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -246,7 +246,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -290,7 +290,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -344,7 +344,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -389,7 +389,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -426,7 +426,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -467,7 +467,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index f3f02e904d..2e12d597b2 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -77,7 +77,7 @@ jobs: uses: actions/checkout@v2.3.5 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v2.3.5 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@v2.3.5 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5217fae7bb..74e14ee22b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Install requirements From 533e6b8d64d344e8fdea23fc8fe6c1a4d0aa6286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 7 Feb 2022 23:38:04 +0100 Subject: [PATCH 202/357] Fix duplicate names of ``Pylint Messages`` in docs (#5744) Also add tbump step to upgrade the placeholder markdown, and an auto generation comment. Co-authored-by: Pierre Sassoulas --- doc/exts/pylint_messages.py | 6 +++++- doc/messages/messages_introduction.rst | 4 ++-- doc/messages/messages_list.rst | 26 ++++++++++++++++++++++++-- doc/release.md | 2 +- tbump.toml | 4 ++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index b737afbf40..32541788fc 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -122,7 +122,11 @@ def _write_messages_list_page( stream.write( f""".. _messages-list: -{get_rst_title("Pylint Messages", "=")} +{get_rst_title("Overview of all Pylint messages", "=")} +.. + NOTE This file is auto-generated. Make any changes to the associated + docs extension in 'pylint_messages.py'. + Pylint can emit the following messages: """ diff --git a/doc/messages/messages_introduction.rst b/doc/messages/messages_introduction.rst index 97a807d0fd..e351d1b9bd 100644 --- a/doc/messages/messages_introduction.rst +++ b/doc/messages/messages_introduction.rst @@ -1,7 +1,7 @@ .. _messages-introduction: -Pylint messages -================ +Message categories +===================== Pylint can emit various messages. These are categorized according to categories:: diff --git a/doc/messages/messages_list.rst b/doc/messages/messages_list.rst index 6dd6c5b39b..d288857414 100644 --- a/doc/messages/messages_list.rst +++ b/doc/messages/messages_list.rst @@ -1,7 +1,11 @@ .. _messages-list: -Pylint Messages -=============== +Overview of all Pylint messages +=============================== + +.. + NOTE This file is auto-generated. Make any changes to the associated + docs extension in 'pylint_messages.py'. Pylint can emit the following messages: @@ -54,6 +58,7 @@ All messages in the error category: error/bad-str-strip-call.rst error/bad-string-format-type.rst error/bad-super-call.rst + error/bidirectional-unicode.rst error/catching-non-exception.rst error/class-variable-slots-conflict.rst error/continue-in-finally.rst @@ -70,6 +75,12 @@ All messages in the error category: error/invalid-all-object.rst error/invalid-bool-returned.rst error/invalid-bytes-returned.rst + error/invalid-character-backspace.rst + error/invalid-character-carriage-return.rst + error/invalid-character-esc.rst + error/invalid-character-nul.rst + error/invalid-character-sub.rst + error/invalid-character-zero-width-space.rst error/invalid-class-object.rst error/invalid-envvar-value.rst error/invalid-format-returned.rst @@ -88,6 +99,7 @@ All messages in the error category: error/invalid-star-assignment-target.rst error/invalid-str-returned.rst error/invalid-unary-operand-type.rst + error/invalid-unicode-codec.rst error/logging-format-truncated.rst error/logging-too-few-args.rst error/logging-too-many-args.rst @@ -98,6 +110,8 @@ All messages in the error category: error/missing-format-string-key.rst error/missing-kwoa.rst error/mixed-format-string.rst + error/modified-iterating-dict.rst + error/modified-iterating-set.rst error/no-member.rst error/no-method-argument.rst error/no-name-in-module.rst @@ -228,6 +242,7 @@ All messages in the warning category: warning/logging-fstring-interpolation.rst warning/logging-not-lazy.rst warning/lost-exception.rst + warning/lru-cache-decorating-method.rst warning/misplaced-future.rst warning/missing-any-param-doc.rst warning/missing-format-argument-key.rst @@ -240,9 +255,11 @@ All messages in the warning category: warning/missing-type-doc.rst warning/missing-yield-doc.rst warning/missing-yield-type-doc.rst + warning/modified-iterating-list.rst warning/multiple-constructor-doc.rst warning/nan-comparison.rst warning/no-init.rst + warning/non-ascii-file-name.rst warning/non-parent-init-called.rst warning/non-str-assignment-to-dunder-name.rst warning/overlapping-except.rst @@ -257,6 +274,7 @@ All messages in the warning category: warning/redeclared-assigned-name.rst warning/redefined-builtin.rst warning/redefined-outer-name.rst + warning/redefined-slots-in-subclass.rst warning/redundant-returns-doc.rst warning/redundant-u-string-prefix.rst warning/redundant-unittest-assert.rst @@ -274,6 +292,7 @@ All messages in the warning category: warning/try-except-raise.rst warning/unbalanced-tuple-unpacking.rst warning/undefined-loop-variable.rst + warning/unnecessary-ellipsis.rst warning/unnecessary-lambda.rst warning/unnecessary-pass.rst warning/unnecessary-semicolon.rst @@ -327,6 +346,7 @@ All messages in the convention category: convention/bad-classmethod-argument.rst convention/bad-docstring-quotes.rst + convention/bad-file-encoding.rst convention/bad-mcs-classmethod-argument.rst convention/bad-mcs-method-argument.rst convention/compare-to-empty-string.rst @@ -351,6 +371,7 @@ All messages in the convention category: convention/mixed-line-endings.rst convention/multiple-imports.rst convention/multiple-statements.rst + convention/non-ascii-module-import.rst convention/non-ascii-name.rst convention/single-string-used-for-slots.rst convention/singleton-comparison.rst @@ -382,6 +403,7 @@ All renamed messages in the convention category: convention/len-as-condition.rst convention/missing-docstring.rst convention/old-misplaced-comparison-constant.rst + convention/old-non-ascii-name.rst Refactor diff --git a/doc/release.md b/doc/release.md index 0854464af9..e2196859e4 100644 --- a/doc/release.md +++ b/doc/release.md @@ -6,7 +6,7 @@ So, you want to release the `X.Y.Z` version of pylint ? 1. Check if the dependencies of the package are correct, make sure that astroid is pinned to the latest version. -2. Install the release dependencies `pip3 install pre-commit tbump` +2. Install the release dependencies `pip3 install pre-commit tbump tox` 3. Bump the version by using `tbump X.Y.Z --no-tag --no-push` 4. Check the result (Do `git diff vX.Y.Z-1 ChangeLog doc/whatsnew/` in particular). 5. Move back to a dev version for pylint with `tbump`: diff --git a/tbump.toml b/tbump.toml index b7855123d0..0429cbd3a7 100644 --- a/tbump.toml +++ b/tbump.toml @@ -28,6 +28,10 @@ src = "pylint/__pkginfo__.py" name = "Upgrade changelog" cmd = "python3 script/bump_changelog.py {new_version}" +[[before_commit]] +name = "Upgrade and check doc" +cmd = "tox -e docs||echo 'Hack so this command does not fail'" + [[before_commit]] name = "Upgrade copyrights" cmd = "pip3 install copyrite;copyrite --contribution-threshold 1 --change-threshold 3 --backend-type git --aliases=.copyrite_aliases . --jobs=8" From 9989444cbf822328adeb6b7b661a58c93f8c313b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:00:35 +0100 Subject: [PATCH 203/357] Fix remaining typing issues for ``PyLinter`` and related functions (#5663) --- pylint/constants.py | 6 ++++-- pylint/lint/pylinter.py | 10 ++++++++-- pylint/typing.py | 6 ++++++ pylint/utils/file_state.py | 4 +++- pylint/utils/linterstats.py | 9 +++------ 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pylint/constants.py b/pylint/constants.py index db54113ee4..813cd54dd8 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -2,11 +2,13 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import platform import sys +from typing import Dict import astroid import platformdirs from pylint.__pkginfo__ import __version__ +from pylint.typing import MessageTypesFullName PY37_PLUS = sys.version_info[:2] >= (3, 7) PY38_PLUS = sys.version_info[:2] >= (3, 8) @@ -24,7 +26,7 @@ # The line/node distinction does not apply to fatal errors and reports. _SCOPE_EXEMPT = "FR" -MSG_TYPES = { +MSG_TYPES: Dict[str, MessageTypesFullName] = { "I": "info", "C": "convention", "R": "refactor", @@ -32,7 +34,7 @@ "E": "error", "F": "fatal", } -MSG_TYPES_LONG = {v: k for k, v in MSG_TYPES.items()} +MSG_TYPES_LONG: Dict[str, str] = {v: k for k, v in MSG_TYPES.items()} MSG_TYPES_STATUS = {"I": 0, "C": 16, "R": 8, "W": 4, "E": 2, "F": 1} diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 379fefe040..f1954e3717 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -546,7 +546,7 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]: def __init__( self, options=(), - reporter=None, + reporter: Union[reporters.BaseReporter, reporters.MultiReporter, None] = None, option_groups=(), pylintrc=None, ): @@ -1552,7 +1552,11 @@ def _add_one_message( msg_cat = MSG_TYPES[message_definition.msgid[0]] self.msg_status |= MSG_TYPES_STATUS[message_definition.msgid[0]] self.stats.increase_single_message_count(msg_cat, 1) - self.stats.increase_single_module_message_count(self.current_name, msg_cat, 1) + self.stats.increase_single_module_message_count( + self.current_name, # type: ignore[arg-type] # Should be removable after https://github.com/PyCQA/pylint/pull/5580 + msg_cat, + 1, + ) try: self.stats.by_msg[message_definition.symbol] += 1 except KeyError: @@ -1669,6 +1673,8 @@ def _set_one_msg_status( ) -> None: """Set the status of an individual message""" if scope == "module": + assert isinstance(line, int) # should always be int inside module scope + self.file_state.set_msg_status(msg, line, enable) if not enable and msg.symbol != "locally-disabled": self.add_message( diff --git a/pylint/typing.py b/pylint/typing.py index 402a6f7162..09e4df126c 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -64,3 +64,9 @@ class ManagedMessage(NamedTuple): symbol: str line: Optional[int] is_disabled: bool + + +MessageTypesFullName = Literal[ + "convention", "error", "fatal", "info", "refactor", "statement", "warning" +] +"""All possible message categories.""" diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index 8527031eab..4252f01542 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -130,7 +130,7 @@ def set_msg_status(self, msg: "MessageDefinition", line: int, status: bool) -> N self._module_msgs_state[msg.msgid] = {line: status} def handle_ignored_message( - self, state_scope: Optional[Literal[0, 1, 2]], msgid: str, line: int + self, state_scope: Optional[Literal[0, 1, 2]], msgid: str, line: Optional[int] ) -> None: """Report an ignored message. @@ -139,6 +139,8 @@ def handle_ignored_message( or globally. """ if state_scope == MSG_STATE_SCOPE_MODULE: + assert isinstance(line, int) # should always be int inside module scope + try: orig_line = self._suppression_mapping[(msgid, line)] self._ignored_msgs[(msgid, orig_line)].add(line) diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py index 4a05c93df0..125954e4eb 100644 --- a/pylint/utils/linterstats.py +++ b/pylint/utils/linterstats.py @@ -4,6 +4,8 @@ import sys from typing import Dict, List, Optional, Set, cast +from pylint.typing import MessageTypesFullName + if sys.version_info >= (3, 8): from typing import Literal, TypedDict else: @@ -73,11 +75,6 @@ class ModuleStats(TypedDict): warning: int -ModuleStatsAttribute = Literal[ - "convention", "error", "fatal", "info", "refactor", "statement", "warning" -] - - # pylint: disable-next=too-many-instance-attributes class LinterStats: """Class used to linter stats""" @@ -290,7 +287,7 @@ def increase_single_message_count(self, type_name: str, increase: int) -> None: setattr(self, type_name, getattr(self, type_name) + increase) def increase_single_module_message_count( - self, modname: str, type_name: ModuleStatsAttribute, increase: int + self, modname: str, type_name: MessageTypesFullName, increase: int ) -> None: """Increase the message type count of an individual message type of a module""" self.by_module[modname][type_name] += increase From 851b63e897fdcdb07b939ac7873e9ea3a94f1115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 8 Feb 2022 23:17:58 +0100 Subject: [PATCH 204/357] Add parameter typing to ``PyLinter`` (#5781) --- pylint/lint/pylinter.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index f1954e3717..36651c04d6 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -545,11 +545,11 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]: def __init__( self, - options=(), + options: Tuple[Tuple[str, OptionDict], ...] = (), reporter: Union[reporters.BaseReporter, reporters.MultiReporter, None] = None, - option_groups=(), - pylintrc=None, - ): + option_groups: Tuple[Tuple[str, str], ...] = (), + pylintrc: Optional[str] = None, + ) -> None: """Some stuff has to be done before ancestors initialization... messages store / checkers / reporter / astroid manager """ @@ -581,9 +581,7 @@ def __init__( self.stats = LinterStats() # Attributes related to (command-line) options and their parsing - # pylint: disable-next=fixme - # TODO: Make these implicitly typing when typing for __init__ parameter is added - self._external_opts: Tuple[Tuple[str, OptionDict], ...] = options + self._external_opts = options self.options: Tuple[Tuple[str, OptionDict], ...] = ( options + PyLinter.make_options() ) From e3d5deca2886d9e2d5f2be2a252e39e02ae42b96 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 10 Feb 2022 09:49:36 -0500 Subject: [PATCH 205/357] Fix false negative for `used-before-assignment` when some except handlers don't define a name (#5764) Co-authored-by: Arianna --- ChangeLog | 6 + doc/whatsnew/2.13.rst | 6 + pylint/checkers/variables.py | 78 ++++++++--- ...ment_except_handler_for_try_with_return.py | 126 ++++++++++++++++++ ...ent_except_handler_for_try_with_return.txt | 6 + ...except_handler_for_try_with_return_py38.py | 46 +++++++ ...except_handler_for_try_with_return_py38.rc | 2 + 7 files changed, 252 insertions(+), 18 deletions(-) create mode 100644 tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.py create mode 100644 tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.rc diff --git a/ChangeLog b/ChangeLog index f786a21f93..e27ef4cb27 100644 --- a/ChangeLog +++ b/ChangeLog @@ -216,6 +216,12 @@ Release date: TBA Closes #5500 +* Fixed a false negative for ``used-before-assignment`` when some but not all + except handlers defined a name relied upon after an except block when the + corresponding try block contained a return statement. + + Closes #5524 + * When evaluating statements in the ``else`` clause of a loop, ``used-before-assignment`` assumes that assignments in the except blocks took place if the except handlers constituted the only ways for the loop to finish without diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 7daac870d0..57afc70fa2 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -257,6 +257,12 @@ Other Changes Closes #5500 +* Fixed a false negative for ``used-before-assignment`` when some but not all + except handlers defined a name relied upon after an except block when the + corresponding try block contained a return statement. + + Closes #5524 + * When evaluating statements in the ``else`` clause of a loop, ``used-before-assignment`` assumes that assignments in the except blocks took place if the except handlers constituted the only ways for the loop to finish without diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index cbe7b4f137..160680caad 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -703,17 +703,18 @@ def _uncertain_nodes_in_except_blocks( for other_node in found_nodes: other_node_statement = other_node.statement(future=True) # Only testing for statements in the except block of TryExcept - if not ( - isinstance(other_node_statement.parent, nodes.ExceptHandler) - and isinstance(other_node_statement.parent.parent, nodes.TryExcept) - ): + closest_except_handler = utils.get_node_first_ancestor_of_type( + other_node_statement, nodes.ExceptHandler + ) + if not closest_except_handler: continue # If the other node is in the same scope as this node, assume it executes - if other_node_statement.parent.parent_of(node): + if closest_except_handler.parent_of(node): continue + closest_try_except: nodes.TryExcept = closest_except_handler.parent try_block_returns = any( isinstance(try_statement, nodes.Return) - for try_statement in other_node_statement.parent.parent.body + for try_statement in closest_try_except.body ) # If the try block returns, assume the except blocks execute. if try_block_returns: @@ -722,28 +723,69 @@ def _uncertain_nodes_in_except_blocks( if ( isinstance(node_statement.parent, nodes.TryFinally) and node_statement in node_statement.parent.finalbody - # We have already tested that other_node_statement has two parents - # and it was TryExcept, so getting one more parent is safe. - and other_node_statement.parent.parent.parent.parent_of( - node_statement - ) + and closest_try_except.parent.parent_of(node_statement) ): uncertain_nodes.append(other_node) - else: - # Assume the except blocks execute. Possibility for a false negative - # if one of the except blocks does not define the name in question, - # raise, or return. See: https://github.com/PyCQA/pylint/issues/5524. + # Assume the except blocks execute, so long as each handler + # defines the name, raises, or returns. + elif all( + NamesConsumer._defines_name_raises_or_returns(node.name, handler) + for handler in closest_try_except.handlers + ): continue - if NamesConsumer._check_loop_finishes_via_except( - node, other_node_statement.parent.parent - ): + if NamesConsumer._check_loop_finishes_via_except(node, closest_try_except): continue # Passed all tests for uncertain execution uncertain_nodes.append(other_node) return uncertain_nodes + @staticmethod + def _defines_name_raises_or_returns( + name: str, handler: nodes.ExceptHandler + ) -> bool: + """Return True if some child of `handler` defines the name `name`, + raises, or returns. + """ + + def _define_raise_or_return(stmt: nodes.NodeNG) -> bool: + if isinstance(stmt, (nodes.Raise, nodes.Return)): + return True + if isinstance(stmt, nodes.Assign): + for target in stmt.targets: + for elt in utils.get_all_elements(target): + if isinstance(elt, nodes.AssignName) and elt.name == name: + return True + if isinstance(stmt, nodes.If): + # Check for assignments inside the test + if ( + isinstance(stmt.test, nodes.NamedExpr) + and stmt.test.target.name == name + ): + return True + if isinstance(stmt.test, nodes.Call): + for arg_or_kwarg in stmt.test.args + [ + kw.value for kw in stmt.test.keywords + ]: + if ( + isinstance(arg_or_kwarg, nodes.NamedExpr) + and arg_or_kwarg.target.name == name + ): + return True + return False + + for stmt in handler.get_children(): + if _define_raise_or_return(stmt): + return True + if isinstance(stmt, (nodes.If, nodes.With)): + if any( + _define_raise_or_return(nested_stmt) + for nested_stmt in stmt.get_children() + ): + return True + return False + @staticmethod def _check_loop_finishes_via_except( node: nodes.NodeNG, other_node_try_except: nodes.TryExcept diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py index 3afd9375ac..dc9661380d 100644 --- a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py @@ -49,3 +49,129 @@ def func_ok3(var): except ZeroDivisionError: msg = "Division by 0" print(msg) + + +def func_ok4(var): + """Define "msg" with a chained assignment.""" + try: + return 1 / var.some_other_func() + except AttributeError: + msg2 = msg = "Division by 0" + print(msg2) + except ZeroDivisionError: + msg = "Division by 0" + print(msg) + + +def func_ok5(var): + """Define 'msg' via unpacked iterable.""" + try: + return 1 / var.some_other_func() + except AttributeError: + msg, msg2 = ["Division by 0", "Division by 0"] + print(msg2) + except ZeroDivisionError: + msg = "Division by 0" + print(msg) + + +def func_ok6(var): + """Define 'msg' in one handler nested under if block.""" + err_message = False + try: + return 1 / var.some_other_func() + except ZeroDivisionError: + if err_message: + msg = "Division by 0" + else: + msg = None + print(msg) + + +def func_ok7(var): + """Define 'msg' in one handler nested under with statement.""" + try: + return 1 / var.some_other_func() + except ZeroDivisionError: + with open(__file__, encoding='utf-8') as my_file: + msg = "Division by 0" + my_file.write(msg) + print(msg) + + +def func_invalid1(var): + """'msg' is not defined in one handler.""" + try: + return 1 / var.some_other_func() + except AttributeError: + pass + except ZeroDivisionError: + msg = "Division by 0" + print(msg) # [used-before-assignment] + + +def func_invalid2(var): + """'msg' is not defined in one handler.""" + try: + return 1 / var.some_other_func() + except AttributeError: + msg: str + except ZeroDivisionError: + msg = "Division by 0" + print(msg) # [used-before-assignment] + + +def func_invalid3(var): + """'msg' is not defined in one handler, but is defined in another + nested under an if. Nesting under an if tests that the implementation + does not assume direct parentage between `msg=` and `except`, and + the prior except is necessary to raise the message. + """ + err_message = False + try: + return 1 / var.some_other_func() + except AttributeError: + pass + except ZeroDivisionError: + if err_message: + msg = "Division by 0" + else: + msg = None + print(msg) # [used-before-assignment] + + +def func_invalid4(var): + """Define 'msg' in one handler nested under with statement.""" + try: + return 1 / var.some_other_func() + except AttributeError: + pass + except ZeroDivisionError: + with open(__file__, encoding='utf-8') as my_file: + msg = "Division by 0" + my_file.write("****") + print(msg) # [used-before-assignment] + + +def func_invalid5(var): + """Define 'msg' in one handler only via chained assignment.""" + try: + return 1 / var.some_other_func() + except AttributeError: + pass + except ZeroDivisionError: + msg2 = msg = "Division by 0" + print(msg2) + print(msg) # [used-before-assignment] + + +def func_invalid6(var): + """Define 'msg' in one handler only via unpacked iterable.""" + try: + return 1 / var.some_other_func() + except AttributeError: + pass + except ZeroDivisionError: + msg, msg2 = ["Division by 0"] * 2 + print(msg2) + print(msg) # [used-before-assignment] diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt index 9f05408f14..55761a411c 100644 --- a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt @@ -1 +1,7 @@ used-before-assignment:16:14:16:29:function:Using variable 'failure_message' before assignment:CONTROL_FLOW +used-before-assignment:110:10:110:13:func_invalid1:Using variable 'msg' before assignment:CONTROL_FLOW +used-before-assignment:121:10:121:13:func_invalid2:Using variable 'msg' before assignment:CONTROL_FLOW +used-before-assignment:140:10:140:13:func_invalid3:Using variable 'msg' before assignment:CONTROL_FLOW +used-before-assignment:153:10:153:13:func_invalid4:Using variable 'msg' before assignment:CONTROL_FLOW +used-before-assignment:165:10:165:13:func_invalid5:Using variable 'msg' before assignment:CONTROL_FLOW +used-before-assignment:177:10:177:13:func_invalid6:Using variable 'msg' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.py b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.py new file mode 100644 index 0000000000..a43a89aa10 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.py @@ -0,0 +1,46 @@ +"""Tests for used-before-assignment with assignments in except handlers after +try blocks with return statements. +See: https://github.com/PyCQA/pylint/issues/5500. +""" +# pylint: disable=inconsistent-return-statements + + +# Named expressions +def func_ok_namedexpr_1(var): + """'msg' is defined in one handler with a named expression under an if.""" + try: + return 1 / var.some_other_func() + except AttributeError: + if (msg := var.get_msg()): + pass + except ZeroDivisionError: + msg = "Division by 0" + print(msg) + + +def func_ok_namedexpr_2(var): + """'msg' is defined in one handler with a named expression occurring + in a call used in an if test. + """ + try: + return 1 / var.some_other_func() + except AttributeError: + if print(msg := var.get_msg()): + pass + except ZeroDivisionError: + msg = "Division by 0" + print(msg) + + +def func_ok_namedexpr_3(var): + """'msg' is defined in one handler with a named expression occurring + as a keyword in a call used in an if test. + """ + try: + return 1 / var.some_other_func() + except AttributeError: + if print("zero!", "here", sep=(msg := var.get_sep())): + pass + except ZeroDivisionError: + msg = "Division by 0" + print(msg) diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.rc b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return_py38.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 From 595ec422d6f9bd32f42c356d2f316ec69e0f7bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 10 Feb 2022 19:30:15 +0100 Subject: [PATCH 206/357] Update ``pydocstringformatter`` to 0.4.0 (#5787) --- .pre-commit-config.yaml | 2 +- doc/exts/pylint_extensions.py | 2 +- doc/exts/pylint_messages.py | 6 +- examples/custom_raw.py | 4 +- pylint/__init__.py | 8 +- pylint/__pkginfo__.py | 2 +- pylint/checkers/__init__.py | 6 +- pylint/checkers/base.py | 64 ++++++++-------- pylint/checkers/base_checker.py | 6 +- pylint/checkers/classes/class_checker.py | 48 ++++++------ .../classes/special_methods_checker.py | 2 +- pylint/checkers/deprecated.py | 12 +-- pylint/checkers/design_analysis.py | 32 ++++---- pylint/checkers/ellipsis_checker.py | 2 +- pylint/checkers/exceptions.py | 4 +- pylint/checkers/format.py | 18 ++--- pylint/checkers/imports.py | 46 ++++++------ pylint/checkers/logging.py | 4 +- pylint/checkers/mapreduce_checker.py | 6 +- pylint/checkers/misc.py | 8 +- pylint/checkers/newstyle.py | 6 +- pylint/checkers/non_ascii_names.py | 4 +- pylint/checkers/raw_metrics.py | 10 +-- .../implicit_booleaness_checker.py | 2 +- pylint/checkers/refactoring/not_checker.py | 2 +- .../refactoring/refactoring_checker.py | 26 +++---- pylint/checkers/similar.py | 52 ++++++------- pylint/checkers/spelling.py | 6 +- pylint/checkers/strings.py | 6 +- pylint/checkers/threading_checker.py | 2 +- pylint/checkers/typecheck.py | 20 ++--- pylint/checkers/unicode.py | 36 ++++----- pylint/checkers/unsupported_version.py | 4 +- pylint/checkers/utils.py | 62 ++++++++-------- pylint/checkers/variables.py | 70 +++++++++--------- pylint/config/find_default_config_files.py | 2 +- pylint/config/man_help_formatter.py | 2 +- pylint/config/option.py | 2 +- pylint/config/option_manager_mixin.py | 24 +++--- pylint/config/options_provider_mixin.py | 16 ++-- pylint/epylint.py | 2 +- pylint/exceptions.py | 12 +-- pylint/extensions/__init__.py | 2 +- pylint/extensions/_check_docs_utils.py | 10 +-- pylint/extensions/check_elif.py | 6 +- pylint/extensions/comparison_placement.py | 2 +- pylint/extensions/docparams.py | 4 +- pylint/extensions/docstyle.py | 2 +- pylint/extensions/empty_comment.py | 4 +- pylint/extensions/mccabe.py | 8 +- pylint/extensions/overlapping_exceptions.py | 2 +- pylint/extensions/redefined_variable_type.py | 2 +- pylint/graph.py | 10 +-- pylint/interfaces.py | 16 ++-- pylint/lint/__init__.py | 2 +- pylint/lint/expand_modules.py | 4 +- pylint/lint/parallel.py | 4 +- pylint/lint/pylinter.py | 74 +++++++++---------- pylint/lint/report_functions.py | 6 +- pylint/lint/run.py | 32 ++++---- pylint/lint/utils.py | 2 +- pylint/message/message.py | 2 +- pylint/message/message_definition.py | 6 +- pylint/message/message_definition_store.py | 4 +- pylint/pyreverse/__init__.py | 2 +- pylint/pyreverse/diadefslib.py | 44 +++++------ pylint/pyreverse/diagrams.py | 50 ++++++------- pylint/pyreverse/dot_printer.py | 2 +- pylint/pyreverse/inspector.py | 28 +++---- pylint/pyreverse/main.py | 6 +- pylint/pyreverse/mermaidjs_printer.py | 8 +- pylint/pyreverse/plantuml_printer.py | 4 +- pylint/pyreverse/printer.py | 6 +- pylint/pyreverse/utils.py | 32 ++++---- pylint/pyreverse/vcg_printer.py | 4 +- pylint/pyreverse/writer.py | 20 ++--- pylint/reporters/__init__.py | 4 +- pylint/reporters/base_reporter.py | 14 ++-- pylint/reporters/collecting_reporter.py | 2 +- pylint/reporters/json_reporter.py | 4 +- pylint/reporters/multi_reporter.py | 12 +-- pylint/reporters/reports_handler_mix_in.py | 10 +-- pylint/reporters/text.py | 26 +++---- pylint/reporters/ureports/base_writer.py | 18 ++--- pylint/reporters/ureports/nodes.py | 22 +++--- pylint/reporters/ureports/text_writer.py | 16 ++-- pylint/testutils/__init__.py | 2 +- pylint/testutils/checker_test_case.py | 2 +- pylint/testutils/configuration_test.py | 2 +- pylint/testutils/get_test_info.py | 2 +- pylint/testutils/lint_module_test.py | 2 +- pylint/testutils/output_line.py | 4 +- pylint/testutils/primer.py | 20 ++--- pylint/testutils/reporter_for_tests.py | 8 +- pylint/typing.py | 10 +-- pylint/utils/__init__.py | 2 +- pylint/utils/ast_walker.py | 2 +- pylint/utils/docs.py | 8 +- pylint/utils/file_state.py | 4 +- pylint/utils/linterstats.py | 46 ++++++------ pylint/utils/pragma_parser.py | 6 +- pylint/utils/utils.py | 20 ++--- script/fix_documentation.py | 2 +- script/get_unused_message_id_category.py | 4 +- tests/benchmark/test_baseline_benchmarks.py | 36 ++++----- tests/checkers/unittest_design.py | 2 +- tests/checkers/unittest_format.py | 6 +- tests/checkers/unittest_non_ascii_name.py | 6 +- tests/checkers/unittest_similar.py | 2 +- tests/checkers/unittest_typecheck.py | 2 +- .../unittest_unicode/unittest_bad_chars.py | 4 +- .../unittest_bidirectional_unicode.py | 2 +- .../unittest_unicode/unittest_functions.py | 4 +- .../unittest_invalid_encoding.py | 4 +- tests/checkers/unittest_utils.py | 2 +- tests/config/file_to_lint.py | 2 +- tests/config/unittest_config.py | 2 +- tests/conftest.py | 2 +- tests/extensions/test_check_docs_utils.py | 2 +- tests/lint/unittest_expand_modules.py | 8 +- tests/lint/unittest_lint.py | 4 +- tests/message/unittest_message_id_store.py | 2 +- tests/primer/test_primer_external.py | 6 +- tests/primer/test_primer_stdlib.py | 2 +- .../profile/test_profile_against_externals.py | 4 +- tests/pyreverse/conftest.py | 2 +- tests/pyreverse/test_diadefs.py | 14 ++-- tests/pyreverse/test_diagrams.py | 2 +- tests/pyreverse/test_inspector.py | 2 +- tests/pyreverse/test_main.py | 2 +- tests/pyreverse/test_printer_factory.py | 2 +- tests/pyreverse/test_utils.py | 6 +- tests/pyreverse/test_writer.py | 4 +- tests/test_check_parallel.py | 34 ++++----- tests/test_epylint.py | 2 +- tests/test_func.py | 4 +- tests/test_import_graph.py | 4 +- tests/test_pylint_runners.py | 2 +- tests/test_regr.py | 2 +- tests/test_self.py | 6 +- tests/testutils/test_decorator.py | 2 +- tests/unittest_reporters_json.py | 4 +- tests/unittest_reporting.py | 6 +- tests/utils/unittest_utils.py | 2 +- 144 files changed, 770 insertions(+), 770 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d625135926..33de05026c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -96,7 +96,7 @@ repos: args: [--prose-wrap=always, --print-width=88] exclude: tests(/.*)*/data - repo: https://github.com/DanielNoord/pydocstringformatter - rev: v0.2.0 + rev: v0.4.0 hooks: - id: pydocstringformatter exclude: *fixtures diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py index 2d1e919480..d8f8dfeca5 100755 --- a/doc/exts/pylint_extensions.py +++ b/doc/exts/pylint_extensions.py @@ -16,7 +16,7 @@ def builder_inited(app): - """Output full documentation in ReST format for all extension modules""" + """Output full documentation in ReST format for all extension modules.""" # PACKAGE/docs/exts/pylint_extensions.py --> PACKAGE/ base_path = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index 32541788fc..1ca3bdb88a 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -18,10 +18,10 @@ from pylint.utils import get_rst_title PYLINT_BASE_PATH = Path(__file__).resolve().parent.parent.parent -"""Base path to the project folder""" +"""Base path to the project folder.""" PYLINT_MESSAGES_PATH = PYLINT_BASE_PATH / "doc" / "messages" -"""Path to the messages documentation folder""" +"""Path to the messages documentation folder.""" MSG_TYPES_DOC = {k: v if v != "info" else "information" for k, v in MSG_TYPES.items()} @@ -218,7 +218,7 @@ def build_messages_pages(app: Optional[Sphinx]) -> None: def setup(app: Sphinx) -> None: - """Connects the extension to the Sphinx process""" + """Connects the extension to the Sphinx process.""" # Register callback at the builder-inited Sphinx event # See https://www.sphinx-doc.org/en/master/extdev/appapi.html app.connect("builder-inited", build_messages_pages) diff --git a/examples/custom_raw.py b/examples/custom_raw.py index 045f22ff49..365e9b7fa0 100644 --- a/examples/custom_raw.py +++ b/examples/custom_raw.py @@ -10,7 +10,7 @@ class MyRawChecker(BaseChecker): - """check for line continuations with '\' instead of using triple + """Check for line continuations with '\' instead of using triple quoted string or parenthesis """ @@ -30,7 +30,7 @@ class MyRawChecker(BaseChecker): options = () def process_module(self, node: nodes.Module) -> None: - """process a module + """Process a module. the module's content is accessible via node.stream() function """ diff --git a/pylint/__init__.py b/pylint/__init__.py index 16d81d9036..02df46054e 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -19,7 +19,7 @@ def run_pylint(argv: Optional[Sequence[str]] = None): - """Run pylint + """Run pylint. argv can be a sequence of strings normally supplied as arguments on the command line """ @@ -32,7 +32,7 @@ def run_pylint(argv: Optional[Sequence[str]] = None): def run_epylint(argv: Optional[Sequence[str]] = None): - """Run epylint + """Run epylint. argv can be a list of strings normally supplied as arguments on the command line """ @@ -42,7 +42,7 @@ def run_epylint(argv: Optional[Sequence[str]] = None): def run_pyreverse(argv: Optional[Sequence[str]] = None): - """Run pyreverse + """Run pyreverse. argv can be a sequence of strings normally supplied as arguments on the command line """ @@ -52,7 +52,7 @@ def run_pyreverse(argv: Optional[Sequence[str]] = None): def run_symilar(argv: Optional[Sequence[str]] = None): - """Run symilar + """Run symilar. argv can be a sequence of strings normally supplied as arguments on the command line """ diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index b2546d3a1f..9e0f1edaa2 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -6,7 +6,7 @@ def get_numversion_from_version(v: str) -> Tuple: - """Kept for compatibility reason + """Kept for compatibility reason. See https://github.com/PyCQA/pylint/issues/4399 https://github.com/PyCQA/pylint/issues/4420, diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 5ca89f7493..e99560faa3 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -19,7 +19,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""utilities methods and classes for checkers +"""Utilities methods and classes for checkers. Base id of standard checkers (used in msg and report ids): 01: base @@ -75,7 +75,7 @@ def table_lines_from_stats( old_stats: Optional[LinterStats], stat_type: Literal["duplicated_lines", "message_types"], ) -> List[str]: - """get values listed in from and , + """Get values listed in from and , and return a formatted list of values, designed to be given to a ureport.Table object """ @@ -138,7 +138,7 @@ def table_lines_from_stats( def initialize(linter): - """initialize linter with checkers in this package""" + """Initialize linter with checkers in this package.""" register_plugins(linter, __path__[0]) diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 8ed43810f1..e2a3ece496 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -68,7 +68,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""basic checker for Python code""" +"""Basic checker for Python code.""" import collections import itertools import re @@ -261,12 +261,12 @@ def _redefines_import(node): def in_loop(node: nodes.NodeNG) -> bool: - """Return whether the node is inside a kind of for loop""" + """Return whether the node is inside a kind of for loop.""" return any(isinstance(parent, LOOPLIKE_NODES) for parent in node.node_ancestors()) def in_nested_list(nested_list, obj): - """return true if the object is an element of or of a nested + """Return true if the object is an element of or of a nested list """ for elmt in nested_list: @@ -401,7 +401,7 @@ def report_by_type_stats( stats: LinterStats, old_stats: Optional[LinterStats], ): - """make a report of + """Make a report of. * percentage of different types documented * percentage of different types with a bad name @@ -438,7 +438,7 @@ def report_by_type_stats( def redefined_by_decorator(node): - """return True if the object is a method redefined via decorator. + """Return True if the object is a method redefined via decorator. For example: @property @@ -749,7 +749,7 @@ def visit_while(self, node: nodes.While) -> None: @utils.check_messages("nonexistent-operator") def visit_unaryop(self, node: nodes.UnaryOp) -> None: - """Check use of the non-existent ++ and -- operators""" + """Check use of the non-existent ++ and -- operators.""" if ( (node.op in "+-") and isinstance(node.operand, nodes.UnaryOp) @@ -844,7 +844,7 @@ def _check_else_on_loop(self, node): ) def _check_in_loop(self, node, node_name): - """check that a node is inside a for or while loop""" + """Check that a node is inside a for or while loop.""" for parent in node.node_ancestors(): if isinstance(parent, (nodes.For, nodes.While)): if node not in parent.orelse: @@ -862,7 +862,7 @@ def _check_in_loop(self, node, node_name): self.add_message("not-in-loop", node=node, args=node_name) def _check_redefinition(self, redeftype, node): - """check for redefinition of a function / method / class name""" + """Check for redefinition of a function / method / class name.""" parent_frame = node.parent.frame(future=True) # Ignore function stubs created for type information @@ -939,7 +939,7 @@ def _check_redefinition(self, redeftype, node): class BasicChecker(_BasicChecker): - """checks for : + """Checks for : * doc strings * number of arguments, local variables, branches, returns and statements in functions, methods @@ -1089,7 +1089,7 @@ def __init__(self, linter): self._tryfinallys = None def open(self): - """initialize visit variables and statistics""" + """Initialize visit variables and statistics.""" py_version = get_global_option(self, "py-version") self._py38_plus = py_version >= (3, 8) self._tryfinallys = [] @@ -1163,11 +1163,11 @@ def _check_using_constant_test(self, node, test): self.add_message("using-constant-test", node=node) def visit_module(self, _: nodes.Module) -> None: - """check module name, docstring and required arguments""" + """Check module name, docstring and required arguments.""" self.linter.stats.node_count["module"] += 1 def visit_classdef(self, _: nodes.ClassDef) -> None: - """check module name, docstring and redefinition + """Check module name, docstring and redefinition increment branch counter """ self.linter.stats.node_count["klass"] += 1 @@ -1176,7 +1176,7 @@ def visit_classdef(self, _: nodes.ClassDef) -> None: "pointless-statement", "pointless-string-statement", "expression-not-assigned" ) def visit_expr(self, node: nodes.Expr) -> None: - """Check for various kind of statements without effect""" + """Check for various kind of statements without effect.""" expr = node.value if isinstance(expr, nodes.Const) and isinstance(expr.value, str): # treat string statement in a separated message @@ -1246,7 +1246,7 @@ def _has_variadic_argument(args, variadic_name): @utils.check_messages("unnecessary-lambda") def visit_lambda(self, node: nodes.Lambda) -> None: - """Check whether the lambda is suspicious""" + """Check whether the lambda is suspicious.""" # if the body of the lambda is a call expression with the same # argument list as the lambda itself, then the lambda is # possibly unnecessary and at least suspicious. @@ -1306,7 +1306,7 @@ def visit_lambda(self, node: nodes.Lambda) -> None: @utils.check_messages("dangerous-default-value") def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """check function name, docstring, arguments, redefinition, + """Check function name, docstring, arguments, redefinition, variable names, max locals """ if node.is_method(): @@ -1370,7 +1370,7 @@ def visit_return(self, node: nodes.Return) -> None: @utils.check_messages("unreachable") def visit_continue(self, node: nodes.Continue) -> None: - """check is the node has a right sibling (if so, that's some unreachable + """Check is the node has a right sibling (if so, that's some unreachable code) """ self._check_unreachable(node) @@ -1389,7 +1389,7 @@ def visit_break(self, node: nodes.Break) -> None: @utils.check_messages("unreachable") def visit_raise(self, node: nodes.Raise) -> None: - """check if the node has a right sibling (if so, that's some unreachable + """Check if the node has a right sibling (if so, that's some unreachable code) """ self._check_unreachable(node) @@ -1419,7 +1419,7 @@ def _check_misplaced_format_function(self, call_node): "eval-used", "exec-used", "bad-reversed-sequence", "misplaced-format-function" ) def visit_call(self, node: nodes.Call) -> None: - """visit a Call node -> check if this is not a disallowed builtin + """Visit a Call node -> check if this is not a disallowed builtin call and check for * or ** use """ self._check_misplaced_format_function(node) @@ -1437,7 +1437,7 @@ def visit_call(self, node: nodes.Call) -> None: @utils.check_messages("assert-on-tuple", "assert-on-string-literal") def visit_assert(self, node: nodes.Assert) -> None: - """check whether assert is used on a tuple or string literal.""" + """Check whether assert is used on a tuple or string literal.""" if ( node.fail is None and isinstance(node.test, nodes.Tuple) @@ -1454,7 +1454,7 @@ def visit_assert(self, node: nodes.Assert) -> None: @utils.check_messages("duplicate-key") def visit_dict(self, node: nodes.Dict) -> None: - """check duplicate key in dictionary""" + """Check duplicate key in dictionary.""" keys = set() for k, _ in node.items: if isinstance(k, nodes.Const): @@ -1468,15 +1468,15 @@ def visit_dict(self, node: nodes.Dict) -> None: keys.add(key) def visit_tryfinally(self, node: nodes.TryFinally) -> None: - """update try...finally flag""" + """Update try...finally flag.""" self._tryfinallys.append(node) def leave_tryfinally(self, _: nodes.TryFinally) -> None: - """update try...finally flag""" + """Update try...finally flag.""" self._tryfinallys.pop() def _check_unreachable(self, node): - """check unreachable code""" + """Check unreachable code.""" unreach_stmt = node.next_sibling() if unreach_stmt is not None: if ( @@ -1492,7 +1492,7 @@ def _check_unreachable(self, node): self.add_message("unreachable", node=unreach_stmt) def _check_not_in_finally(self, node, node_name, breaker_classes=()): - """check that a node is not inside a 'finally' clause of a + """Check that a node is not inside a 'finally' clause of a 'try...finally' statement. If we find a parent which type is in breaker_classes before a 'try...finally' block we skip the whole check. @@ -1511,7 +1511,7 @@ def _check_not_in_finally(self, node, node_name, breaker_classes=()): _parent = _node.parent def _check_reversed(self, node): - """check that the argument to `reversed` is a sequence""" + """Check that the argument to `reversed` is a sequence.""" try: argument = utils.safe_infer(utils.get_argument_from_call(node, position=0)) except utils.NoSuchArgumentError: @@ -1942,7 +1942,7 @@ def visit_global(self, node: nodes.Global) -> None: @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword") def visit_assignname(self, node: nodes.AssignName) -> None: - """check module level assigned names""" + """Check module level assigned names.""" self._check_assign_to_new_keyword_violation(node.name, node) frame = node.frame(future=True) assign_type = node.assign_type() @@ -1981,7 +1981,7 @@ def visit_assignname(self, node: nodes.AssignName) -> None: self._check_name("class_attribute", node.name, node) def _recursive_check_names(self, args): - """check names in a possibly recursive list """ + """Check names in a possibly recursive list .""" for arg in args: if isinstance(arg, nodes.AssignName): self._check_name("argument", arg.name, arg) @@ -2029,7 +2029,7 @@ def _name_disallowed_by_regex(self, name: str) -> bool: ) def _check_name(self, node_type, name, node, confidence=interfaces.HIGH): - """check for a name using the type's regexp""" + """Check for a name using the type's regexp.""" def _should_exempt_from_invalid_name(node): if node_type == "variable": @@ -2190,7 +2190,7 @@ def _check_docstring( report_missing=True, confidence=interfaces.HIGH, ): - """Check if the node has a non-empty docstring""" + """Check if the node has a non-empty docstring.""" docstring = node.doc if docstring is None: docstring = _infer_dunder_doc_attribute(node) @@ -2243,7 +2243,7 @@ def _check_docstring( class PassChecker(_BasicChecker): - """check if the pass statement is really necessary""" + """Check if the pass statement is really necessary.""" msgs = { "W0107": ( @@ -2285,7 +2285,7 @@ def _infer_dunder_doc_attribute(node): class ComparisonChecker(_BasicChecker): - """Checks for comparisons + """Checks for comparisons. - singleton comparison: 'expr == True', 'expr == False' and 'expr == None' - yoda condition: 'const "comp" right' where comp can be '==', '!=', '<', @@ -2339,7 +2339,7 @@ class ComparisonChecker(_BasicChecker): def _check_singleton_comparison( self, left_value, right_value, root_node, checking_for_absence: bool = False ): - """Check if == or != is being used to compare a singleton value""" + """Check if == or != is being used to compare a singleton value.""" singleton_values = (True, False, None) def _is_singleton_const(node) -> bool: diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index 5a6efd20c2..10e990f935 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -48,7 +48,7 @@ class BaseChecker(OptionsProviderMixIn): enabled: bool = True def __init__(self, linter=None): - """checker instances should have the linter as argument + """Checker instances should have the linter as argument. :param ILinter linter: is an object implementing ILinter. """ @@ -189,10 +189,10 @@ def get_message_definition(self, msgid): raise InvalidMessageError(error_msg) def open(self): - """called before visiting project (i.e. set of modules)""" + """Called before visiting project (i.e. set of modules).""" def close(self): - """called after visiting project (i.e set of modules)""" + """Called after visiting project (i.e set of modules).""" class BaseTokenChecker(BaseChecker): diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 513b27308f..dcde9231b0 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -49,7 +49,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Classes checker for Python code""" +"""Classes checker for Python code.""" import collections from itertools import chain, zip_longest from typing import Dict, List, Pattern, Set @@ -186,7 +186,7 @@ class _DefaultMissing: def _has_different_parameters_default_value(original, overridden): - """Check if original and overridden methods arguments have different default values + """Check if original and overridden methods arguments have different default values. Return True if one of the overridden arguments has a default value different from the default value of the original argument @@ -257,7 +257,7 @@ def _different_parameters( overridden: nodes.FunctionDef, dummy_parameter_regex: Pattern, ) -> List[str]: - """Determine if the two methods have different parameters + """Determine if the two methods have different parameters. They are considered to have different parameters if: @@ -690,7 +690,7 @@ def accessed(self, scope): class ClassChecker(BaseChecker): - """checks for : + """Checks for : * methods without self as first argument * overridden methods signature * access only to existent members via self @@ -800,7 +800,7 @@ def _ignore_mixin(self): "redefined-slots-in-subclass", ) def visit_classdef(self, node: nodes.ClassDef) -> None: - """init visit variable _accessed""" + """Init visit variable _accessed.""" self._check_bases_classes(node) # if not an exception or a metaclass if node.type == "class" and has_known_bases(node): @@ -849,7 +849,7 @@ def _check_proper_bases(self, node): ) def _check_typing_final(self, node: nodes.ClassDef) -> None: - """Detect that a class does not subclass a class decorated with `typing.final`""" + """Detect that a class does not subclass a class decorated with `typing.final`.""" if not self._py38_plus: return for base in node.bases: @@ -869,7 +869,7 @@ def _check_typing_final(self, node: nodes.ClassDef) -> None: @check_messages("unused-private-member", "attribute-defined-outside-init") def leave_classdef(self, node: nodes.ClassDef) -> None: - """close a class node: + """Close a class node: check that instance attributes are defined in __init__ and check access to existent members """ @@ -929,7 +929,7 @@ def _check_unused_private_functions(self, node: nodes.ClassDef) -> None: ) def _check_unused_private_variables(self, node: nodes.ClassDef) -> None: - """Check if private variables are never used within a class""" + """Check if private variables are never used within a class.""" for assign_name in node.nodes_of_class(nodes.AssignName): if isinstance(assign_name.parent, nodes.Arguments): continue # Ignore function arguments @@ -1074,7 +1074,7 @@ def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None: ) def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """check method arguments, overriding""" + """Check method arguments, overriding.""" # ignore actual functions if not node.is_method(): return @@ -1171,7 +1171,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: visit_asyncfunctiondef = visit_functiondef def _check_useless_super_delegation(self, function): - """Check if the given function node is an useless method override + """Check if the given function node is an useless method override. We consider it *useless* if it uses the super() builtin, but having nothing additional whatsoever than not implementing the method at all. @@ -1370,7 +1370,7 @@ def _check_redefined_slots( slots_node: nodes.NodeNG, slots_list: List[nodes.NodeNG], ) -> None: - """Check if `node` redefines a slot which is defined in an ancestor class""" + """Check if `node` redefines a slot which is defined in an ancestor class.""" slots_names: List[str] = [] for slot in slots_list: if isinstance(slot, nodes.Const): @@ -1426,7 +1426,7 @@ def _check_slots_elt(self, elt, node): ) def leave_functiondef(self, node: nodes.FunctionDef) -> None: - """on method node, check if this method couldn't be a function + """On method node, check if this method couldn't be a function. ignore class, static and abstract methods, initializer, methods overridden from a parent class. @@ -1455,7 +1455,7 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None: leave_asyncfunctiondef = leave_functiondef def visit_attribute(self, node: nodes.Attribute) -> None: - """check if the getattr is an access to a class member + """Check if the getattr is an access to a class member if so, register it. Also check for access to protected class member from outside its class (but ignore __special__ methods) @@ -1566,7 +1566,7 @@ def visit_assign(self, assign_node: nodes.Assign) -> None: self._check_protected_attribute_access(node) def _check_classmethod_declaration(self, node): - """Checks for uses of classmethod() or staticmethod() + """Checks for uses of classmethod() or staticmethod(). When a @classmethod or @staticmethod decorator should be used instead. A message will be emitted only if the assignment is at a class scope @@ -1699,7 +1699,7 @@ def _check_protected_attribute_access(self, node: nodes.Attribute): @staticmethod def _is_called_inside_special_method(node: nodes.NodeNG) -> bool: - """Returns true if the node is located inside a special (aka dunder) method""" + """Returns true if the node is located inside a special (aka dunder) method.""" frame_name = node.frame(future=True).name return frame_name and frame_name in PYMETHODS @@ -1751,7 +1751,7 @@ def _is_class_attribute(name, klass): return False def visit_name(self, node: nodes.Name) -> None: - """check if the name handle an access to a class member + """Check if the name handle an access to a class member if so, register it """ if self._first_attrs and ( @@ -1760,7 +1760,7 @@ def visit_name(self, node: nodes.Name) -> None: self._meth_could_be_func = False def _check_accessed_members(self, node, accessed): - """check that accessed members are defined""" + """Check that accessed members are defined.""" excs = ("AttributeError", "Exception", "BaseException") for attr, nodes_lst in accessed.items(): try: @@ -1820,7 +1820,7 @@ def _check_accessed_members(self, node, accessed): ) def _check_first_arg_for_type(self, node, metaclass=0): - """check the name of first argument, expect: + """Check the name of first argument, expect:. * 'self' for a regular method * 'cls' for a class method or a metaclass regular method (actually @@ -1896,7 +1896,7 @@ def _check_first_arg_config(self, first, config, node, message, method_name): self.add_message(message, args=(method_name, valid), node=node) def _check_bases_classes(self, node): - """check that the given class node implements abstract methods from + """Check that the given class node implements abstract methods from base classes """ @@ -1923,7 +1923,7 @@ def is_abstract(method): self.add_message("abstract-method", node=node, args=(name, owner.name)) def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> None: - """check that the __init__ method call super or ancestors'__init__ + """Check that the __init__ method call super or ancestors'__init__ method (unless it is used for type hinting with `typing.overload`) """ if not self.linter.is_message_enabled( @@ -2006,7 +2006,7 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No ) def _check_signature(self, method1, refmethod, class_type, cls): - """check that the signature of the two given methods match""" + """Check that the signature of the two given methods match.""" if not ( isinstance(method1, nodes.FunctionDef) and isinstance(refmethod, nodes.FunctionDef) @@ -2084,14 +2084,14 @@ def _check_signature(self, method1, refmethod, class_type, cls): ) def _uses_mandatory_method_param(self, node): - """Check that attribute lookup name use first attribute variable name + """Check that attribute lookup name use first attribute variable name. Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. """ return self._is_mandatory_method_param(node.expr) def _is_mandatory_method_param(self, node: nodes.NodeNG) -> bool: - """Check if nodes.Name corresponds to first attribute variable name + """Check if nodes.Name corresponds to first attribute variable name. Name is `self` for method, `cls` for classmethod and `mcs` for metaclass. """ @@ -2113,7 +2113,7 @@ def _is_mandatory_method_param(self, node: nodes.NodeNG) -> bool: def _ancestors_to_call( klass_node: nodes.ClassDef, method="__init__" ) -> Dict[nodes.ClassDef, bases.UnboundMethod]: - """return a dictionary where keys are the list of base classes providing + """Return a dictionary where keys are the list of base classes providing the queried method, and so that should/may be called from the method node """ to_call: Dict[nodes.ClassDef, bases.UnboundMethod] = {} diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py index f268230072..7eb61440df 100644 --- a/pylint/checkers/classes/special_methods_checker.py +++ b/pylint/checkers/classes/special_methods_checker.py @@ -1,7 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Special methods checker and helper function's module""" +"""Special methods checker and helper function's module.""" import astroid from astroid import nodes diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index ac1cd6425d..dc0713a89e 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -70,7 +70,7 @@ def visit_call(self, node: nodes.Call) -> None: "deprecated-class", ) def visit_import(self, node: nodes.Import) -> None: - """triggered when an import statement is seen""" + """Triggered when an import statement is seen.""" for name in (name for name, _ in node.names): self.check_deprecated_module(node, name) if "." in name: @@ -89,7 +89,7 @@ def deprecated_decorators(self) -> Iterable: @utils.check_messages("deprecated-decorator") def visit_decorators(self, node: nodes.Decorators) -> None: - """Triggered when a decorator statement is seen""" + """Triggered when a decorator statement is seen.""" children = list(node.get_children()) if not children: return @@ -106,7 +106,7 @@ def visit_decorators(self, node: nodes.Decorators) -> None: "deprecated-class", ) def visit_importfrom(self, node: nodes.ImportFrom) -> None: - """triggered when a from statement is seen""" + """Triggered when a from statement is seen.""" basename = node.modname basename = get_import_name(node, basename) self.check_deprecated_module(node, basename) @@ -174,7 +174,7 @@ def deprecated_classes(self, module: str) -> Iterable: return () def check_deprecated_module(self, node, mod_path): - """Checks if the module is deprecated""" + """Checks if the module is deprecated.""" for mod_name in self.deprecated_modules(): if mod_path == mod_name or mod_path.startswith(mod_name + "."): self.add_message("deprecated-module", node=node, args=mod_path) @@ -225,7 +225,7 @@ def check_deprecated_method(self, node, inferred): ) def check_deprecated_class(self, node, mod_name, class_names): - """Checks if the class is deprecated""" + """Checks if the class is deprecated.""" for class_name in class_names: if class_name in self.deprecated_classes(mod_name): @@ -234,7 +234,7 @@ def check_deprecated_class(self, node, mod_name, class_names): ) def check_deprecated_class_in_call(self, node): - """Checks if call the deprecated class""" + """Checks if call the deprecated class.""" if isinstance(node.func, nodes.Attribute) and isinstance( node.func.expr, nodes.Name diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index ec518e857c..94569d4ea8 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -28,7 +28,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""check for signs of poor design""" +"""Check for signs of poor design.""" import re from collections import defaultdict @@ -193,7 +193,7 @@ def _is_exempt_from_public_methods(node: astroid.ClassDef) -> bool: - """Check if a class is exempt from too-few-public-methods""" + """Check if a class is exempt from too-few-public-methods.""" # If it's a typing.Namedtuple, typing.TypedDict or an Enum for ancestor in node.ancestors(): @@ -225,7 +225,7 @@ def _is_exempt_from_public_methods(node: astroid.ClassDef) -> bool: def _count_boolean_expressions(bool_op): - """Counts the number of boolean expressions in BoolOp `bool_op` (recursive) + """Counts the number of boolean expressions in BoolOp `bool_op` (recursive). example: a and (b or c or (d and e)) ==> 5 boolean expressions """ @@ -291,7 +291,7 @@ def _get_parents( class MisdesignChecker(BaseChecker): - """checks for sign of poor/misdesign: + """Checks for sign of poor/misdesign: * number of methods, attributes, local variables... * size, complexity of functions, methods """ @@ -428,7 +428,7 @@ def __init__(self, linter=None): self._stmts = None def open(self): - """initialize visit variables""" + """Initialize visit variables.""" self.linter.stats.reset_node_count() self._returns = [] self._branches = defaultdict(int) @@ -452,7 +452,7 @@ def _ignored_argument_names(self): "too-many-public-methods", ) def visit_classdef(self, node: nodes.ClassDef) -> None: - """check size of inheritance hierarchy and number of instance attributes""" + """Check size of inheritance hierarchy and number of instance attributes.""" parents = _get_parents( node, STDLIB_CLASSES_IGNORE_ANCESTOR.union(self.config.ignored_parents) ) @@ -473,7 +473,7 @@ def visit_classdef(self, node: nodes.ClassDef) -> None: @check_messages("too-few-public-methods", "too-many-public-methods") def leave_classdef(self, node: nodes.ClassDef) -> None: - """check number of public methods""" + """Check number of public methods.""" my_methods = sum( 1 for method in node.mymethods() if not method.name.startswith("_") ) @@ -526,7 +526,7 @@ def leave_classdef(self, node: nodes.ClassDef) -> None: "keyword-arg-before-vararg", ) def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """check function name, docstring, arguments, redefinition, + """Check function name, docstring, arguments, redefinition, variable names, max locals """ # init branch and returns counters @@ -569,7 +569,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: "too-many-statements", ) def leave_functiondef(self, node: nodes.FunctionDef) -> None: - """most of the work is done here on close: + """Most of the work is done here on close: checks for max returns, branch, return in __init__ """ returns = self._returns.pop() @@ -598,20 +598,20 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None: leave_asyncfunctiondef = leave_functiondef def visit_return(self, _: nodes.Return) -> None: - """count number of returns""" + """Count number of returns.""" if not self._returns: return # return outside function, reported by the base checker self._returns[-1] += 1 def visit_default(self, node: nodes.NodeNG) -> None: - """default visit method -> increments the statements counter if + """Default visit method -> increments the statements counter if necessary """ if node.is_statement: self._inc_all_stmts(1) def visit_tryexcept(self, node: nodes.TryExcept) -> None: - """increments the branches counter""" + """Increments the branches counter.""" branches = len(node.handlers) if node.orelse: branches += 1 @@ -619,13 +619,13 @@ def visit_tryexcept(self, node: nodes.TryExcept) -> None: self._inc_all_stmts(branches) def visit_tryfinally(self, node: nodes.TryFinally) -> None: - """increments the branches counter""" + """Increments the branches counter.""" self._inc_branch(node, 2) self._inc_all_stmts(2) @check_messages("too-many-boolean-expressions") def visit_if(self, node: nodes.If) -> None: - """increments the branches counter and checks boolean expressions""" + """Increments the branches counter and checks boolean expressions.""" self._check_boolean_expressions(node) branches = 1 # don't double count If nodes coming from some 'elif' @@ -652,7 +652,7 @@ def _check_boolean_expressions(self, node): ) def visit_while(self, node: nodes.While) -> None: - """increments the branches counter""" + """Increments the branches counter.""" branches = 1 if node.orelse: branches += 1 @@ -661,7 +661,7 @@ def visit_while(self, node: nodes.While) -> None: visit_for = visit_while def _inc_branch(self, node, branchesnum=1): - """increments the branches counter""" + """Increments the branches counter.""" self._branches[node.scope()] += branchesnum diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py index af8715ed55..f29ebed042 100644 --- a/pylint/checkers/ellipsis_checker.py +++ b/pylint/checkers/ellipsis_checker.py @@ -1,4 +1,4 @@ -"""Ellipsis checker for Python code""" +"""Ellipsis checker for Python code.""" from typing import TYPE_CHECKING from astroid import nodes diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index eafbafa2b9..9960b8d32e 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -75,7 +75,7 @@ def _annotated_unpack_infer(stmt, context=None): def _is_raising(body: List) -> bool: - """Return whether the given statement node raises an exception""" + """Return whether the given statement node raises an exception.""" return any(isinstance(node, nodes.Raise) for node in body) @@ -506,7 +506,7 @@ def visit_compare(self, node: nodes.Compare) -> None: "duplicate-except", ) def visit_tryexcept(self, node: nodes.TryExcept) -> None: - """check for empty except""" + """Check for empty except.""" self._check_try_except_raise(node) exceptions_classes: List[Any] = [] nb_handlers = len(node.handlers) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 4f5f01e55b..fe505a66d5 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -237,7 +237,7 @@ def line(self, idx): class FormatChecker(BaseTokenChecker): - """checks for : + """Checks for : * unauthorized constructions * strict indentation * line length @@ -348,7 +348,7 @@ def __init__(self, linter=None): self._bracket_stack = [None] def new_line(self, tokens, line_end, line_start): - """a new line has been encountered, process it if necessary""" + """A new line has been encountered, process it if necessary.""" if _last_token_on_line_is(tokens, line_end, ";"): self.add_message("unnecessary-semicolon", line=tokens.start_line(line_end)) @@ -471,7 +471,7 @@ def _prepare_token_dispatcher(self): return dispatch def process_tokens(self, tokens): - """process tokens and search for : + """Process tokens and search for :. _ too long lines (i.e. longer than ) _ optionally bad construct (if given, bad_construct must be a compiled @@ -590,7 +590,7 @@ def _check_line_ending(self, line_ending, line_num): @check_messages("multiple-statements") def visit_default(self, node: nodes.NodeNG) -> None: - """check the node line number and check it if not yet done""" + """Check the node line number and check it if not yet done.""" if not node.is_statement: return if not node.root().pure_python: @@ -681,7 +681,7 @@ def check_line_ending(self, line: str, i: int) -> None: ) def check_line_length(self, line: str, i: int, checker_off: bool) -> None: - """Check that the line length is less than the authorized value""" + """Check that the line length is less than the authorized value.""" max_chars = self.config.max_line_length ignore_long_line = self.config.ignore_long_lines line = line.rstrip() @@ -693,7 +693,7 @@ def check_line_length(self, line: str, i: int, checker_off: bool) -> None: @staticmethod def remove_pylint_option_from_lines(options_pattern_obj) -> str: - """Remove the `# pylint ...` pattern from lines""" + """Remove the `# pylint ...` pattern from lines.""" lines = options_pattern_obj.string purged_lines = ( lines[: options_pattern_obj.start(1)].rstrip() @@ -703,7 +703,7 @@ def remove_pylint_option_from_lines(options_pattern_obj) -> str: @staticmethod def is_line_length_check_activated(pylint_pattern_match_object) -> bool: - """Return true if the line length check is activated""" + """Return true if the line length check is activated.""" try: for pragma in parse_pragma(pylint_pattern_match_object.group(2)): if pragma.action == "disable" and "line-too-long" in pragma.messages: @@ -715,7 +715,7 @@ def is_line_length_check_activated(pylint_pattern_match_object) -> bool: @staticmethod def specific_splitlines(lines: str) -> List[str]: - """Split lines according to universal newlines except those in a specific sets""" + """Split lines according to universal newlines except those in a specific sets.""" unsplit_ends = { "\v", "\x0b", @@ -788,7 +788,7 @@ def check_lines(self, lines: str, lineno: int) -> None: self.check_line_length(line, lineno + offset, checker_off) def check_indent_level(self, string, expected, line_num): - """return the indent level of the string""" + """Return the indent level of the string.""" indent = self.config.indent_string if indent == "\\t": # \t is not interpreted in the configuration file indent = "\t" diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 3f639eef0c..bd368a2c59 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -45,7 +45,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""imports checkers for Python code""" +"""Imports checkers for Python code.""" import collections import copy @@ -77,7 +77,7 @@ def _qualified_names(modname): - """Split the names of the given module into subparts + """Split the names of the given module into subparts. For example, _qualified_names('pylint.checkers.ImportsChecker') @@ -89,7 +89,7 @@ def _qualified_names(modname): def _get_first_import(node, context, name, base, level, alias): - """return the node where [base.] is imported or None if not found""" + """Return the node where [base.] is imported or None if not found.""" fullname = f"{base}.{name}" if base else name first = None @@ -140,7 +140,7 @@ def _ignore_import_failure(node, modname, ignored_modules): def _make_tree_defs(mod_files_list): - """get a list of 2-uple (module, list_of_files_which_import_this_module), + """Get a list of 2-uple (module, list_of_files_which_import_this_module), it will return a dictionary to represent this as a tree """ tree_defs = {} @@ -153,7 +153,7 @@ def _make_tree_defs(mod_files_list): def _repr_tree_defs(data, indent_str=None): - """return a string which represents imports as a tree""" + """Return a string which represents imports as a tree.""" lines = [] nodes_items = data.items() for i, (mod, (sub, files)) in enumerate(sorted(nodes_items, key=lambda x: x[0])): @@ -173,7 +173,7 @@ def _repr_tree_defs(data, indent_str=None): def _dependencies_graph(filename: str, dep_info: Dict[str, Set[str]]) -> str: - """write dependencies as a dot (graphviz) file""" + """Write dependencies as a dot (graphviz) file.""" done = {} printer = DotBackend(os.path.splitext(os.path.basename(filename))[0], rankdir="LR") printer.emit('URL="." node[shape="box"]') @@ -194,7 +194,7 @@ def _dependencies_graph(filename: str, dep_info: Dict[str, Set[str]]) -> str: def _make_graph( filename: str, dep_info: Dict[str, Set[str]], sect: Section, gtype: str ): - """generate a dependencies graph and add some information about it in the + """Generate a dependencies graph and add some information about it in the report's section """ outputfile = _dependencies_graph(filename, dep_info) @@ -302,7 +302,7 @@ def _make_graph( class ImportsChecker(DeprecatedMixin, BaseChecker): - """checks for + """Checks for * external modules dependencies * relative / wildcard imports * cyclic imports @@ -469,7 +469,7 @@ def _normalized_path(path): return paths def open(self): - """called before visiting project (i.e set of modules)""" + """Called before visiting project (i.e set of modules).""" self.linter.stats.dependencies = {} self.linter.stats = self.linter.stats self.import_graph = collections.defaultdict(set) @@ -491,7 +491,7 @@ def _import_graph_without_ignored_edges(self): return filtered_graph def close(self): - """called before visiting project (i.e set of modules)""" + """Called before visiting project (i.e set of modules).""" if self.linter.is_message_enabled("cyclic-import"): graph = self._import_graph_without_ignored_edges() vertices = list(graph) @@ -504,7 +504,7 @@ def deprecated_modules(self): @check_messages(*MSGS) def visit_import(self, node: nodes.Import) -> None: - """triggered when an import statement is seen""" + """Triggered when an import statement is seen.""" self._check_reimport(node) self._check_import_as_rename(node) self._check_toplevel(node) @@ -530,7 +530,7 @@ def visit_import(self, node: nodes.Import) -> None: @check_messages(*MSGS) def visit_importfrom(self, node: nodes.ImportFrom) -> None: - """triggered when a from statement is seen""" + """Triggered when a from statement is seen.""" basename = node.modname imported_module = self._get_imported_module(node, basename) @@ -673,7 +673,7 @@ def _check_same_line_imports(self, node): self.add_message("reimported", node=node, args=(name, node.fromlineno)) def _check_position(self, node): - """Check `node` import or importfrom node position is correct + """Check `node` import or importfrom node position is correct. Send a message if `node` comes before another instruction """ @@ -683,7 +683,7 @@ def _check_position(self, node): self.add_message("wrong-import-position", node=node, args=node.as_string()) def _record_import(self, node, importedmodnode): - """Record the package `node` imports from""" + """Record the package `node` imports from.""" if isinstance(node, nodes.ImportFrom): importedname = node.modname else: @@ -709,7 +709,7 @@ def _is_fallback_import(node, imports): return any(astroid.are_exclusive(import_node, node) for import_node in imports) def _check_imports_order(self, _module_node): - """Checks imports of module `node` are grouped by category + """Checks imports of module `node` are grouped by category. Imports must follow this order: standard, 3rd party, local """ @@ -831,7 +831,7 @@ def _get_imported_module(self, importnode, modname): def _add_imported_module( self, node: Union[nodes.Import, nodes.ImportFrom], importedmodname: str ) -> None: - """notify an imported module, used to analyze dependencies""" + """Notify an imported module, used to analyze dependencies.""" module_file = node.root().file context_name = node.root().name base = os.path.splitext(os.path.basename(module_file))[0] @@ -872,7 +872,7 @@ def _add_imported_module( self._excluded_edges[context_name].add(importedmodname) def _check_preferred_module(self, node, mod_path): - """check if the module has a preferred replacement""" + """Check if the module has a preferred replacement.""" if mod_path in self.preferred_modules: self.add_message( "preferred-module", @@ -904,7 +904,7 @@ def _check_import_as_rename( ) def _check_reimport(self, node, basename=None, level=None): - """check if the import is necessary (i.e. not already done)""" + """Check if the import is necessary (i.e. not already done).""" if not self.linter.is_message_enabled("reimported"): return @@ -925,7 +925,7 @@ def _check_reimport(self, node, basename=None, level=None): ) def _report_external_dependencies(self, sect, _, _dummy): - """return a verbatim layout for displaying dependencies""" + """Return a verbatim layout for displaying dependencies.""" dep_info = _make_tree_defs(self._external_dependencies_info().items()) if not dep_info: raise EmptyReportError() @@ -933,7 +933,7 @@ def _report_external_dependencies(self, sect, _, _dummy): sect.append(VerbatimText(tree_str)) def _report_dependencies_graph(self, sect, _, _dummy): - """write dependencies as a dot (graphviz) file""" + """Write dependencies as a dot (graphviz) file.""" dep_info = self.linter.stats.dependencies if not dep_info or not ( self.config.import_graph @@ -952,7 +952,7 @@ def _report_dependencies_graph(self, sect, _, _dummy): _make_graph(filename, self._internal_dependencies_info(), sect, "internal ") def _filter_dependencies_graph(self, internal): - """build the internal or the external dependency graph""" + """Build the internal or the external dependency graph.""" graph = collections.defaultdict(set) for importee, importers in self.linter.stats.dependencies.items(): for importer in importers: @@ -964,14 +964,14 @@ def _filter_dependencies_graph(self, internal): @astroid.decorators.cached def _external_dependencies_info(self): - """return cached external dependencies information or build and + """Return cached external dependencies information or build and cache them """ return self._filter_dependencies_graph(internal=False) @astroid.decorators.cached def _internal_dependencies_info(self): - """return cached internal dependencies information or build and + """Return cached internal dependencies information or build and cache them """ return self._filter_dependencies_graph(internal=True) diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index 4c44017419..aedaf62415 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -23,7 +23,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""checker for use of Python logging""" +"""Checker for use of Python logging.""" import string from typing import TYPE_CHECKING, Set @@ -294,7 +294,7 @@ def _helper_string(self, node): @staticmethod def _is_operand_literal_str(operand): - """Return True if the operand in argument is a literal string""" + """Return True if the operand in argument is a literal string.""" return isinstance(operand, nodes.Const) and operand.name == "str" def _check_call_func(self, node: nodes.Call): diff --git a/pylint/checkers/mapreduce_checker.py b/pylint/checkers/mapreduce_checker.py index b1722fc9bb..a4a2c605c4 100644 --- a/pylint/checkers/mapreduce_checker.py +++ b/pylint/checkers/mapreduce_checker.py @@ -8,12 +8,12 @@ class MapReduceMixin(metaclass=abc.ABCMeta): - """A mixin design to allow multiprocess/threaded runs of a Checker""" + """A mixin design to allow multiprocess/threaded runs of a Checker.""" @abc.abstractmethod def get_map_data(self): - """Returns mergeable/reducible data that will be examined""" + """Returns mergeable/reducible data that will be examined.""" @abc.abstractmethod def reduce_map_data(self, linter, data): - """For a given Checker, receives data for all mapped runs""" + """For a given Checker, receives data for all mapped runs.""" diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index 5c3a9ab64f..69149e61a9 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -25,7 +25,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Check source code is ascii only or has an encoding declaration (PEP 263)""" +"""Check source code is ascii only or has an encoding declaration (PEP 263).""" import re import tokenize @@ -76,7 +76,7 @@ def process_module(self, node: nodes.Module) -> None: class EncodingChecker(BaseChecker): - """checks for: + """Checks for: * warning notes in the code like FIXME, XXX * encoding issues. """ @@ -145,7 +145,7 @@ def _check_encoding( return None def process_module(self, node: nodes.Module) -> None: - """inspect the source file to find encoding problem""" + """Inspect the source file to find encoding problem.""" encoding = node.file_encoding if node.file_encoding else "ascii" with node.stream() as stream: @@ -153,7 +153,7 @@ def process_module(self, node: nodes.Module) -> None: self._check_encoding(lineno + 1, line, encoding) def process_tokens(self, tokens): - """inspect the source to find fixme problems""" + """Inspect the source to find fixme problems.""" if not self.config.notes: return comments = ( diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index 1435509099..00fa0748e3 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -20,7 +20,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""check for new / old style related problems""" +"""Check for new / old style related problems.""" from typing import TYPE_CHECKING import astroid @@ -44,7 +44,7 @@ class NewStyleConflictChecker(BaseChecker): - """checks for usage of new style capabilities on old style classes and + """Checks for usage of new style capabilities on old style classes and other new/old styles conflicts problems * use of property, __slots__, super * "super" usage @@ -62,7 +62,7 @@ class NewStyleConflictChecker(BaseChecker): @check_messages("bad-super-call") def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """check use of super""" + """Check use of super.""" # ignore actual functions or method within a new style class if not node.is_method(): return diff --git a/pylint/checkers/non_ascii_names.py b/pylint/checkers/non_ascii_names.py index 1ff2b67e80..d9b3bfd505 100644 --- a/pylint/checkers/non_ascii_names.py +++ b/pylint/checkers/non_ascii_names.py @@ -41,7 +41,7 @@ def isascii(self: str) -> bool: class NonAsciiNameChecker(base_checker.BaseChecker): - """A strict name checker only allowing ASCII + """A strict name checker only allowing ASCII. Note: This check only checks Names, so it ignores the content of docstrings and comments! @@ -147,7 +147,7 @@ def visit_global(self, node: nodes.Global) -> None: @utils.check_messages("non-ascii-name") def visit_assignname(self, node: nodes.AssignName) -> None: - """check module level assigned names""" + """Check module level assigned names.""" # The NameChecker from which this Checker originates knows a lot of different # versions of variables, i.e. constants, inline variables etc. # To simplify we use only `variable` here, as we don't need to apply different diff --git a/pylint/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index 379665eace..84e6c23c47 100644 --- a/pylint/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py @@ -39,7 +39,7 @@ def report_raw_stats( stats: LinterStats, old_stats: Optional[LinterStats], ) -> None: - """calculate percentage of code / doc / comment / empty""" + """Calculate percentage of code / doc / comment / empty.""" total_lines = stats.code_type_count["total"] sect.description = f"{total_lines} lines have been analyzed" lines = ["type", "number", "%", "previous", "difference"] @@ -60,7 +60,7 @@ def report_raw_stats( class RawMetricsChecker(BaseTokenChecker): - """does not check anything but gives some raw metrics : + """Does not check anything but gives some raw metrics : * total number of lines * total number of code lines * total number of docstring lines @@ -83,11 +83,11 @@ def __init__(self, linter): super().__init__(linter) def open(self): - """init statistics""" + """Init statistics.""" self.linter.stats.reset_code_count() def process_tokens(self, tokens): - """update stats""" + """Update stats.""" i = 0 tokens = list(tokens) while i < len(tokens): @@ -100,7 +100,7 @@ def process_tokens(self, tokens): def get_type(tokens, start_index): - """return the line type : docstring, comment, code, empty""" + """Return the line type : docstring, comment, code, empty.""" i = start_index start = tokens[i][2] pos = start diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index c4b25eeb7c..b1fa672788 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -145,7 +145,7 @@ def visit_compare(self, node: nodes.Compare) -> None: def _check_use_implicit_booleaness_not_comparison( self, node: nodes.Compare ) -> None: - """Check for left side and right side of the node for empty literals""" + """Check for left side and right side of the node for empty literals.""" is_left_empty_literal = utils.is_base_container( node.left ) or utils.is_empty_dict_literal(node.left) diff --git a/pylint/checkers/refactoring/not_checker.py b/pylint/checkers/refactoring/not_checker.py index ec29dfb8e0..d1710ea222 100644 --- a/pylint/checkers/refactoring/not_checker.py +++ b/pylint/checkers/refactoring/not_checker.py @@ -10,7 +10,7 @@ class NotChecker(checkers.BaseChecker): - """checks for too many not in comparison expressions + """Checks for too many not in comparison expressions. - "not not" should trigger a warning - "not" followed by a comparison should trigger a warning diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 25313494bb..1bf75e77e3 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -54,7 +54,7 @@ def _if_statement_is_always_returning(if_node, returning_node_class) -> bool: def _is_trailing_comma(tokens: List[tokenize.TokenInfo], index: int) -> bool: - """Check if the given token is a trailing comma + """Check if the given token is a trailing comma. :param tokens: Sequence of modules tokens :type tokens: list[tokenize.TokenInfo] @@ -81,7 +81,7 @@ def _is_trailing_comma(tokens: List[tokenize.TokenInfo], index: int) -> bool: return False def get_curline_index_start(): - """Get the index denoting the start of the current line""" + """Get the index denoting the start of the current line.""" for subindex, token in enumerate(reversed(tokens[:index])): # See Lib/tokenize.py and Lib/token.py in cpython for more info if token.type == tokenize.NEWLINE: @@ -173,13 +173,13 @@ def get_stack_for_frame( return self.module_scope def clear_all(self) -> None: - """Convenience method to clear all stacks""" + """Convenience method to clear all stacks.""" for stack in self: stack.clear() class RefactoringChecker(checkers.BaseTokenChecker): - """Looks for code which can be refactored + """Looks for code which can be refactored. This checker also mixes the astroid and the token approaches in order to create knowledge about whether an "else if" node @@ -486,7 +486,7 @@ def _is_bool_const(node): ) def _is_actual_elif(self, node): - """Check if the given node is an actual elif + """Check if the given node is an actual elif. This is a problem we're having with the builtin ast module, which splits `elif` branches into a separate if statement. @@ -910,7 +910,7 @@ def visit_raise(self, node: nodes.Raise) -> None: self._check_stop_iteration_inside_generator(node) def _check_stop_iteration_inside_generator(self, node): - """Check if an exception of type StopIteration is raised inside a generator""" + """Check if an exception of type StopIteration is raised inside a generator.""" frame = node.frame(future=True) if not isinstance(frame, nodes.FunctionDef) or not frame.is_generator(): return @@ -926,7 +926,7 @@ def _check_stop_iteration_inside_generator(self, node): @staticmethod def _check_exception_inherit_from_stopiteration(exc): - """Return True if the exception node in argument inherit from StopIteration""" + """Return True if the exception node in argument inherit from StopIteration.""" stopiteration_qname = f"{utils.EXCEPTIONS_MODULE}.StopIteration" return any(_class.qname() == stopiteration_qname for _class in exc.mro()) @@ -1050,7 +1050,7 @@ def _check_super_with_arguments(self, node): self.add_message("super-with-arguments", node=node) def _check_raising_stopiteration_in_generator_next_call(self, node): - """Check if a StopIteration exception is raised by the call to next function + """Check if a StopIteration exception is raised by the call to next function. If the next value has a default value, then do not add message. @@ -1084,7 +1084,7 @@ def _looks_like_infinite_iterator(param): self.add_message("stop-iteration-return", node=node) def _check_nested_blocks(self, node): - """Update and check the number of nested blocks""" + """Update and check the number of nested blocks.""" # only check block levels inside functions or methods if not isinstance(node.scope(), nodes.FunctionDef): return @@ -1273,7 +1273,7 @@ def _find_lower_upper_bounds(comparison_node, uses): @staticmethod def _apply_boolean_simplification_rules(operator, values): - """Removes irrelevant values or returns shortcircuiting values + """Removes irrelevant values or returns shortcircuiting values. This function applies the following two rules: 1) an OR expression with True in it will always be true, and the @@ -1299,7 +1299,7 @@ def _apply_boolean_simplification_rules(operator, values): return simplified_values or [nodes.Const(operator == "and")] def _simplify_boolean_operation(self, bool_op): - """Attempts to simplify a boolean operation + """Attempts to simplify a boolean operation. Recursively applies simplification on the operator terms, and keeps track of whether reductions have been made. @@ -1503,7 +1503,7 @@ def _check_consider_using_with(self, node: nodes.Call): self.add_message("consider-using-with", node=node) def _check_use_list_or_dict_literal(self, node: nodes.Call) -> None: - """Check if empty list or dict is created by using the literal [] or {}""" + """Check if empty list or dict is created by using the literal [] or {}.""" if node.as_string() in {"list()", "dict()"}: inferred = utils.safe_infer(node.func) if isinstance(inferred, nodes.ClassDef) and not node.args: @@ -1798,7 +1798,7 @@ def _is_node_return_ended(self, node: nodes.NodeNG) -> bool: @staticmethod def _has_return_in_siblings(node: nodes.NodeNG) -> bool: - """Returns True if there is at least one return in the node's siblings""" + """Returns True if there is at least one return in the node's siblings.""" next_sibling = node.next_sibling() while next_sibling: if isinstance(next_sibling, nodes.Return): diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index ed85c34b37..113b086bc7 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -27,7 +27,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""a similarities / code duplication command line tool and pylint checker +"""A similarities / code duplication command line tool and pylint checker. The algorithm is based on comparing the hash value of n successive lines of a file. First the files are read and any line that doesn't fulfill requirement are removed (comments, docstrings...) @@ -139,13 +139,13 @@ class LinesChunk: def __init__(self, fileid: str, num_line: int, *lines: Iterable[str]) -> None: self._fileid: str = fileid - """The name of the file from which the LinesChunk object is generated """ + """The name of the file from which the LinesChunk object is generated.""" self._index: Index = Index(num_line) - """The index in the stripped lines that is the starting of consecutive lines""" + """The index in the stripped lines that is the starting of consecutive lines.""" self._hash: int = sum(hash(lin) for lin in lines) - """The hash of some consecutive lines""" + """The hash of some consecutive lines.""" def __eq__(self, o: Any) -> bool: if not isinstance(o, LinesChunk): @@ -196,7 +196,7 @@ def __repr__(self) -> str: class LineSetStartCouple(NamedTuple): - """Indices in both linesets that mark the beginning of successive lines""" + """Indices in both linesets that mark the beginning of successive lines.""" fst_lineset_index: Index snd_lineset_index: Index @@ -269,7 +269,7 @@ def hash_lineset( def remove_successives(all_couples: CplIndexToCplLines_T) -> None: - """Removes all successive entries in the dictionary in argument + """Removes all successive entries in the dictionary in argument. :param all_couples: collection that has to be cleaned up from successives entries. The keys are couples of indices that mark the beginning of common entries @@ -353,7 +353,7 @@ class Commonality(NamedTuple): class Similar: - """finds copy-pasted lines of code in a project""" + """Finds copy-pasted lines of code in a project.""" def __init__( self, @@ -373,7 +373,7 @@ def __init__( def append_stream( self, streamid: str, stream: STREAM_TYPES, encoding: Optional[str] = None ) -> None: - """append a file to search for similarities""" + """Append a file to search for similarities.""" if isinstance(stream, BufferedIOBase): if encoding is None: raise ValueError @@ -395,13 +395,13 @@ def append_stream( pass def run(self) -> None: - """start looking for similarities and display results on stdout""" + """Start looking for similarities and display results on stdout.""" if self.min_lines == 0: return self._display_sims(self._compute_sims()) def _compute_sims(self) -> List[Tuple[int, Set[LinesChunkLimits_T]]]: - """compute similarities in appended files""" + """Compute similarities in appended files.""" no_duplicates: Dict[int, List[Set[LinesChunkLimits_T]]] = defaultdict(list) for commonality in self._iter_sims(): @@ -442,14 +442,14 @@ def _compute_sims(self) -> List[Tuple[int, Set[LinesChunkLimits_T]]]: def _display_sims( self, similarities: List[Tuple[int, Set[LinesChunkLimits_T]]] ) -> None: - """Display computed similarities on stdout""" + """Display computed similarities on stdout.""" report = self._get_similarity_report(similarities) print(report) def _get_similarity_report( self, similarities: List[Tuple[int, Set[LinesChunkLimits_T]]] ) -> str: - """Create a report from similarities""" + """Create a report from similarities.""" report: str = "" duplicated_line_number: int = 0 for number, couples in similarities: @@ -535,7 +535,7 @@ def _find_common( yield com def _iter_sims(self) -> Generator[Commonality, None, None]: - """iterate on similarities among all files, by making a cartesian + """Iterate on similarities among all files, by making a cartesian product """ for idx, lineset in enumerate(self.linesets[:-1]): @@ -543,7 +543,7 @@ def _iter_sims(self) -> Generator[Commonality, None, None]: yield from self._find_common(lineset, lineset2) def get_map_data(self): - """Returns the data we can use for a map/reduce process + """Returns the data we can use for a map/reduce process. In this case we are returning this instance's Linesets, that is all file information that will later be used for vectorisation. @@ -551,7 +551,7 @@ def get_map_data(self): return self.linesets def combine_mapreduce_data(self, linesets_collection): - """Reduces and recombines data into a format that we can report on + """Reduces and recombines data into a format that we can report on. The partner function of get_map_data() """ @@ -565,7 +565,7 @@ def stripped_lines( ignore_imports: bool, ignore_signatures: bool, ) -> List[LineSpecifs]: - """Return tuples of line/line number/line type with leading/trailing whitespace and any ignored code features removed + """Return tuples of line/line number/line type with leading/trailing whitespace and any ignored code features removed. :param lines: a collection of lines :param ignore_comments: if true, any comment in the lines collection is removed from the result @@ -720,7 +720,7 @@ def report_similarities( stats: LinterStats, old_stats: Optional[LinterStats], ) -> None: - """make a layout with some stats about duplication""" + """Make a layout with some stats about duplication.""" lines = ["", "now", "previous", "difference"] lines += table_lines_from_stats(stats, old_stats, "duplicated_lines") sect.append(Table(children=lines, cols=4, rheaders=1, cheaders=1)) @@ -728,7 +728,7 @@ def report_similarities( # wrapper to get a pylint checker from the similar class class SimilarChecker(BaseChecker, Similar, MapReduceMixin): - """checks for similarities and duplicated code. This computation may be + """Checks for similarities and duplicated code. This computation may be memory / CPU intensive, so you should disable it if you experiment some problems. """ @@ -802,7 +802,7 @@ def __init__(self, linter=None) -> None: ) def set_option(self, optname, value, action=None, optdict=None): - """method called to set an option (registered in the options list) + """Method called to set an option (registered in the options list). Overridden to report options setting to Similar """ @@ -819,12 +819,12 @@ def set_option(self, optname, value, action=None, optdict=None): self.ignore_signatures = self.config.ignore_signatures def open(self): - """init the checkers: reset linesets and statistics information""" + """Init the checkers: reset linesets and statistics information.""" self.linesets = [] self.linter.stats.reset_duplicated_lines() def process_module(self, node: nodes.Module) -> None: - """process a module + """Process a module. the module's content is accessible via the stream object @@ -842,7 +842,7 @@ def process_module(self, node: nodes.Module) -> None: self.append_stream(self.linter.current_name, stream, node.file_encoding) # type: ignore[arg-type] def close(self): - """compute and display similarities on closing (i.e. end of parsing)""" + """Compute and display similarities on closing (i.e. end of parsing).""" total = sum(len(lineset) for lineset in self.linesets) duplicated = 0 stats = self.linter.stats @@ -863,11 +863,11 @@ def close(self): stats.percent_duplicated_lines += float(total and duplicated * 100.0 / total) def get_map_data(self): - """Passthru override""" + """Passthru override.""" return Similar.get_map_data(self) def reduce_map_data(self, linter, data): - """Reduces and recombines data into a format that we can report on + """Reduces and recombines data into a format that we can report on. The partner function of get_map_data() """ @@ -887,7 +887,7 @@ def register(linter: "PyLinter") -> None: def usage(status=0): - """display command line usage information""" + """Display command line usage information.""" print("finds copy pasted blocks in a set of files") print() print( @@ -898,7 +898,7 @@ def usage(status=0): def Run(argv=None): - """standalone command line access point""" + """Standalone command line access point.""" if argv is None: argv = sys.argv[1:] diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index 777153c64d..8c004444e6 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -146,7 +146,7 @@ class SphinxDirectives(RegExFilter): class ForwardSlashChunker(Chunker): - """This chunker allows splitting words like 'before/after' into 'before' and 'after'""" + """This chunker allows splitting words like 'before/after' into 'before' and 'after'.""" def next(self): while True: @@ -203,7 +203,7 @@ def replace_code_but_leave_surrounding_characters(match_obj) -> str: class SpellingChecker(BaseTokenChecker): - """Check spelling in comments and docstrings""" + """Check spelling in comments and docstrings.""" __implements__ = (ITokenChecker, IAstroidChecker) name = "spelling" @@ -459,7 +459,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: visit_asyncfunctiondef = visit_functiondef def _check_docstring(self, node): - """check the node has any spelling errors""" + """Check the node has any spelling errors.""" docstring = node.doc if not docstring: return diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index e512e457d4..e89e8db5f3 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -635,7 +635,7 @@ def _check_new_format_specifiers(self, node, fields, named): class StringConstantChecker(BaseTokenChecker): - """Check string literals""" + """Check string literals.""" __implements__ = (IAstroidChecker, ITokenChecker, IRawChecker) name = "string" @@ -850,7 +850,7 @@ def process_string_token(self, token, start_row, start_col): def process_non_raw_string_token( self, prefix, string_body, start_row, string_start_col ): - """check for bad escapes in a non-raw string. + """Check for bad escapes in a non-raw string. prefix: lowercase string of eg 'ur' string prefix markers. string_body: the un-parsed body of the string, not including the quote @@ -917,7 +917,7 @@ def visit_const(self, node: nodes.Const) -> None: self._detect_u_string_prefix(node) def _detect_u_string_prefix(self, node: nodes.Const): - """Check whether strings include a 'u' prefix like u'String'""" + """Check whether strings include a 'u' prefix like u'String'.""" if node.kind == "u": self.add_message( "redundant-u-string-prefix", diff --git a/pylint/checkers/threading_checker.py b/pylint/checkers/threading_checker.py index cedf46a9ee..6148b82923 100644 --- a/pylint/checkers/threading_checker.py +++ b/pylint/checkers/threading_checker.py @@ -14,7 +14,7 @@ class ThreadingChecker(BaseChecker): - """Checks for threading module + """Checks for threading module. - useless with lock - locking used in wrong way that has no effect (with threading.Lock():) """ diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 825c380826..b28815e704 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -55,7 +55,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""try to find more bugs in the code using astroid inference capabilities""" +"""Try to find more bugs in the code using astroid inference capabilities.""" import fnmatch import heapq @@ -149,7 +149,7 @@ def _flatten_container(iterable): def _is_owner_ignored(owner, attrname, ignored_classes, ignored_modules): - """Check if the given owner should be ignored + """Check if the given owner should be ignored. This will verify if the owner's module is in *ignored_modules* or the owner's module fully qualified name is in *ignored_modules* @@ -233,7 +233,7 @@ def _string_distance(seq1, seq2): def _similar_names(owner, attrname, distance_threshold, max_choices): - """Given an owner and a name, try to find similar names + """Given an owner and a name, try to find similar names. The similar names are searched given a distance metric and only a given number of choices will be returned. @@ -664,7 +664,7 @@ def _no_context_variadic_positional(node, scope): def _no_context_variadic(node, variadic_name, variadic_type, variadics): - """Verify if the given call node has variadic nodes without context + """Verify if the given call node has variadic nodes without context. This is a workaround for handling cases of nested call functions which don't have the specific call context at hand. @@ -722,7 +722,7 @@ def _is_invalid_metaclass(metaclass): def _infer_from_metaclass_constructor(cls, func: nodes.FunctionDef): - """Try to infer what the given *func* constructor is building + """Try to infer what the given *func* constructor is building. :param astroid.FunctionDef func: A metaclass constructor. Metaclass definitions can be @@ -781,7 +781,7 @@ def _is_invalid_isinstance_type(arg): class TypeChecker(BaseChecker): - """try to find bugs in the code using type inference""" + """Try to find bugs in the code using type inference.""" __implements__ = (IAstroidChecker,) @@ -1006,7 +1006,7 @@ def visit_delattr(self, node: nodes.DelAttr) -> None: @check_messages("no-member", "c-extension-no-member") def visit_attribute(self, node: nodes.Attribute) -> None: - """check that the accessed attribute exists + """Check that the accessed attribute exists. to avoid too much false positives for now, we'll consider the code as correct if a single of the inferred nodes has the accessed attribute. @@ -1210,7 +1210,7 @@ def _is_list_sort_method(node: nodes.Call) -> bool: ) def _check_dundername_is_string(self, node) -> None: - """Check a string is assigned to self.__name__""" + """Check a string is assigned to self.__name__.""" # Check the left-hand side of the assignment is .__name__ lhs = node.targets[0] @@ -1331,7 +1331,7 @@ def _check_isinstance_args(self, node): # pylint: disable=too-many-branches,too-many-locals @check_messages(*(list(MSGS.keys()))) def visit_call(self, node: nodes.Call) -> None: - """check that called functions/methods are inferred to callable objects, + """Check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in the inferred function's definition """ @@ -1644,7 +1644,7 @@ def _check_invalid_sequence_index(self, subscript: nodes.Subscript): def _check_not_callable( self, node: nodes.Call, inferred_call: Optional[nodes.NodeNG] ) -> None: - """Checks to see if the not-callable message should be emitted + """Checks to see if the not-callable message should be emitted. Only functions, generators and objects defining __call__ are "callable" We ignore instances of descriptors since astroid cannot properly handle them yet diff --git a/pylint/checkers/unicode.py b/pylint/checkers/unicode.py index 88b534daea..93dfacb4b6 100644 --- a/pylint/checkers/unicode.py +++ b/pylint/checkers/unicode.py @@ -52,7 +52,7 @@ class _BadChar(NamedTuple): - """Representation of an ASCII char considered bad""" + """Representation of an ASCII char considered bad.""" name: str unescaped: str @@ -61,14 +61,14 @@ class _BadChar(NamedTuple): help_text: str def description(self) -> str: - """Used for the detailed error message description""" + """Used for the detailed error message description.""" return ( f"Invalid unescaped character {self.name}, " f'use "{self.escaped}" instead.' ) def human_code(self) -> str: - """Used to generate the human readable error message""" + """Used to generate the human readable error message.""" return f"invalid-character-{self.name}" @@ -135,7 +135,7 @@ def human_code(self) -> str: def _line_length(line: _StrLike, codec: str) -> int: - """Get the length of a string like line as displayed in an editor""" + """Get the length of a string like line as displayed in an editor.""" if isinstance(line, bytes): decoded = _remove_bom(line, codec).decode(codec, "replace") else: @@ -155,7 +155,7 @@ def _map_positions_to_result( new_line: _StrLike, byte_str_length: int = 1, ) -> Dict[int, _BadChar]: - """Get all occurrences of search dict keys within line + """Get all occurrences of search dict keys within line. Ignores Windows end of line and can handle bytes as well as string. Also takes care of encodings for which the length of an encoded code point does not @@ -208,12 +208,12 @@ def _map_positions_to_result( def _normalize_codec_name(codec: str) -> str: - """Make sure the codec name is always given as defined in the BOM dict""" + """Make sure the codec name is always given as defined in the BOM dict.""" return UTF_NAME_REGEX_COMPILED.sub(r"utf-\1\2", codec).lower() def _remove_bom(encoded: bytes, encoding: str) -> bytes: - """remove the bom if given from a line""" + """Remove the bom if given from a line.""" if not encoding.startswith("utf"): return encoded bom = UNICODE_BOMS[encoding] @@ -223,12 +223,12 @@ def _remove_bom(encoded: bytes, encoding: str) -> bytes: def _encode_without_bom(string: str, encoding: str) -> bytes: - """Encode a string but remove the BOM""" + """Encode a string but remove the BOM.""" return _remove_bom(string.encode(encoding), encoding) def _byte_to_str_length(codec: str) -> int: - """return how many byte are usually(!) a character point""" + """Return how many byte are usually(!) a character point.""" if codec.startswith("utf-32"): return 4 if codec.startswith("utf-16"): @@ -239,12 +239,12 @@ def _byte_to_str_length(codec: str) -> int: @lru_cache(maxsize=1000) def _cached_encode_search(string: str, encoding: str) -> bytes: - """A cached version of encode used for search pattern""" + """A cached version of encode used for search pattern.""" return _encode_without_bom(string, encoding) def _fix_utf16_32_line_stream(steam: Iterable[bytes], codec: str) -> Iterable[bytes]: - """Handle line ending for UTF16 and UTF32 correctly + """Handle line ending for UTF16 and UTF32 correctly. Currently Python simply strips the required zeros after \n after the line ending. Leading to lines that can't be decoded propery @@ -274,7 +274,7 @@ def _fix_utf16_32_line_stream(steam: Iterable[bytes], codec: str) -> Iterable[by def extract_codec_from_bom(first_line: bytes) -> str: - """Try to extract the codec (unicode only) by checking for the BOM + """Try to extract the codec (unicode only) by checking for the BOM. For details about BOM see https://unicode.org/faq/utf_bom.html#BOM @@ -295,7 +295,7 @@ def extract_codec_from_bom(first_line: bytes) -> str: class UnicodeChecker(pylint.checkers.BaseChecker): - """Check characters that could be used to hide bad code to humans + """Check characters that could be used to hide bad code to humans. This includes: @@ -381,7 +381,7 @@ def _is_unicode(codec: str) -> bool: @classmethod def _find_line_matches(cls, line: bytes, codec: str) -> Dict[int, _BadChar]: - """Find all matches of BAD_CHARS within line + """Find all matches of BAD_CHARS within line. Args: line: the input @@ -417,7 +417,7 @@ def _find_line_matches(cls, line: bytes, codec: str) -> Dict[int, _BadChar]: @staticmethod def _determine_codec(stream: io.BytesIO) -> Tuple[str, int]: - """determine the codec from the given stream + """Determine the codec from the given stream. first tries https://www.python.org/dev/peps/pep-0263/ and if this fails also checks for BOMs of UTF-16 and UTF-32 @@ -457,7 +457,7 @@ def _determine_codec(stream: io.BytesIO) -> Tuple[str, int]: return _normalize_codec_name(codec), codec_definition_line def _check_codec(self, codec: str, codec_definition_line: int) -> None: - """Check validity of the codec""" + """Check validity of the codec.""" if codec != "utf-8": msg = "bad-file-encoding" if self._is_invalid_codec(codec): @@ -474,7 +474,7 @@ def _check_codec(self, codec: str, codec_definition_line: int) -> None: ) def _check_invalid_chars(self, line: bytes, lineno: int, codec: str) -> None: - """Look for chars considered bad""" + """Look for chars considered bad.""" matches = self._find_line_matches(line, codec) for col, char in matches.items(): self.add_message( @@ -489,7 +489,7 @@ def _check_invalid_chars(self, line: bytes, lineno: int, codec: str) -> None: ) def _check_bidi_chars(self, line: bytes, lineno: int, codec: str) -> None: - """Look for Bidirectional Unicode, if we use unicode""" + """Look for Bidirectional Unicode, if we use unicode.""" if not self._is_unicode(codec): return for dangerous in BIDI_UNICODE: diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index ada104ab90..b3369b877f 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -57,13 +57,13 @@ def open(self) -> None: @check_messages("using-f-string-in-unsupported-version") def visit_joinedstr(self, node: nodes.JoinedStr) -> None: - """Check f-strings""" + """Check f-strings.""" if not self._py36_plus: self.add_message("using-f-string-in-unsupported-version", node=node) @check_messages("using-final-decorator-in-unsupported-version") def visit_decorators(self, node: nodes.Decorators) -> None: - """Check decorators""" + """Check decorators.""" self._check_typing_final(node) def _check_typing_final(self, node: nodes.Decorators) -> None: diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index f34faecc8a..026a78b5e7 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -57,7 +57,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""some functions that may be useful for various checkers""" +"""Some functions that may be useful for various checkers.""" import builtins import itertools import numbers @@ -293,7 +293,7 @@ class InferredTypeError(Exception): def is_inside_lambda(node: nodes.NodeNG) -> bool: - """Return whether the given node is inside a lambda""" + """Return whether the given node is inside a lambda.""" warnings.warn( "utils.is_inside_lambda will be removed in favour of calling " "utils.get_node_first_ancestor_of_type(x, nodes.Lambda) in pylint 3.0", @@ -314,14 +314,14 @@ def get_all_elements( def is_super(node: nodes.NodeNG) -> bool: - """return True if the node is referencing the "super" builtin function""" + """Return True if the node is referencing the "super" builtin function.""" if getattr(node, "name", None) == "super" and node.root().name == "builtins": return True return False def is_error(node: nodes.FunctionDef) -> bool: - """Return true if the given function node only raises an exception""" + """Return true if the given function node only raises an exception.""" return len(node.body) == 1 and isinstance(node.body[0], nodes.Raise) @@ -335,7 +335,7 @@ def is_builtin_object(node: nodes.NodeNG) -> bool: def is_builtin(name: str) -> bool: - """return true if could be considered as a builtin defined by python""" + """Return true if could be considered as a builtin defined by python.""" return name in builtins or name in SPECIAL_BUILTINS # type: ignore[attr-defined] @@ -388,7 +388,7 @@ def is_defined_in_scope( def is_defined_before(var_node: nodes.Name) -> bool: - """Check if the given variable node is defined before + """Check if the given variable node is defined before. Verify that the variable node is defined by a parent node (list, set, dict, or generator comprehension, lambda) @@ -417,7 +417,7 @@ def is_defined_before(var_node: nodes.Name) -> bool: def is_default_argument( node: nodes.NodeNG, scope: Optional[nodes.NodeNG] = None ) -> bool: - """return true if the given Name node is used in function or lambda + """Return true if the given Name node is used in function or lambda default argument's value """ if not scope: @@ -436,7 +436,7 @@ def is_default_argument( def is_func_decorator(node: nodes.NodeNG) -> bool: - """return true if the name is used in function decorator""" + """Return true if the name is used in function decorator.""" for parent in node.node_ancestors(): if isinstance(parent, nodes.Decorators): return True @@ -453,7 +453,7 @@ def is_func_decorator(node: nodes.NodeNG) -> bool: def is_ancestor_name(frame: nodes.ClassDef, node: nodes.NodeNG) -> bool: - """return whether `frame` is an astroid.Class node with `node` in the + """Return whether `frame` is an astroid.Class node with `node` in the subtree of its bases attribute """ if not isinstance(frame, nodes.ClassDef): @@ -462,19 +462,19 @@ def is_ancestor_name(frame: nodes.ClassDef, node: nodes.NodeNG) -> bool: def is_being_called(node: nodes.NodeNG) -> bool: - """return True if node is the function being called in a Call node""" + """Return True if node is the function being called in a Call node.""" return isinstance(node.parent, nodes.Call) and node.parent.func is node def assign_parent(node: nodes.NodeNG) -> nodes.NodeNG: - """return the higher parent which is not an AssignName, Tuple or List node""" + """Return the higher parent which is not an AssignName, Tuple or List node.""" while node and isinstance(node, (nodes.AssignName, nodes.Tuple, nodes.List)): node = node.parent return node def overrides_a_method(class_node: nodes.ClassDef, name: str) -> bool: - """return True if is a method overridden from an ancestor + """Return True if is a method overridden from an ancestor which is not the base object class """ for ancestor in class_node.ancestors(): @@ -486,7 +486,7 @@ def overrides_a_method(class_node: nodes.ClassDef, name: str) -> bool: def check_messages(*messages: str) -> Callable: - """decorator to store messages that are handled by a checker method""" + """Decorator to store messages that are handled by a checker method.""" def store_messages(func): func.checks_msgs = messages @@ -652,7 +652,7 @@ def parse_format_method_string( def is_attr_protected(attrname: str) -> bool: - """return True if attribute name is protected (start with _ and some other + """Return True if attribute name is protected (start with _ and some other details), False otherwise. """ return ( @@ -663,7 +663,7 @@ def is_attr_protected(attrname: str) -> bool: def node_frame_class(node: nodes.NodeNG) -> Optional[nodes.ClassDef]: - """Return the class that is wrapping the given node + """Return the class that is wrapping the given node. The function returns a class for a method node (or a staticmethod or a classmethod), otherwise it returns `None`. @@ -688,7 +688,7 @@ def node_frame_class(node: nodes.NodeNG) -> Optional[nodes.ClassDef]: def get_outer_class(class_node: astroid.ClassDef) -> Optional[astroid.ClassDef]: - """Return the class that is the outer class of given (nested) class_node""" + """Return the class that is the outer class of given (nested) class_node.""" parent_klass = class_node.parent.frame(future=True) return parent_klass if isinstance(parent_klass, astroid.ClassDef) else None @@ -792,17 +792,17 @@ def _is_property_kind(node, *kinds): def is_property_setter(node: nodes.FunctionDef) -> bool: - """Check if the given node is a property setter""" + """Check if the given node is a property setter.""" return _is_property_kind(node, "setter") def is_property_deleter(node: nodes.FunctionDef) -> bool: - """Check if the given node is a property deleter""" + """Check if the given node is a property deleter.""" return _is_property_kind(node, "deleter") def is_property_setter_or_deleter(node: nodes.FunctionDef) -> bool: - """Check if the given node is either a property setter or a deleter""" + """Check if the given node is either a property setter or a deleter.""" return _is_property_kind(node, "setter", "deleter") @@ -1071,7 +1071,7 @@ def node_ignores_exception(node: nodes.NodeNG, exception=Exception) -> bool: def class_is_abstract(node: nodes.ClassDef) -> bool: - """return true if the given class node should be considered as an abstract + """Return true if the given class node should be considered as an abstract class """ # Only check for explicit metaclass=ABCMeta on this specific class @@ -1313,7 +1313,7 @@ def is_none(node: nodes.NodeNG) -> bool: def node_type(node: nodes.NodeNG) -> Optional[nodes.NodeNG]: - """Return the inferred type for `node` + """Return the inferred type for `node`. If there is more than one possible type, or if inferred type is Uninferable or None, return None @@ -1388,7 +1388,7 @@ def get_node_last_lineno(node: nodes.NodeNG) -> int: def is_postponed_evaluation_enabled(node: nodes.NodeNG) -> bool: - """Check if the postponed evaluation of annotations is enabled""" + """Check if the postponed evaluation of annotations is enabled.""" module = node.root() return "annotations" in module.future_imports @@ -1466,7 +1466,7 @@ def is_overload_stub(node: nodes.NodeNG) -> bool: def is_protocol_class(cls: nodes.NodeNG) -> bool: - """Check if the given node represents a protocol class + """Check if the given node represents a protocol class. :param cls: The node to check :returns: True if the node is a typing protocol class, false otherwise. @@ -1480,7 +1480,7 @@ def is_protocol_class(cls: nodes.NodeNG) -> bool: def is_call_of_name(node: nodes.NodeNG, name: str) -> bool: - """Checks if node is a function call with the given name""" + """Checks if node is a function call with the given name.""" return ( isinstance(node, nodes.Call) and isinstance(node.func, nodes.Name) @@ -1492,7 +1492,7 @@ def is_test_condition( node: nodes.NodeNG, parent: Optional[nodes.NodeNG] = None, ) -> bool: - """Returns true if the given node is being tested for truthiness""" + """Returns true if the given node is being tested for truthiness.""" parent = parent or node.parent if isinstance(parent, (nodes.While, nodes.If, nodes.IfExp, nodes.Assert)): return node is parent.test or parent.test.parent_of(node) @@ -1600,7 +1600,7 @@ def get_subscript_const_value(node: nodes.Subscript) -> nodes.Const: def get_import_name( importnode: Union[nodes.Import, nodes.ImportFrom], modname: str ) -> str: - """Get a prepared module name from the given import node + """Get a prepared module name from the given import node. In the case of relative imports, this will return the absolute qualified module name, which might be useful @@ -1666,7 +1666,7 @@ def is_node_in_guarded_import_block(node: nodes.NodeNG) -> bool: def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: - """Check if the given variable name is reassigned in the same scope after the current node""" + """Check if the given variable name is reassigned in the same scope after the current node.""" return any( a.name == varname and a.lineno > node.lineno for a in node.scope().nodes_of_class( @@ -1676,7 +1676,7 @@ def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool: - """Check if the given variable name is deleted in the same scope after the current node""" + """Check if the given variable name is deleted in the same scope after the current node.""" return any( getattr(target, "name", None) == varname and target.lineno > node.lineno for del_node in node.scope().nodes_of_class(nodes.Delete) @@ -1685,7 +1685,7 @@ def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool: def is_function_body_ellipsis(node: nodes.FunctionDef) -> bool: - """Checks whether a function body only consists of a single Ellipsis""" + """Checks whether a function body only consists of a single Ellipsis.""" return ( len(node.body) == 1 and isinstance(node.body[0], nodes.Expr) @@ -1709,7 +1709,7 @@ def is_empty_str_literal(node: Optional[nodes.NodeNG]) -> bool: def returns_bool(node: nodes.NodeNG) -> bool: - """Returns true if a node is a return that returns a constant boolean""" + """Returns true if a node is a return that returns a constant boolean.""" return ( isinstance(node, nodes.Return) and isinstance(node.value, nodes.Const) @@ -1720,7 +1720,7 @@ def returns_bool(node: nodes.NodeNG) -> bool: def get_node_first_ancestor_of_type( node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node], ...]] ) -> Optional[T_Node]: - """Return the first parent node that is any of the provided types (or None)""" + """Return the first parent node that is any of the provided types (or None).""" for ancestor in node.node_ancestors(): if isinstance(ancestor, ancestor_type): return ancestor diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 160680caad..90eee481f0 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -54,7 +54,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Variables checkers for Python code""" +"""Variables checkers for Python code.""" import collections import copy import itertools @@ -197,7 +197,7 @@ def in_for_else_branch(parent, stmt): @lru_cache(maxsize=1000) def overridden_method(klass, name): - """get overridden method if any""" + """Get overridden method if any.""" try: parent = next(klass.local_attr_ancestors(name)) except (StopIteration, KeyError): @@ -214,7 +214,7 @@ def overridden_method(klass, name): def _get_unpacking_extra_info(node, inferred): - """return extra information to add to the message for unpacking-non-sequence + """Return extra information to add to the message for unpacking-non-sequence and unbalanced-tuple-unpacking errors """ more = "" @@ -378,13 +378,13 @@ def _flattened_scope_names(iterator): def _assigned_locally(name_node): - """Checks if name_node has corresponding assign statement in same scope""" + """Checks if name_node has corresponding assign statement in same scope.""" assign_stmts = name_node.scope().nodes_of_class(nodes.AssignName) return any(a.name == name_node.name for a in assign_stmts) def _is_type_checking_import(node: Union[nodes.Import, nodes.ImportFrom]) -> bool: - """Check if an import node is guarded by a TYPE_CHECKS guard""" + """Check if an import node is guarded by a TYPE_CHECKS guard.""" return any( isinstance(ancestor, nodes.If) and ancestor.test.as_string() in TYPING_TYPE_CHECKS_GUARDS @@ -556,7 +556,7 @@ class ScopeConsumer(NamedTuple): class NamesConsumer: - """A simple class to handle consumed, to consume and scope type info of node locals""" + """A simple class to handle consumed, to consume and scope type info of node locals.""" def __init__(self, node, scope_type): self._atomic = ScopeConsumer( @@ -981,7 +981,7 @@ def _uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( # pylint: disable=too-many-public-methods class VariablesChecker(BaseChecker): - """checks for + """Checks for * unused variables / imports * undefined variables * redefinition of variable from builtins or from an outer scope @@ -1093,11 +1093,11 @@ def __init__(self, linter=None): self._except_handler_names_queue: List[ Tuple[nodes.ExceptHandler, nodes.AssignName] ] = [] - """This is a queue, last in first out""" + """This is a queue, last in first out.""" self._postponed_evaluation_enabled = False def open(self) -> None: - """Called when loading the checker""" + """Called when loading the checker.""" self._is_undefined_variable_enabled = self.linter.is_message_enabled( "undefined-variable" ) @@ -1136,7 +1136,7 @@ def leave_for(self, node: nodes.For) -> None: self._store_type_annotation_names(node) def visit_module(self, node: nodes.Module) -> None: - """visit module : update consumption analysis variable + """Visit module : update consumption analysis variable checks globals doesn't overrides builtins """ self._to_consume = [NamesConsumer(node, "module")] @@ -1158,7 +1158,7 @@ def visit_module(self, node: nodes.Module) -> None: "unused-variable", ) def leave_module(self, node: nodes.Module) -> None: - """leave module: check globals""" + """Leave module: check globals.""" assert len(self._to_consume) == 1 self._check_metaclasses(node) @@ -1177,52 +1177,52 @@ def leave_module(self, node: nodes.Module) -> None: self._check_imports(not_consumed) def visit_classdef(self, node: nodes.ClassDef) -> None: - """visit class: update consumption analysis variable""" + """Visit class: update consumption analysis variable.""" self._to_consume.append(NamesConsumer(node, "class")) def leave_classdef(self, _: nodes.ClassDef) -> None: - """leave class: update consumption analysis variable""" + """Leave class: update consumption analysis variable.""" # do not check for not used locals here (no sense) self._to_consume.pop() def visit_lambda(self, node: nodes.Lambda) -> None: - """visit lambda: update consumption analysis variable""" + """Visit lambda: update consumption analysis variable.""" self._to_consume.append(NamesConsumer(node, "lambda")) def leave_lambda(self, _: nodes.Lambda) -> None: - """leave lambda: update consumption analysis variable""" + """Leave lambda: update consumption analysis variable.""" # do not check for not used locals here self._to_consume.pop() def visit_generatorexp(self, node: nodes.GeneratorExp) -> None: - """visit genexpr: update consumption analysis variable""" + """Visit genexpr: update consumption analysis variable.""" self._to_consume.append(NamesConsumer(node, "comprehension")) def leave_generatorexp(self, _: nodes.GeneratorExp) -> None: - """leave genexpr: update consumption analysis variable""" + """Leave genexpr: update consumption analysis variable.""" # do not check for not used locals here self._to_consume.pop() def visit_dictcomp(self, node: nodes.DictComp) -> None: - """visit dictcomp: update consumption analysis variable""" + """Visit dictcomp: update consumption analysis variable.""" self._to_consume.append(NamesConsumer(node, "comprehension")) def leave_dictcomp(self, _: nodes.DictComp) -> None: - """leave dictcomp: update consumption analysis variable""" + """Leave dictcomp: update consumption analysis variable.""" # do not check for not used locals here self._to_consume.pop() def visit_setcomp(self, node: nodes.SetComp) -> None: - """visit setcomp: update consumption analysis variable""" + """Visit setcomp: update consumption analysis variable.""" self._to_consume.append(NamesConsumer(node, "comprehension")) def leave_setcomp(self, _: nodes.SetComp) -> None: - """leave setcomp: update consumption analysis variable""" + """Leave setcomp: update consumption analysis variable.""" # do not check for not used locals here self._to_consume.pop() def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """visit function: update consumption analysis variable and check locals""" + """Visit function: update consumption analysis variable and check locals.""" self._to_consume.append(NamesConsumer(node, "function")) if not ( self.linter.is_message_enabled("redefined-outer-name") @@ -1264,7 +1264,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self.add_message("redefined-builtin", args=name, node=stmt) def leave_functiondef(self, node: nodes.FunctionDef) -> None: - """leave function: check function's locals are consumed""" + """Leave function: check function's locals are consumed.""" self._check_metaclasses(node) if node.type_comment_returns: @@ -1306,7 +1306,7 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None: "redefined-builtin", ) def visit_global(self, node: nodes.Global) -> None: - """check names imported exists in the global scope""" + """Check names imported exists in the global scope.""" frame = node.frame(future=True) if isinstance(frame, nodes.Module): self.add_message("global-at-module-level", node=node) @@ -1502,7 +1502,7 @@ def _check_consumer( consumer_level: int, base_scope_type: Any, ) -> Tuple[VariableVisitConsumerAction, Optional[List[nodes.NodeNG]]]: - """Checks a consumer for conditions that should trigger messages""" + """Checks a consumer for conditions that should trigger messages.""" # If the name has already been consumed, only check it's not a loop # variable used outside the loop. # Avoid the case where there are homonyms inside function scope and @@ -1719,7 +1719,7 @@ def _check_consumer( @utils.check_messages("no-name-in-module") def visit_import(self, node: nodes.Import) -> None: - """check modules attribute accesses""" + """Check modules attribute accesses.""" if not self._analyse_fallback_blocks and utils.is_from_fallback_block(node): # No need to verify this, since ImportError is already # handled by the client code. @@ -1741,7 +1741,7 @@ def visit_import(self, node: nodes.Import) -> None: @utils.check_messages("no-name-in-module") def visit_importfrom(self, node: nodes.ImportFrom) -> None: - """check modules attribute accesses""" + """Check modules attribute accesses.""" if not self._analyse_fallback_blocks and utils.is_from_fallback_block(node): # No need to verify this, since ImportError is already # handled by the client code. @@ -1786,11 +1786,11 @@ def visit_assign(self, node: nodes.Assign) -> None: # listcomp have now also their scope def visit_listcomp(self, node: nodes.ListComp) -> None: - """visit dictcomp: update consumption analysis variable""" + """Visit dictcomp: update consumption analysis variable.""" self._to_consume.append(NamesConsumer(node, "comprehension")) def leave_listcomp(self, _: nodes.ListComp) -> None: - """leave dictcomp: update consumption analysis variable""" + """Leave dictcomp: update consumption analysis variable.""" # do not check for not used locals here self._to_consume.pop() @@ -1845,7 +1845,7 @@ def _defined_in_function_definition(node, frame): def _in_lambda_or_comprehension_body( node: nodes.NodeNG, frame: nodes.NodeNG ) -> bool: - """return True if node within a lambda/comprehension body (or similar) and thus should not have access to class attributes in frame""" + """Return True if node within a lambda/comprehension body (or similar) and thus should not have access to class attributes in frame.""" child = node parent = node.parent while parent is not None: @@ -2076,7 +2076,7 @@ def _is_variable_violation( @staticmethod def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool: - """Check if variable only gets assigned a type and never a value""" + """Check if variable only gets assigned a type and never a value.""" if not isinstance(defstmt, nodes.AnnAssign) or defstmt.value: return False @@ -2487,7 +2487,7 @@ def _has_homonym_in_upper_function_scope( ) def _store_type_annotation_node(self, type_annotation): - """Given a type annotation, store all the name nodes it refers to""" + """Given a type annotation, store all the name nodes it refers to.""" if isinstance(type_annotation, nodes.Name): self._type_annotation_names.append(type_annotation.name) return @@ -2518,7 +2518,7 @@ def _store_type_annotation_names(self, node): self._store_type_annotation_node(node.type_annotation) def _check_self_cls_assign(self, node: nodes.Assign) -> None: - """Check that self/cls don't get assigned""" + """Check that self/cls don't get assigned.""" assign_names: Set[Optional[str]] = set() for target in node.targets: if isinstance(target, nodes.AssignName): @@ -2591,7 +2591,7 @@ def _check_unpacking(self, inferred, node, targets): @staticmethod def _nodes_to_unpack(node: nodes.NodeNG) -> Optional[List[nodes.NodeNG]]: - """Return the list of values of the `Assign` node""" + """Return the list of values of the `Assign` node.""" if isinstance(node, (nodes.Tuple, nodes.List)): return node.itered() if isinstance(node, astroid.Instance) and any( @@ -2601,7 +2601,7 @@ def _nodes_to_unpack(node: nodes.NodeNG) -> Optional[List[nodes.NodeNG]]: return None def _check_module_attrs(self, node, module, module_names): - """check that module_names (list of string) are accessible through the + """Check that module_names (list of string) are accessible through the given module if the latest access name corresponds to a module, return it """ diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index 36fd351f05..10c31345de 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -71,7 +71,7 @@ def find_default_config_files() -> Iterator[str]: def find_pylintrc() -> Optional[str]: - """Search the pylint rc file and return its path if it finds it, else return None""" + """Search the pylint rc file and return its path if it finds it, else return None.""" for config_file in find_default_config_files(): if config_file.endswith("pylintrc"): return config_file diff --git a/pylint/config/man_help_formatter.py b/pylint/config/man_help_formatter.py index e8e0198247..c00f45468e 100644 --- a/pylint/config/man_help_formatter.py +++ b/pylint/config/man_help_formatter.py @@ -107,7 +107,7 @@ def format_tail(pkginfo): return tail def format_usage(self, usage): - """Taken from optparse.IndentedHelpFormatter""" + """Taken from optparse.IndentedHelpFormatter.""" return f"Usage: {usage}\n" diff --git a/pylint/config/option.py b/pylint/config/option.py index 61384b8d41..cf88e3fc90 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -128,7 +128,7 @@ def _call_validator(opttype, optdict, option, value): def _validate(value, optdict, name=""): - """return a validated value for an option according to its type + """Return a validated value for an option according to its type. optional argument name is only used for error message formatting """ diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 67740d3342..9260f75930 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -23,7 +23,7 @@ def _expand_default(self, option): - """Patch OptionParser.expand_default with custom behaviour + """Patch OptionParser.expand_default with custom behaviour. This will handle defaults to avoid overriding values in the configuration file. @@ -57,7 +57,7 @@ def _patch_optparse(): class OptionsManagerMixIn: - """Handle configuration from both a configuration file and command line options""" + """Handle configuration from both a configuration file and command line options.""" def __init__(self, usage, config_file=None): self.config_file = config_file @@ -83,7 +83,7 @@ def reset_parsers(self, usage=""): self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS) def register_options_provider(self, provider, own_group=True): - """register an options provider""" + """Register an options provider.""" assert provider.priority <= 0, "provider's priority can't be >= 0" for i, options_provider in enumerate(self.options_providers): if provider.priority > options_provider.priority: @@ -142,7 +142,7 @@ def add_optik_option(self, provider, optikcontainer, opt, optdict): self._maxlevel = max(self._maxlevel, option.level or 0) def optik_option(self, provider, opt, optdict): - """get our personal option definition and return a suitable form for + """Get our personal option definition and return a suitable form for use with optik/optparse """ optdict = copy.copy(optdict) @@ -173,7 +173,7 @@ def optik_option(self, provider, opt, optdict): return args, optdict def cb_set_provider_option(self, option, opt, value, parser): - """optik callback for option setting""" + """Optik callback for option setting.""" if opt.startswith("--"): # remove -- on long option opt = opt[2:] @@ -186,13 +186,13 @@ def cb_set_provider_option(self, option, opt, value, parser): self.global_set_option(opt, value) def global_set_option(self, opt, value): - """set option on the correct option provider""" + """Set option on the correct option provider.""" self._all_options[opt].set_option(opt, value) def generate_config( self, stream: Optional[TextIO] = None, skipsections: Tuple[str, ...] = () ) -> None: - """write a configuration file according to the current configuration + """Write a configuration file according to the current configuration into the given stream or stdout """ options_by_section: Dict[str, List[Tuple]] = {} @@ -239,7 +239,7 @@ def generate_manpage( print(formatter.format_tail(pkginfo), file=stream) def load_provider_defaults(self): - """initialize configuration using default values""" + """Initialize configuration using default values.""" for provider in self.options_providers: provider.load_defaults() @@ -343,7 +343,7 @@ def load_config_file(self): continue def load_configuration(self, **kwargs): - """override configuration according to given parameters""" + """Override configuration according to given parameters.""" return self.load_configuration_from_config(kwargs) def load_configuration_from_config(self, config): @@ -353,7 +353,7 @@ def load_configuration_from_config(self, config): provider.set_option(opt, opt_value) def load_command_line_configuration(self, args=None) -> List[str]: - """Override configuration according to command line parameters + """Override configuration according to command line parameters. return additional arguments """ @@ -370,7 +370,7 @@ def load_command_line_configuration(self, args=None) -> List[str]: return args def add_help_section(self, title, description, level=0): - """add a dummy option section for help purpose""" + """Add a dummy option section for help purpose.""" group = optparse.OptionGroup( self.cmdline_parser, title=title.capitalize(), description=description ) @@ -379,7 +379,7 @@ def add_help_section(self, title, description, level=0): self.cmdline_parser.add_option_group(group) def help(self, level=0): - """return the usage string for available options""" + """Return the usage string for available options.""" self.cmdline_parser.formatter.output_level = level with _patch_optparse(): return self.cmdline_parser.format_help() diff --git a/pylint/config/options_provider_mixin.py b/pylint/config/options_provider_mixin.py index d3872135cc..8c6204586a 100644 --- a/pylint/config/options_provider_mixin.py +++ b/pylint/config/options_provider_mixin.py @@ -9,11 +9,11 @@ class UnsupportedAction(Exception): - """raised by set_option when it doesn't know what to do for an action""" + """Raised by set_option when it doesn't know what to do for an action.""" class OptionsProviderMixIn: - """Mixin to provide options to an OptionsManager""" + """Mixin to provide options to an OptionsManager.""" # those attributes should be overridden priority = -1 @@ -26,7 +26,7 @@ def __init__(self): self.load_defaults() def load_defaults(self): - """initialize the provider using default values""" + """Initialize the provider using default values.""" for opt, optdict in self.options: action = optdict.get("action") if action != "callback": @@ -37,17 +37,17 @@ def load_defaults(self): self.set_option(opt, default, action, optdict) def option_attrname(self, opt, optdict=None): - """get the config attribute corresponding to opt""" + """Get the config attribute corresponding to opt.""" if optdict is None: optdict = self.get_option_def(opt) return optdict.get("dest", opt.replace("-", "_")) def option_value(self, opt): - """get the current value for the given option""" + """Get the current value for the given option.""" return getattr(self.config, self.option_attrname(opt), None) def set_option(self, optname, value, action=None, optdict=None): - """method called to set an option (registered in the options list)""" + """Method called to set an option (registered in the options list).""" if optdict is None: optdict = self.get_option_def(optname) if value is not None: @@ -80,7 +80,7 @@ def set_option(self, optname, value, action=None, optdict=None): raise UnsupportedAction(action) def get_option_def(self, opt): - """return the dictionary defining an option given its name""" + """Return the dictionary defining an option given its name.""" assert self.options for option in self.options: if option[0] == opt: @@ -90,7 +90,7 @@ def get_option_def(self, opt): ) def options_by_section(self): - """return an iterator on options grouped by section + """Return an iterator on options grouped by section. (section, [list of (optname, optdict, optvalue)]) """ diff --git a/pylint/epylint.py b/pylint/epylint.py index 5517543a4d..223f723a77 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -138,7 +138,7 @@ def lint(filename, options=()): def py_run(command_options="", return_std=False, stdout=None, stderr=None): - """Run pylint from python + """Run pylint from python. ``command_options`` is a string containing ``pylint`` command line options; ``return_std`` (boolean) indicates return of created standard output diff --git a/pylint/exceptions.py b/pylint/exceptions.py index ec1e513f7c..5999dcfd4a 100644 --- a/pylint/exceptions.py +++ b/pylint/exceptions.py @@ -16,24 +16,24 @@ class InvalidMessageError(Exception): - """raised when a message creation, registration or addition is rejected""" + """Raised when a message creation, registration or addition is rejected.""" class UnknownMessageError(Exception): - """raised when an unregistered message id is encountered""" + """Raised when an unregistered message id is encountered.""" class EmptyReportError(Exception): - """raised when a report is empty and so should not be displayed""" + """Raised when a report is empty and so should not be displayed.""" class InvalidReporterError(Exception): - """raised when selected reporter is invalid (e.g. not found)""" + """Raised when selected reporter is invalid (e.g. not found).""" class InvalidArgsError(ValueError): - """raised when passed arguments are invalid, e.g., have the wrong length""" + """Raised when passed arguments are invalid, e.g., have the wrong length.""" class NoLineSuppliedError(Exception): - """raised when trying to disable a message on a next line without supplying a line number""" + """Raised when trying to disable a message on a next line without supplying a line number.""" diff --git a/pylint/extensions/__init__.py b/pylint/extensions/__init__.py index 8b5fa91324..c348361019 100644 --- a/pylint/extensions/__init__.py +++ b/pylint/extensions/__init__.py @@ -10,7 +10,7 @@ def initialize(linter: "PyLinter") -> None: - """Initialize linter with checkers in the extensions directory""" + """Initialize linter with checkers in the extensions directory.""" register_plugins(linter, __path__[0]) diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 59f5dd9928..70b312539e 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -34,7 +34,7 @@ def space_indentation(s): - """The number of leading spaces in a string + """The number of leading spaces in a string. :param str s: input string @@ -216,7 +216,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__}:'''{self.doc}'''>" def matching_sections(self) -> int: - """Returns the number of matching docstring sections""" + """Returns the number of matching docstring sections.""" return 0 def exceptions(self): @@ -329,7 +329,7 @@ class SphinxDocstring(Docstring): supports_yields = False def matching_sections(self) -> int: - """Returns the number of matching docstring sections""" + """Returns the number of matching docstring sections.""" return sum( bool(i) for i in ( @@ -536,7 +536,7 @@ class GoogleDocstring(Docstring): supports_yields = True def matching_sections(self) -> int: - """Returns the number of matching docstring sections""" + """Returns the number of matching docstring sections.""" return sum( bool(i) for i in ( @@ -782,7 +782,7 @@ class NumpyDocstring(GoogleDocstring): supports_yields = True def match_param_docs(self) -> Tuple[Set[str], Set[str]]: - """Matches parameter documentation section to parameter documentation rules""" + """Matches parameter documentation section to parameter documentation rules.""" params_with_doc = set() params_with_type = set() diff --git a/pylint/extensions/check_elif.py b/pylint/extensions/check_elif.py index 1fb6323b26..0563d5170c 100644 --- a/pylint/extensions/check_elif.py +++ b/pylint/extensions/check_elif.py @@ -25,7 +25,7 @@ class ElseifUsedChecker(BaseTokenChecker): - """Checks for use of "else if" when an "elif" could be used""" + """Checks for use of "else if" when an "elif" could be used.""" __implements__ = (ITokenChecker, IAstroidChecker) name = "else_if_used" @@ -47,7 +47,7 @@ def _init(self): self._elifs = {} def process_tokens(self, tokens): - """Process tokens and look for 'if' or 'elif'""" + """Process tokens and look for 'if' or 'elif'.""" self._elifs = { begin: token for _, token, begin, _, _ in tokens if token in {"elif", "if"} } @@ -57,7 +57,7 @@ def leave_module(self, _: nodes.Module) -> None: @check_messages("else-if-used") def visit_if(self, node: nodes.If) -> None: - """Current if node must directly follow an 'else'""" + """Current if node must directly follow an 'else'.""" if ( isinstance(node.parent, nodes.If) and node.parent.orelse == [node] diff --git a/pylint/extensions/comparison_placement.py b/pylint/extensions/comparison_placement.py index e045ebdd01..f34f1eb821 100644 --- a/pylint/extensions/comparison_placement.py +++ b/pylint/extensions/comparison_placement.py @@ -21,7 +21,7 @@ class MisplacedComparisonConstantChecker(BaseChecker): - """Checks the placement of constants in comparisons""" + """Checks the placement of constants in comparisons.""" __implements__ = (IAstroidChecker,) diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 7c18e6b623..35324ac041 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -24,7 +24,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings""" +"""Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings.""" import re from typing import TYPE_CHECKING, Optional @@ -43,7 +43,7 @@ class DocstringParameterChecker(BaseChecker): - """Checker for Sphinx, Google, or Numpy style docstrings + """Checker for Sphinx, Google, or Numpy style docstrings. * Check that all function, method and constructor parameters are mentioned in the params and types part of the docstring. Constructor parameters diff --git a/pylint/extensions/docstyle.py b/pylint/extensions/docstyle.py index d2f07003b1..7dc26db5d4 100644 --- a/pylint/extensions/docstyle.py +++ b/pylint/extensions/docstyle.py @@ -25,7 +25,7 @@ class DocStringStyleChecker(checkers.BaseChecker): - """Checks format of docstrings based on PEP 0257""" + """Checks format of docstrings based on PEP 0257.""" __implements__ = IAstroidChecker name = "docstyle" diff --git a/pylint/extensions/empty_comment.py b/pylint/extensions/empty_comment.py index c525403082..68b48970d9 100644 --- a/pylint/extensions/empty_comment.py +++ b/pylint/extensions/empty_comment.py @@ -10,7 +10,7 @@ def is_line_commented(line): - """Checks if a `# symbol that is not part of a string was found in line""" + """Checks if a `# symbol that is not part of a string was found in line.""" comment_idx = line.find(b"#") if comment_idx == -1: @@ -21,7 +21,7 @@ def is_line_commented(line): def comment_part_of_string(line, comment_idx): - """checks if the symbol at comment_idx is part of a string""" + """Checks if the symbol at comment_idx is part of a string.""" if ( line[:comment_idx].count(b"'") % 2 == 1 diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py index d675087fe1..b84865decf 100644 --- a/pylint/extensions/mccabe.py +++ b/pylint/extensions/mccabe.py @@ -11,7 +11,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Module to add McCabe checker class for pylint. """ +"""Module to add McCabe checker class for pylint.""" from typing import TYPE_CHECKING @@ -115,7 +115,7 @@ def _append_node(self, node): return node def _subgraph(self, node, name, extra_blocks=()): - """create the subgraphs representing any `if` and `for` statements""" + """Create the subgraphs representing any `if` and `for` statements.""" if self.graph is None: # global loop self.graph = PathGraph(node) @@ -127,7 +127,7 @@ def _subgraph(self, node, name, extra_blocks=()): self._subgraph_parse(node, node, extra_blocks) def _subgraph_parse(self, node, pathnode, extra_blocks): - """parse the body and any `else` block of `if` and `for` statements""" + """Parse the body and any `else` block of `if` and `for` statements.""" loose_ends = [] self.tail = node self.dispatch_list(node.body) @@ -180,7 +180,7 @@ class McCabeMethodChecker(checkers.BaseChecker): @check_messages("too-complex") def visit_module(self, node: nodes.Module) -> None: - """visit an astroid.Module node to check too complex rating and + """Visit an astroid.Module node to check too complex rating and add message if is greater than max_complexity stored from options """ visitor = PathGraphingAstVisitor() diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index 9729fd112e..11c79eb04e 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -37,7 +37,7 @@ class OverlappingExceptionsChecker(checkers.BaseChecker): @utils.check_messages("overlapping-except") def visit_tryexcept(self, node: nodes.TryExcept) -> None: - """check for empty except""" + """Check for empty except.""" for handler in node.handlers: if handler.type is None: continue diff --git a/pylint/extensions/redefined_variable_type.py b/pylint/extensions/redefined_variable_type.py index 775f7adaeb..527418bf53 100644 --- a/pylint/extensions/redefined_variable_type.py +++ b/pylint/extensions/redefined_variable_type.py @@ -24,7 +24,7 @@ class MultipleTypesChecker(BaseChecker): - """Checks for variable type redefinitions (NoneType excepted) + """Checks for variable type redefinitions (NoneType excepted). At a function, method, class or module scope diff --git a/pylint/graph.py b/pylint/graph.py index 70a6186784..f75ce4e641 100644 --- a/pylint/graph.py +++ b/pylint/graph.py @@ -74,7 +74,7 @@ def __init__( self.emit("=".join(param)) def get_source(self): - """returns self._source""" + """Returns self._source.""" if self._source is None: self.emit("}\n") self._source = "\n".join(self.lines) @@ -151,7 +151,7 @@ def emit(self, line): self.lines.append(line) def emit_edge(self, name1, name2, **props): - """emit an edge from to . + """Emit an edge from to . edge properties: see https://www.graphviz.org/doc/info/attrs.html """ attrs = [f'{prop}="{value}"' for prop, value in props.items()] @@ -159,7 +159,7 @@ def emit_edge(self, name1, name2, **props): self.emit(f"{n_from} -> {n_to} [{', '.join(sorted(attrs))}];") def emit_node(self, name, **props): - """emit a node with given properties. + """Emit a node with given properties. node properties: see https://www.graphviz.org/doc/info/attrs.html """ attrs = [f'{prop}="{value}"' for prop, value in props.items()] @@ -172,7 +172,7 @@ def normalize_node_id(nid): def get_cycles(graph_dict, vertices=None): - """given a dictionary representing an ordered graph (i.e. key are vertices + """Given a dictionary representing an ordered graph (i.e. key are vertices and values is a list of destination vertices representing edges), return a list of detected cycles """ @@ -187,7 +187,7 @@ def get_cycles(graph_dict, vertices=None): def _get_cycles(graph_dict, path, visited, result, vertice): - """recursive function doing the real work for get_cycles""" + """Recursive function doing the real work for get_cycles.""" if vertice in path: cycle = [vertice] for node in path[::-1]: diff --git a/pylint/interfaces.py b/pylint/interfaces.py index 103eb207b4..e2b4b44220 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -17,7 +17,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Interfaces for Pylint objects""" +"""Interfaces for Pylint objects.""" from collections import namedtuple from typing import TYPE_CHECKING, Tuple, Type, Union @@ -79,17 +79,17 @@ class IChecker(Interface): """Base interface, to be used only for sub interfaces definition.""" def open(self): - """called before visiting project (i.e. set of modules)""" + """Called before visiting project (i.e. set of modules).""" def close(self): - """called after visiting project (i.e. set of modules)""" + """Called after visiting project (i.e. set of modules).""" class IRawChecker(IChecker): - """Interface for checker which need to parse the raw file""" + """Interface for checker which need to parse the raw file.""" def process_module(self, node: nodes.Module) -> None: - """process a module + """Process a module. The module's content is accessible via ``astroid.stream`` """ @@ -106,16 +106,16 @@ def process_tokens(self, tokens): class IAstroidChecker(IChecker): - """interface for checker which prefers receive events according to + """Interface for checker which prefers receive events according to statement type """ class IReporter(Interface): - """reporter collect messages and display results encapsulated in a layout""" + """Reporter collect messages and display results encapsulated in a layout.""" def handle_message(self, msg) -> None: """Handle the given message object.""" def display_reports(self, layout: "Section") -> None: - """display results encapsulated in the layout tree""" + """Display results encapsulated in the layout tree.""" diff --git a/pylint/lint/__init__.py b/pylint/lint/__init__.py index 69d0f95d14..ae3c56c890 100644 --- a/pylint/lint/__init__.py +++ b/pylint/lint/__init__.py @@ -59,7 +59,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -""" pylint [options] modules_or_packages +"""Pylint [options] modules_or_packages. Check that module(s) satisfy a coding standard (and more !). diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 4d3600a000..296e6b5778 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -33,7 +33,7 @@ def get_python_path(filepath: str) -> str: def _is_in_ignore_list_re(element: str, ignore_list_re: List[Pattern]) -> bool: - """determines if the element is matched in a regex ignore-list""" + """Determines if the element is matched in a regex ignore-list.""" return any(file_pattern.match(element) for file_pattern in ignore_list_re) @@ -43,7 +43,7 @@ def expand_modules( ignore_list_re: List[Pattern], ignore_list_paths_re: List[Pattern[str]], ) -> Tuple[List[ModuleDescriptionDict], List[ErrorDescriptionDict]]: - """take a list of files/modules/packages and return the list of tuple + """Take a list of files/modules/packages and return the list of tuple (file, module name) which have to be actually checked """ result: List[ModuleDescriptionDict] = [] diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index 63b1e812b7..f3e2d6cb45 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -51,7 +51,7 @@ def _get_new_args(message): def _worker_initialize( linter: bytes, arguments: Union[None, str, Sequence[str]] = None ) -> None: - """Function called to initialize a worker for a Process within a multiprocessing Pool + """Function called to initialize a worker for a Process within a multiprocessing Pool. :param linter: A linter-class (PyLinter) instance pickled with dill :param arguments: File or module name(s) to lint and to be added to sys.path @@ -107,7 +107,7 @@ def _worker_check_single_file( def _merge_mapreduce_data(linter, all_mapreduce_data): - """Merges map/reduce data across workers, invoking relevant APIs on checkers""" + """Merges map/reduce data across workers, invoking relevant APIs on checkers.""" # First collate the data and prepare it, so we can send it to the checkers for # validation. The intent here is to collect all the mapreduce data for all checker- # runs across processes - that will then be passed to a static method on the diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 36651c04d6..2f5389d613 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -193,7 +193,7 @@ class PyLinter( reporters.ReportsHandlerMixIn, checkers.BaseTokenChecker, ): - """lint Python modules using external checkers. + """Lint Python modules using external checkers. This is the main checker controlling the other ones and the reports generation. It is itself both a raw checker and an astroid checker in order @@ -560,15 +560,15 @@ def __init__( else: self.set_reporter(TextReporter()) self._reporters: Dict[str, Type[reporters.BaseReporter]] = {} - """Dictionary of possible but non-initialized reporters""" + """Dictionary of possible but non-initialized reporters.""" # Attributes for checkers and plugins self._checkers: DefaultDict[ str, List[checkers.BaseChecker] ] = collections.defaultdict(list) - """Dictionary of registered and initialized checkers""" + """Dictionary of registered and initialized checkers.""" self._dynamic_plugins: Set[str] = set() - """Set of loaded plugin names""" + """Set of loaded plugin names.""" # Attributes related to visiting files self.file_state = FileState() @@ -598,7 +598,7 @@ def __init__( "enable-msg": self._options_methods["enable"], } self.fail_on_symbols: List[str] = [] - """List of message symbols on which pylint should fail, set by --fail-on""" + """List of message symbols on which pylint should fail, set by --fail-on.""" self._error_mode = False # Attributes related to messages (states) and their handling @@ -631,7 +631,7 @@ def load_default_plugins(self): reporters.initialize(self) def load_plugin_modules(self, modnames): - """take a list of module names which are pylint plugins and load + """Take a list of module names which are pylint plugins and load and register them """ for modname in modnames: @@ -645,7 +645,7 @@ def load_plugin_modules(self, modnames): pass def load_plugin_configuration(self): - """Call the configuration hook for plugins + """Call the configuration hook for plugins. This walks through the list of plugins, grabs the "load_configuration" hook, if exposed, and calls it to allow plugins to configure specific @@ -660,7 +660,7 @@ def load_plugin_configuration(self): self.add_message("bad-plugin-value", args=(modname, e), line=0) def _load_reporters(self, reporter_names: str) -> None: - """Load the reporters if they are available on _reporters""" + """Load the reporters if they are available on _reporters.""" if not self._reporters: return sub_reporters = [] @@ -706,12 +706,12 @@ def _load_reporter_by_name(self, reporter_name: str) -> reporters.BaseReporter: def set_reporter( self, reporter: Union[reporters.BaseReporter, reporters.MultiReporter] ) -> None: - """set the reporter used to display messages and reports""" + """Set the reporter used to display messages and reports.""" self.reporter = reporter reporter.linter = self def set_option(self, optname, value, action=None, optdict=None): - """overridden from config.OptionsProviderMixin to handle some + """Overridden from config.OptionsProviderMixin to handle some special options """ if optname in self._options_methods or optname in self._bw_options_methods: @@ -774,7 +774,7 @@ def register_checker(self, checker: checkers.BaseChecker) -> None: self.disable(checker.name) def enable_fail_on_messages(self): - """enable 'fail on' msgs + """Enable 'fail on' msgs. Convert values in config.fail_on (which might be msg category, msg id, or symbol) to specific msgs, then enable and flag them for later. @@ -820,13 +820,13 @@ def disable_noerror_messages(self): self.disable(msgid) def disable_reporters(self): - """disable all reporters""" + """Disable all reporters.""" for _reporters in self._reports.values(): for report_id, _, _ in _reporters: self.disable_report(report_id) def error_mode(self): - """error mode: enable only errors; no reports, no persistent""" + """Error mode: enable only errors; no reports, no persistent.""" self._error_mode = True self.disable_noerror_messages() self.disable("miscellaneous") @@ -941,7 +941,7 @@ def process_tokens(self, tokens): # code checking methods ################################################### def get_checkers(self): - """return all available checkers as a list""" + """Return all available checkers as a list.""" return [self] + [ c for _checkers in self._checkers.values() @@ -961,7 +961,7 @@ def get_checker_names(self): ) def prepare_checkers(self): - """return checkers needed for activated messages and reports""" + """Return checkers needed for activated messages and reports.""" if not self.config.reports: self.disable_reporters() # get needed checkers @@ -1002,7 +1002,7 @@ def should_analyze_file(modname, path, is_argument=False): # pylint: enable=unused-argument def initialize(self): - """Initialize linter for linting + """Initialize linter for linting. This method is called before any linting is done. """ @@ -1040,7 +1040,7 @@ def _discover_files(files_or_modules: Sequence[str]) -> Iterator[str]: yield something def check(self, files_or_modules: Union[Sequence[str], str]) -> None: - """main checking entry: check a list of files or modules from their name. + """Main checking entry: check a list of files or modules from their name. files_or_modules is either a string or list of strings presenting modules to check. """ @@ -1089,7 +1089,7 @@ def check_single_file(self, name: str, filepath: str, modname: str) -> None: self.check_single_file_item(FileItem(name, filepath, modname)) def check_single_file_item(self, file: FileItem) -> None: - """Check single file item + """Check single file item. The arguments are the same that are documented in _check_files @@ -1103,7 +1103,7 @@ def _check_files( get_ast, file_descrs: Iterable[FileItem], ) -> None: - """Check all files from file_descrs""" + """Check all files from file_descrs.""" with self._astroid_module_checker() as check_astroid_module: for file in file_descrs: try: @@ -1121,7 +1121,7 @@ def _check_files( self.add_message(symbol, args=msg) def _check_file(self, get_ast, check_astroid_module, file: FileItem): - """Check a file using the passed utility functions (get_ast and check_astroid_module) + """Check a file using the passed utility functions (get_ast and check_astroid_module). :param callable get_ast: callable returning AST from defined file taking the following arguments - filepath: path to the file to check @@ -1152,7 +1152,7 @@ def _check_file(self, get_ast, check_astroid_module, file: FileItem): @staticmethod def _get_file_descr_from_stdin(filepath: str) -> FileItem: - """Return file description (tuple of module name, file path, base name) from given file path + """Return file description (tuple of module name, file path, base name) from given file path. This method is used for creating suitable file description for _check_files when the source is standard input. @@ -1168,7 +1168,7 @@ def _get_file_descr_from_stdin(filepath: str) -> FileItem: return FileItem(modname, filepath, filepath) def _iterate_file_descrs(self, files_or_modules) -> Iterator[FileItem]: - """Return generator yielding file descriptions (tuples of module name, file path, base name) + """Return generator yielding file descriptions (tuples of module name, file path, base name). The returned generator yield one item for each Python module that should be linted. """ @@ -1178,7 +1178,7 @@ def _iterate_file_descrs(self, files_or_modules) -> Iterator[FileItem]: yield FileItem(name, filepath, descr["basename"]) def _expand_files(self, modules) -> List[ModuleDescriptionDict]: - """get modules and errors from a list of modules and handle errors""" + """Get modules and errors from a list of modules and handle errors.""" result, errors = expand_modules( modules, self.config.black_list, @@ -1195,7 +1195,7 @@ def _expand_files(self, modules) -> List[ModuleDescriptionDict]: return result def set_current_module(self, modname, filepath: Optional[str] = None): - """set the name of the currently analyzed module and + """Set the name of the currently analyzed module and init statistics for it """ if not modname and filepath is None: @@ -1216,7 +1216,7 @@ def set_current_module(self, modname, filepath: Optional[str] = None): @contextlib.contextmanager def _astroid_module_checker(self): - """Context manager for checking ASTs + """Context manager for checking ASTs. The value in the context is callable accepting AST as its only argument. """ @@ -1307,7 +1307,7 @@ def check_astroid_module(self, ast_node, walker, rawcheckers, tokencheckers): def _check_astroid_module( self, node: nodes.Module, walker, rawcheckers, tokencheckers ): - """Check given AST node with given walker and checkers + """Check given AST node with given walker and checkers. :param astroid.nodes.Module node: AST node of the module to check :param pylint.utils.ast_walker.ASTWalker walker: AST walker @@ -1347,7 +1347,7 @@ def _check_astroid_module( # IAstroidChecker interface ################################################# def open(self): - """initialize counters""" + """Initialize counters.""" self.stats = LinterStats() MANAGER.always_load_extensions = self.config.unsafe_load_any_extension MANAGER.max_inferable_values = self.config.limit_inference_results @@ -1360,7 +1360,7 @@ def open(self): self._ignore_paths = get_global_option(self, "ignore-paths") def generate_reports(self): - """close the whole package /module, it's time to make reports ! + """Close the whole package /module, it's time to make reports ! if persistent run, pickle results for later comparison """ @@ -1388,7 +1388,7 @@ def generate_reports(self): return score_value def _report_evaluation(self): - """make the global evaluation report""" + """Make the global evaluation report.""" # check with at least check 1 statements (usually 0 when there is a # syntax error preventing pylint from further processing) note = None @@ -1445,7 +1445,7 @@ def _get_message_state_scope( return None def _is_one_message_enabled(self, msgid: str, line: Optional[int]) -> bool: - """Checks state of a single message for the current file + """Checks state of a single message for the current file. This function can't be cached as it depends on self.file_state which can change. @@ -1635,7 +1635,7 @@ def add_ignored_message( node: Optional[nodes.NodeNG] = None, confidence: Optional[interfaces.Confidence] = interfaces.UNDEFINED, ) -> None: - """Prepares a message to be added to the ignored message storage + """Prepares a message to be added to the ignored message storage. Some checks return early in special cases and never reach add_message(), even though they would normally issue a message. @@ -1656,7 +1656,7 @@ def add_ignored_message( # Setting the state (disabled/enabled) of messages and registering them def _message_symbol(self, msgid: str) -> List[str]: - """Get the message symbol of the given message id + """Get the message symbol of the given message id. Return the original message id if the message does not exist. @@ -1669,7 +1669,7 @@ def _message_symbol(self, msgid: str) -> List[str]: def _set_one_msg_status( self, scope: str, msg: MessageDefinition, line: Optional[int], enable: bool ) -> None: - """Set the status of an individual message""" + """Set the status of an individual message.""" if scope == "module": assert isinstance(line, int) # should always be int inside module scope @@ -1740,7 +1740,7 @@ def _set_msg_status( line: Optional[int] = None, ignore_unknown: bool = False, ) -> None: - """Do some tests and then iterate over message definitions to set state""" + """Do some tests and then iterate over message definitions to set state.""" assert scope in {"package", "module"} message_definitions = self._get_messages_to_set(msgid, enable, ignore_unknown) @@ -1782,7 +1782,7 @@ def disable( line: Optional[int] = None, ignore_unknown: bool = False, ) -> None: - """Disable a message for a scope""" + """Disable a message for a scope.""" self._set_msg_status( msgid, enable=False, scope=scope, line=line, ignore_unknown=ignore_unknown ) @@ -1795,7 +1795,7 @@ def disable_next( line: Optional[int] = None, ignore_unknown: bool = False, ) -> None: - """Disable a message for the next line""" + """Disable a message for the next line.""" if not line: raise exceptions.NoLineSuppliedError self._set_msg_status( @@ -1814,7 +1814,7 @@ def enable( line: Optional[int] = None, ignore_unknown: bool = False, ) -> None: - """Enable a message for a scope""" + """Enable a message for a scope.""" self._set_msg_status( msgid, enable=True, scope=scope, line=line, ignore_unknown=ignore_unknown ) diff --git a/pylint/lint/report_functions.py b/pylint/lint/report_functions.py index 5e95354c45..0f5e9360db 100644 --- a/pylint/lint/report_functions.py +++ b/pylint/lint/report_functions.py @@ -14,7 +14,7 @@ def report_total_messages_stats( stats: LinterStats, previous_stats: LinterStats, ): - """make total errors / warnings report""" + """Make total errors / warnings report.""" lines = ["type", "number", "previous", "difference"] lines += checkers.table_lines_from_stats(stats, previous_stats, "message_types") sect.append(Table(children=lines, cols=4, rheaders=1)) @@ -25,7 +25,7 @@ def report_messages_stats( stats: LinterStats, _: LinterStats, ): - """make messages type report""" + """Make messages type report.""" by_msg_stats = stats.by_msg in_order = sorted( (value, msg_id) @@ -44,7 +44,7 @@ def report_messages_by_module_stats( stats: LinterStats, _: LinterStats, ): - """make errors / warnings by modules report""" + """Make errors / warnings by modules report.""" module_stats = stats.by_module if len(module_stats) == 1: # don't print this report when we are analysing a single module diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 0adba07236..25311024a2 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -31,7 +31,7 @@ def _cpu_count() -> int: def cb_list_extensions(option, optname, value, parser): - """List all the extensions under pylint.extensions""" + """List all the extensions under pylint.extensions.""" for filename in os.listdir(os.path.dirname(extensions.__file__)): if filename.endswith(".py") and not filename.startswith("_"): @@ -47,7 +47,7 @@ def cb_list_confidence_levels(option, optname, value, parser): def cb_init_hook(optname, value): - """Execute arbitrary code to set 'sys.path' for instance""" + """Execute arbitrary code to set 'sys.path' for instance.""" exec(value) # pylint: disable=exec-used @@ -55,7 +55,7 @@ def cb_init_hook(optname, value): class Run: - """helper class to use as main for pylint : + """Helper class to use as main for pylint :. run(*sys.argv[1:]) """ @@ -385,23 +385,23 @@ def __init__( sys.exit(self.linter.msg_status) def version_asked(self, _, __): - """callback for version (i.e. before option parsing)""" + """Callback for version (i.e. before option parsing).""" self._version_asked = True def cb_set_rcfile(self, name, value): - """callback for option preprocessing (i.e. before option parsing)""" + """Callback for option preprocessing (i.e. before option parsing).""" self._rcfile = value def cb_set_output(self, name, value): - """callback for option preprocessing (i.e. before option parsing)""" + """Callback for option preprocessing (i.e. before option parsing).""" self._output = value def cb_add_plugins(self, name, value): - """callback for option preprocessing (i.e. before option parsing)""" + """Callback for option preprocessing (i.e. before option parsing).""" self._plugins.extend(utils._splitstrip(value)) def cb_error_mode(self, *args, **kwargs): - """error mode: + """Error mode: * disable all but error messages * disable the 'miscellaneous' checker which can be safely deactivated in debug @@ -411,37 +411,37 @@ def cb_error_mode(self, *args, **kwargs): self.linter.error_mode() def cb_generate_config(self, *args, **kwargs): - """optik callback for sample config file generation""" + """Optik callback for sample config file generation.""" self.linter.generate_config(skipsections=("COMMANDS",)) sys.exit(0) def cb_generate_manpage(self, *args, **kwargs): - """optik callback for sample config file generation""" + """Optik callback for sample config file generation.""" self.linter.generate_manpage(__pkginfo__) sys.exit(0) def cb_help_message(self, option, optname, value, parser): - """optik callback for printing some help about a particular message""" + """Optik callback for printing some help about a particular message.""" self.linter.msgs_store.help_message(utils._splitstrip(value)) sys.exit(0) def cb_full_documentation(self, option, optname, value, parser): - """optik callback for printing full documentation""" + """Optik callback for printing full documentation.""" print_full_documentation(self.linter) sys.exit(0) def cb_list_messages(self, option, optname, value, parser): - """optik callback for printing available messages""" + """Optik callback for printing available messages.""" self.linter.msgs_store.list_messages() sys.exit(0) def cb_list_messages_enabled(self, option, optname, value, parser): - """optik callback for printing available messages""" + """Optik callback for printing available messages.""" self.linter.list_messages_enabled() sys.exit(0) def cb_list_groups(self, *args, **kwargs): - """List all the check groups that pylint knows about + """List all the check groups that pylint knows about. These should be useful to know what check groups someone can disable or enable. @@ -454,7 +454,7 @@ def cb_verbose_mode(self, *args, **kwargs): self.verbose = True def cb_enable_all_extensions(self, option_name: str, value: None) -> None: - """Callback to load and enable all available extensions""" + """Callback to load and enable all available extensions.""" for filename in os.listdir(os.path.dirname(extensions.__file__)): if filename.endswith(".py") and not filename.startswith("_"): extension_name = f"pylint.extensions.{filename[:-3]}" diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index fb1093d809..410269e9d9 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -69,7 +69,7 @@ def get_fatal_error_message(filepath: str, issue_template_path: Path) -> str: def preprocess_options(args, search_for): - """look for some options (keys of ) which have to be processed + """Look for some options (keys of ) which have to be processed before others values of are callback functions to call when the option is diff --git a/pylint/message/message.py b/pylint/message/message.py index b431d502b7..8a12d84b84 100644 --- a/pylint/message/message.py +++ b/pylint/message/message.py @@ -32,7 +32,7 @@ class Message(_MsgBase): - """This class represent a message to be issued by the reporters""" + """This class represent a message to be issued by the reporters.""" @overload def __new__( diff --git a/pylint/message/message_definition.py b/pylint/message/message_definition.py index 8785e154d8..a286f5a6d5 100644 --- a/pylint/message/message_definition.py +++ b/pylint/message/message_definition.py @@ -58,7 +58,7 @@ def __str__(self) -> str: return f"{repr(self)}:\n{self.msg} {self.description}" def may_be_emitted(self) -> bool: - """return True if message may be emitted using the current interpreter""" + """Return True if message may be emitted using the current interpreter.""" if self.minversion is not None and self.minversion > sys.version_info: return False if self.maxversion is not None and self.maxversion <= sys.version_info: @@ -66,7 +66,7 @@ def may_be_emitted(self) -> bool: return True def format_help(self, checkerref: bool = False) -> str: - """return the help string for the given message id""" + """Return the help string for the given message id.""" desc = self.description if checkerref: desc += f" This message belongs to the {self.checker_name} checker." @@ -94,7 +94,7 @@ def format_help(self, checkerref: bool = False) -> str: def check_message_definition( self, line: Optional[int], node: Optional[nodes.NodeNG] ) -> None: - """Check MessageDefinition for possible errors""" + """Check MessageDefinition for possible errors.""" if self.msgid[0] not in _SCOPE_EXEMPT: # Fatal messages and reports are special, the node/scope distinction # does not apply to them. diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py index 150c35fe83..c8b4e166a0 100644 --- a/pylint/message/message_definition_store.py +++ b/pylint/message/message_definition_store.py @@ -72,7 +72,7 @@ def get_msg_display_string(self, msgid_or_symbol: str) -> str: return repr([md.symbol for md in message_definitions]) def help_message(self, msgids_or_symbols: List[str]) -> None: - """Display help messages for the given message identifiers""" + """Display help messages for the given message identifiers.""" for msgids_or_symbol in msgids_or_symbols: try: for message_definition in self.get_message_definitions( @@ -99,7 +99,7 @@ def list_messages(self) -> None: def find_emittable_messages( self, ) -> Tuple[List[MessageDefinition], List[MessageDefinition]]: - """Finds all emittable and non-emittable messages""" + """Finds all emittable and non-emittable messages.""" messages = sorted(self._messages_definitions.values(), key=lambda m: m.msgid) emittable = [] non_emittable = [] diff --git a/pylint/pyreverse/__init__.py b/pylint/pyreverse/__init__.py index 3902445224..cce5caec3a 100644 --- a/pylint/pyreverse/__init__.py +++ b/pylint/pyreverse/__init__.py @@ -1,6 +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/main/LICENSE -"""pyreverse.extensions""" +"""Pyreverse.extensions.""" __revision__ = "$Id $" diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index 1e059dea31..44a593849c 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -19,7 +19,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""handle diagram generation options for class diagram or default diagrams""" +"""Handle diagram generation options for class diagram or default diagrams.""" from typing import Any, Optional @@ -34,24 +34,24 @@ class DiaDefGenerator: - """handle diagram generation options""" + """Handle diagram generation options.""" def __init__(self, linker, handler): - """common Diagram Handler initialization""" + """Common Diagram Handler initialization.""" self.config = handler.config self._set_default_options() self.linker = linker self.classdiagram = None # defined by subclasses def get_title(self, node): - """get title for objects""" + """Get title for objects.""" title = node.name if self.module_names: title = f"{node.root().name}.{title}" return title def _set_option(self, option): - """activate some options if not explicitly deactivated""" + """Activate some options if not explicitly deactivated.""" # if we have a class diagram, we want more information by default; # so if the option is None, we return True if option is None: @@ -59,7 +59,7 @@ def _set_option(self, option): return option def _set_default_options(self): - """set different default options with _default dictionary""" + """Set different default options with _default dictionary.""" self.module_names = self._set_option(self.config.module_names) all_ancestors = self._set_option(self.config.all_ancestors) all_associated = self._set_option(self.config.all_associated) @@ -75,22 +75,22 @@ def _set_default_options(self): self.anc_level, self.association_level = anc_level, association_level def _get_levels(self): - """help function for search levels""" + """Help function for search levels.""" return self.anc_level, self.association_level def show_node(self, node): - """true if builtins and not show_builtins""" + """True if builtins and not show_builtins.""" if self.config.show_builtin: return True return node.root().name != "builtins" def add_class(self, node): - """visit one class and add it to diagram""" + """Visit one class and add it to diagram.""" self.linker.visit(node) self.classdiagram.add_object(self.get_title(node), node) def get_ancestors(self, node, level): - """return ancestor nodes of a class node""" + """Return ancestor nodes of a class node.""" if level == 0: return for ancestor in node.ancestors(recurs=False): @@ -99,7 +99,7 @@ def get_ancestors(self, node, level): yield ancestor def get_associated(self, klass_node, level): - """return associated nodes of a class node""" + """Return associated nodes of a class node.""" if level == 0: return for association_nodes in list(klass_node.instance_attrs_type.values()) + list( @@ -113,7 +113,7 @@ def get_associated(self, klass_node, level): yield node def extract_classes(self, klass_node, anc_level, association_level): - """extract recursively classes related to klass_node""" + """Extract recursively classes related to klass_node.""" if self.classdiagram.has_node(klass_node) or not self.show_node(klass_node): return self.add_class(klass_node) @@ -126,7 +126,7 @@ def extract_classes(self, klass_node, anc_level, association_level): class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): - """generate minimum diagram definition for the project : + """Generate minimum diagram definition for the project :. * a package diagram including project's modules * a class diagram including project's classes @@ -137,7 +137,7 @@ def __init__(self, linker, handler): LocalsVisitor.__init__(self) def visit_project(self, node: Project) -> None: - """visit a pyreverse.utils.Project node + """Visit a pyreverse.utils.Project node. create a diagram definition for packages """ @@ -151,7 +151,7 @@ def visit_project(self, node: Project) -> None: self.classdiagram = ClassDiagram(f"classes {node.name}", mode) def leave_project(self, _: Project) -> Any: - """leave the pyreverse.utils.Project node + """Leave the pyreverse.utils.Project node. return the generated diagram definition """ @@ -160,7 +160,7 @@ def leave_project(self, _: Project) -> Any: return (self.classdiagram,) def visit_module(self, node: nodes.Module) -> None: - """visit an astroid.Module node + """Visit an astroid.Module node. add this class to the package diagram definition """ @@ -169,7 +169,7 @@ def visit_module(self, node: nodes.Module) -> None: self.pkgdiagram.add_object(node.name, node) def visit_classdef(self, node: nodes.ClassDef) -> None: - """visit an astroid.Class node + """Visit an astroid.Class node. add this class to the class diagram definition """ @@ -177,18 +177,18 @@ def visit_classdef(self, node: nodes.ClassDef) -> None: self.extract_classes(node, anc_level, association_level) def visit_importfrom(self, node: nodes.ImportFrom) -> None: - """visit astroid.ImportFrom and catch modules for package diagram""" + """Visit astroid.ImportFrom and catch modules for package diagram.""" if self.pkgdiagram: self.pkgdiagram.add_from_depend(node, node.modname) class ClassDiadefGenerator(DiaDefGenerator): - """generate a class diagram definition including all classes related to a + """Generate a class diagram definition including all classes related to a given class """ def class_diagram(self, project, klass): - """return a class diagram definition for the given klass and its + """Return a class diagram definition for the given klass and its related klasses """ @@ -210,7 +210,7 @@ def class_diagram(self, project, klass): class DiadefsHandler: - """handle diagram definitions : + """Handle diagram definitions :. get it from user (i.e. xml files) or generate them """ @@ -219,7 +219,7 @@ def __init__(self, config): self.config = config def get_diadefs(self, project, linker): - """Get the diagram's configuration data + """Get the diagram's configuration data. :param project:The pyreverse project :type project: pyreverse.utils.Project diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 5fb7cb9dd9..fa88e38165 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -16,7 +16,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""diagram objects""" +"""Diagram objects.""" import astroid from astroid import nodes @@ -25,11 +25,11 @@ class Figure: - """base class for counter handling""" + """Base class for counter handling.""" class Relationship(Figure): - """A relationship from an object in the diagram to another""" + """A relationship from an object in the diagram to another.""" def __init__(self, from_object, to_object, relation_type, name=None): super().__init__() @@ -40,7 +40,7 @@ def __init__(self, from_object, to_object, relation_type, name=None): class DiagramEntity(Figure): - """a diagram object, i.e. a label associated to an astroid node""" + """A diagram object, i.e. a label associated to an astroid node.""" def __init__(self, title="No name", node=None): super().__init__() @@ -49,11 +49,11 @@ def __init__(self, title="No name", node=None): class PackageEntity(DiagramEntity): - """A diagram object representing a package""" + """A diagram object representing a package.""" class ClassEntity(DiagramEntity): - """A diagram object representing a class""" + """A diagram object representing a class.""" def __init__(self, title, node): super().__init__(title=title, node=node) @@ -62,7 +62,7 @@ def __init__(self, title, node): class ClassDiagram(Figure, FilterMixIn): - """main class diagram handling""" + """Main class diagram handling.""" TYPE = "class" @@ -83,19 +83,19 @@ def get_relationships(self, role): ) def add_relationship(self, from_object, to_object, relation_type, name=None): - """create a relationship""" + """Create a relationship.""" rel = Relationship(from_object, to_object, relation_type, name) self.relationships.setdefault(relation_type, []).append(rel) def get_relationship(self, from_object, relation_type): - """return a relationship or None""" + """Return a relationship or None.""" for rel in self.relationships.get(relation_type, ()): if rel.from_object is from_object: return rel raise KeyError(relation_type) def get_attrs(self, node): - """return visible attributes, possibly with class name""" + """Return visible attributes, possibly with class name.""" attrs = [] properties = [ (n, m) @@ -116,7 +116,7 @@ def get_attrs(self, node): return sorted(attrs) def get_methods(self, node): - """return visible methods""" + """Return visible methods.""" methods = [ m for m in node.values() @@ -128,14 +128,14 @@ def get_methods(self, node): return sorted(methods, key=lambda n: n.name) def add_object(self, title, node): - """create a diagram object""" + """Create a diagram object.""" assert node not in self._nodes ent = DiagramEntity(title, node) self._nodes[node] = ent self.objects.append(ent) def class_names(self, nodes_lst): - """return class names if needed in diagram""" + """Return class names if needed in diagram.""" names = [] for node in nodes_lst: if isinstance(node, astroid.Instance): @@ -151,30 +151,30 @@ def class_names(self, nodes_lst): return names def nodes(self): - """return the list of underlying nodes""" + """Return the list of underlying nodes.""" return self._nodes.keys() def has_node(self, node): - """return true if the given node is included in the diagram""" + """Return true if the given node is included in the diagram.""" return node in self._nodes def object_from_node(self, node): - """return the diagram object mapped to node""" + """Return the diagram object mapped to node.""" return self._nodes[node] def classes(self): - """return all class nodes in the diagram""" + """Return all class nodes in the diagram.""" return [o for o in self.objects if isinstance(o.node, nodes.ClassDef)] def classe(self, name): - """return a class by its name, raise KeyError if not found""" + """Return a class by its name, raise KeyError if not found.""" for klass in self.classes(): if klass.node.name == name: return klass raise KeyError(name) def extract_relationships(self): - """Extract relationships between nodes in the diagram""" + """Extract relationships between nodes in the diagram.""" for obj in self.classes(): node = obj.node obj.attrs = self.get_attrs(node) @@ -215,23 +215,23 @@ def extract_relationships(self): class PackageDiagram(ClassDiagram): - """package diagram handling""" + """Package diagram handling.""" TYPE = "package" def modules(self): - """return all module nodes in the diagram""" + """Return all module nodes in the diagram.""" return [o for o in self.objects if isinstance(o.node, nodes.Module)] def module(self, name): - """return a module by its name, raise KeyError if not found""" + """Return a module by its name, raise KeyError if not found.""" for mod in self.modules(): if mod.node.name == name: return mod raise KeyError(name) def get_module(self, name, node): - """return a module by its name, looking also for relative imports; + """Return a module by its name, looking also for relative imports; raise KeyError if not found """ for mod in self.modules(): @@ -247,14 +247,14 @@ def get_module(self, name, node): raise KeyError(name) def add_from_depend(self, node, from_module): - """add dependencies created by from-imports""" + """Add dependencies created by from-imports.""" mod_name = node.root().name obj = self.module(mod_name) if from_module not in obj.node.depends: obj.node.depends.append(from_module) def extract_relationships(self): - """Extract relationships between nodes in the diagram""" + """Extract relationships between nodes in the diagram.""" super().extract_relationships() for obj in self.classes(): # ownership diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index 59865a0ab5..ee19a5b659 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -51,7 +51,7 @@ def __init__( super().__init__(title, layout, use_automatic_namespace) def _open_graph(self) -> None: - """Emit the header lines""" + """Emit the header lines.""" self.emit(f'digraph "{self.title}" {{') if self.layout: self.emit(f"rankdir={self.layout.value}") diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index d3774389e6..61c8d1f7ea 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -71,17 +71,17 @@ def __init__(self, start_value=0): self.id_count = start_value def init_counter(self, start_value=0): - """init the id counter""" + """Init the id counter.""" self.id_count = start_value def generate_id(self): - """generate a new identifier""" + """Generate a new identifier.""" self.id_count += 1 return self.id_count class Project: - """a project handle a set of modules / packages""" + """A project handle a set of modules / packages.""" def __init__(self, name=""): self.name = name @@ -143,7 +143,7 @@ def __init__(self, project, inherited_interfaces=0, tag=False): self.project = project def visit_project(self, node: Project) -> None: - """visit a pyreverse.utils.Project node + """Visit a pyreverse.utils.Project node. * optionally tag the node with a unique id """ @@ -153,7 +153,7 @@ def visit_project(self, node: Project) -> None: self.visit(module) def visit_module(self, node: nodes.Module) -> None: - """visit an astroid.Module node + """Visit an astroid.Module node. * set the locals_type mapping * set the depends mapping @@ -167,7 +167,7 @@ def visit_module(self, node: nodes.Module) -> None: node.uid = self.generate_id() def visit_classdef(self, node: nodes.ClassDef) -> None: - """visit an astroid.Class node + """Visit an astroid.Class node. * set the locals_type and instance_attrs_type mappings * set the implements list and build it @@ -196,7 +196,7 @@ def visit_classdef(self, node: nodes.ClassDef) -> None: node.implements = [] def visit_functiondef(self, node: nodes.FunctionDef) -> None: - """visit an astroid.Function node + """Visit an astroid.Function node. * set the locals_type mapping * optionally tag the node with a unique id @@ -213,7 +213,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: link_function = visit_functiondef def visit_assignname(self, node: nodes.AssignName) -> None: - """visit an astroid.AssignName node + """Visit an astroid.AssignName node. handle locals_type """ @@ -244,7 +244,7 @@ def visit_assignname(self, node: nodes.AssignName) -> None: @staticmethod def handle_assignattr_type(node, parent): - """handle an astroid.assignattr node + """Handle an astroid.assignattr node. handle instance_attrs_type """ @@ -254,7 +254,7 @@ def handle_assignattr_type(node, parent): ) def visit_import(self, node: nodes.Import) -> None: - """visit an astroid.Import node + """Visit an astroid.Import node. resolve module dependencies """ @@ -264,7 +264,7 @@ def visit_import(self, node: nodes.Import) -> None: self._imported_module(node, name[0], relative) def visit_importfrom(self, node: nodes.ImportFrom) -> None: - """visit an astroid.ImportFrom node + """Visit an astroid.ImportFrom node. resolve module dependencies """ @@ -288,7 +288,7 @@ def visit_importfrom(self, node: nodes.ImportFrom) -> None: self._imported_module(node, fullname, relative) def compute_module(self, context_name, mod_path): - """return true if the module should be added to dependencies""" + """Return true if the module should be added to dependencies.""" package_dir = os.path.dirname(self.project.path) if context_name == mod_path: return 0 @@ -297,7 +297,7 @@ def compute_module(self, context_name, mod_path): return 0 def _imported_module(self, node, mod_path, relative): - """Notify an imported module, used to analyze dependencies""" + """Notify an imported module, used to analyze dependencies.""" module = node.root() context_name = module.name if relative: @@ -314,7 +314,7 @@ def _imported_module(self, node, mod_path, relative): def project_from_files( files, func_wrapper=_astroid_wrapper, project_name="no name", black_list=("CVS",) ): - """return a Project from a list of files or modules""" + """Return a Project from a list of files or modules.""" # build the project representation astroid_manager = astroid.manager.AstroidManager() project = Project(project_name) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index f96c07a596..e955388cb0 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -20,7 +20,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""%prog [options] +"""%prog [options] . create UML diagrams for classes and modules in """ @@ -197,7 +197,7 @@ class Run(ConfigurationMixIn): - """base class providing common behaviour for pyreverse commands""" + """Base class providing common behaviour for pyreverse commands.""" options = OPTIONS @@ -218,7 +218,7 @@ def __init__(self, args: Iterable[str]): sys.exit(self.run(args)) def run(self, args): - """checking arguments and run project""" + """Checking arguments and run project.""" if not args: print(self.help()) return 1 diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index bf12fa2ec3..0140419931 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -3,7 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Class to generate files in mermaidjs format""" +"""Class to generate files in mermaidjs format.""" from typing import Dict, Optional from pylint.pyreverse.printer import EdgeType, NodeProperties, NodeType, Printer @@ -11,7 +11,7 @@ class MermaidJSPrinter(Printer): - """Printer for MermaidJS diagrams""" + """Printer for MermaidJS diagrams.""" DEFAULT_COLOR = "black" @@ -28,7 +28,7 @@ class MermaidJSPrinter(Printer): } def _open_graph(self) -> None: - """Emit the header lines""" + """Emit the header lines.""" self.emit("classDiagram") self._inc_indent() @@ -82,7 +82,7 @@ def _close_graph(self) -> None: class HTMLMermaidJSPrinter(MermaidJSPrinter): - """Printer for MermaidJS diagrams wrapped in a html boilerplate""" + """Printer for MermaidJS diagrams wrapped in a html boilerplate.""" HTML_OPEN_BOILERPLATE = """ diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index be49c51eba..5693e626ff 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -11,7 +11,7 @@ class PlantUmlPrinter(Printer): - """Printer for PlantUML diagrams""" + """Printer for PlantUML diagrams.""" DEFAULT_COLOR = "black" @@ -28,7 +28,7 @@ class PlantUmlPrinter(Printer): } def _open_graph(self) -> None: - """Emit the header lines""" + """Emit the header lines.""" self.emit("@startuml " + self.title) if not self.use_automatic_namespace: self.emit("set namespaceSeparator none") diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index 0fdee79fd4..559e456be3 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -46,7 +46,7 @@ class NodeProperties(NamedTuple): class Printer(ABC): - """Base class defining the interface for a printer""" + """Base class defining the interface for a printer.""" def __init__( self, @@ -62,11 +62,11 @@ def __init__( self._open_graph() def _inc_indent(self) -> None: - """increment indentation""" + """Increment indentation.""" self._indent += " " def _dec_indent(self) -> None: - """decrement indentation""" + """Decrement indentation.""" self._indent = self._indent[:-2] @abstractmethod diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 594c52c5b6..5cb4138e74 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -19,7 +19,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Generic classes/functions for pyreverse core/extensions. """ +"""Generic classes/functions for pyreverse core/extensions.""" import os import re import shutil @@ -47,7 +47,7 @@ def get_default_options(): def insert_default_options(): - """insert default options to sys.argv""" + """Insert default options to sys.argv.""" options = get_default_options() options.reverse() for arg in options: @@ -61,7 +61,7 @@ def insert_default_options(): def get_visibility(name): - """return the visibility from a name: public, protected, private or special""" + """Return the visibility from a name: public, protected, private or special.""" if SPECIAL.match(name): visibility = "special" elif PRIVATE.match(name): @@ -79,14 +79,14 @@ def get_visibility(name): def is_abstract(node): - """return true if the given class node correspond to an abstract class + """Return true if the given class node correspond to an abstract class definition """ return ABSTRACT.match(node.name) def is_final(node): - """return true if the given class/function node correspond to final + """Return true if the given class/function node correspond to final definition """ return FINAL.match(node.name) @@ -123,10 +123,10 @@ def is_exception(node): class FilterMixIn: - """filter nodes according to a mode and nodes' visibility""" + """Filter nodes according to a mode and nodes' visibility.""" def __init__(self, mode): - "init filter modes" + """Init filter modes.""" __mode = 0 for nummod in mode.split("+"): try: @@ -136,13 +136,13 @@ def __init__(self, mode): self.__mode = __mode def show_attr(self, node): - """return true if the node should be treated""" + """Return true if the node should be treated.""" visibility = get_visibility(getattr(node, "name", node)) return not self.__mode & VIS_MOD[visibility] class ASTWalker: - """a walker visiting a tree in preorder, calling on the handler: + """A walker visiting a tree in preorder, calling on the handler:. * visit_ on entering a node, where class name is the class of the node in lower case @@ -156,7 +156,7 @@ def __init__(self, handler): self._cache = {} def walk(self, node, _done=None): - """walk on the tree from , getting callbacks from handler""" + """Walk on the tree from , getting callbacks from handler.""" if _done is None: _done = set() if node in _done: @@ -170,7 +170,7 @@ def walk(self, node, _done=None): assert node.parent is not node def get_callbacks(self, node): - """get callbacks from handler for the visited node""" + """Get callbacks from handler for the visited node.""" klass = node.__class__ methods = self._cache.get(klass) if methods is None: @@ -188,27 +188,27 @@ def get_callbacks(self, node): return e_method, l_method def visit(self, node): - """walk on the tree from , getting callbacks from handler""" + """Walk on the tree from , getting callbacks from handler.""" method = self.get_callbacks(node)[0] if method is not None: method(node) def leave(self, node): - """walk on the tree from , getting callbacks from handler""" + """Walk on the tree from , getting callbacks from handler.""" method = self.get_callbacks(node)[1] if method is not None: method(node) class LocalsVisitor(ASTWalker): - """visit a project by traversing the locals dictionary""" + """Visit a project by traversing the locals dictionary.""" def __init__(self): super().__init__(self) self._visited = set() def visit(self, node): - """launch the visit starting from the given node""" + """Launch the visit starting from the given node.""" if node in self._visited: return None @@ -236,7 +236,7 @@ def get_annotation_label(ann: Union[nodes.Name, nodes.Subscript]) -> str: def get_annotation( node: Union[nodes.AssignAttr, nodes.AssignName] ) -> Optional[Union[nodes.Name, nodes.Subscript]]: - """return the annotation for `node`""" + """Return the annotation for `node`.""" ann = None if isinstance(node.parent, nodes.AnnAssign): ann = node.parent.annotation diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index ccaa553212..f7e2a46652 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -187,7 +187,7 @@ class VCGPrinter(Printer): def _open_graph(self) -> None: - """Emit the header lines""" + """Emit the header lines.""" self.emit("graph:{\n") self._inc_indent() self._write_attributes( @@ -269,7 +269,7 @@ def emit_edge( self.emit("}") def _write_attributes(self, attributes_dict: Mapping[str, Any], **args) -> None: - """write graph, node or edge attributes""" + """Write graph, node or edge attributes.""" for key, value in args.items(): try: _type = attributes_dict[key] diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index fa2ce18362..86a03ac7e6 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -16,7 +16,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Utilities for creating VCG and Dot diagrams""" +"""Utilities for creating VCG and Dot diagrams.""" import itertools import os @@ -36,7 +36,7 @@ class DiagramWriter: - """base class for writing project diagrams""" + """Base class for writing project diagrams.""" def __init__(self, config): self.config = config @@ -68,7 +68,7 @@ def __init__(self, config): self.used_colors = {} def write(self, diadefs): - """write files for according to """ + """Write files for according to .""" for diagram in diadefs: basename = diagram.title.strip().replace(" ", "_") file_name = f"{basename}.{self.config.output_format}" @@ -82,7 +82,7 @@ def write(self, diadefs): self.save() def write_packages(self, diagram: PackageDiagram) -> None: - """write a package diagram""" + """Write a package diagram.""" # sorted to get predictable (hence testable) results for module in sorted(diagram.modules(), key=lambda x: x.title): module.fig_id = module.node.qname() @@ -100,7 +100,7 @@ def write_packages(self, diagram: PackageDiagram) -> None: ) def write_classes(self, diagram: ClassDiagram) -> None: - """write a class diagram""" + """Write a class diagram.""" # sorted to get predictable (hence testable) results for obj in sorted(diagram.objects, key=lambda x: x.title): obj.fig_id = obj.node.qname() @@ -132,19 +132,19 @@ def write_classes(self, diagram: ClassDiagram) -> None: ) def set_printer(self, file_name: str, basename: str) -> None: - """set printer""" + """Set printer.""" self.printer = self.printer_class(basename) self.file_name = file_name def get_package_properties(self, obj: PackageEntity) -> NodeProperties: - """get label and shape for packages.""" + """Get label and shape for packages.""" return NodeProperties( label=obj.title, color=self.get_shape_color(obj) if self.config.colorized else "black", ) def get_class_properties(self, obj: ClassEntity) -> NodeProperties: - """get label and shape for classes.""" + """Get label and shape for classes.""" properties = NodeProperties( label=obj.title, attrs=obj.attrs if not self.config.only_classnames else None, @@ -155,7 +155,7 @@ def get_class_properties(self, obj: ClassEntity) -> NodeProperties: return properties def get_shape_color(self, obj: DiagramEntity) -> str: - """get shape color""" + """Get shape color.""" qualified_name = obj.node.qname() if modutils.is_standard_module(qualified_name.split(".", maxsplit=1)[0]): return "grey" @@ -171,5 +171,5 @@ def get_shape_color(self, obj: DiagramEntity) -> str: return self.used_colors[base_name] def save(self) -> None: - """write to disk""" + """Write to disk.""" self.printer.generate(self.file_name) diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 420154d54c..92b6bae21e 100644 --- a/pylint/reporters/__init__.py +++ b/pylint/reporters/__init__.py @@ -21,7 +21,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""utilities methods and classes for reporters""" +"""Utilities methods and classes for reporters.""" from typing import TYPE_CHECKING from pylint import utils @@ -36,7 +36,7 @@ def initialize(linter: "PyLinter") -> None: - """initialize linter with reporters in this package""" + """Initialize linter with reporters in this package.""" utils.register_plugins(linter, __path__[0]) diff --git a/pylint/reporters/base_reporter.py b/pylint/reporters/base_reporter.py index 7ee55beffa..570175567b 100644 --- a/pylint/reporters/base_reporter.py +++ b/pylint/reporters/base_reporter.py @@ -16,7 +16,7 @@ class BaseReporter: - """base class for reporters + """Base class for reporters. symbols: show short symbolic names for messages. """ @@ -24,7 +24,7 @@ class BaseReporter: extension = "" name = "base" - """Name of the reporter""" + """Name of the reporter.""" def __init__(self, output: Optional[TextIO] = None) -> None: self.linter: "PyLinter" @@ -39,7 +39,7 @@ def handle_message(self, msg: Message) -> None: self.messages.append(msg) def set_output(self, output: Optional[TextIO] = None) -> None: - """set output stream""" + """Set output stream.""" # pylint: disable-next=fixme # TODO: Remove this method after depreciation warn( @@ -49,11 +49,11 @@ def set_output(self, output: Optional[TextIO] = None) -> None: self.out = output or sys.stdout def writeln(self, string: str = "") -> None: - """write a line in the output buffer""" + """Write a line in the output buffer.""" print(string, file=self.out) def display_reports(self, layout: "Section") -> None: - """display results encapsulated in the layout tree""" + """Display results encapsulated in the layout tree.""" self.section = 0 if layout.report_id: if isinstance(layout.children[0].children[0], Text): @@ -63,11 +63,11 @@ def display_reports(self, layout: "Section") -> None: self._display(layout) def _display(self, layout: "Section") -> None: - """display the layout""" + """Display the layout.""" raise NotImplementedError() def display_messages(self, layout: Optional["Section"]) -> None: - """Hook for displaying the messages of the reporter + """Hook for displaying the messages of the reporter. This will be called whenever the underlying messages needs to be displayed. For some reporters, it probably diff --git a/pylint/reporters/collecting_reporter.py b/pylint/reporters/collecting_reporter.py index 70f313bb35..9b787342af 100644 --- a/pylint/reporters/collecting_reporter.py +++ b/pylint/reporters/collecting_reporter.py @@ -9,7 +9,7 @@ class CollectingReporter(BaseReporter): - """collects messages""" + """Collects messages.""" name = "collector" diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index 8761979aa5..5c20747735 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -12,7 +12,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""JSON reporter""" +"""JSON reporter.""" import json from typing import TYPE_CHECKING, Optional @@ -32,7 +32,7 @@ class JSONReporter(BaseReporter): extension = "json" def display_messages(self, layout: Optional["Section"]) -> None: - """Launch layouts display""" + """Launch layouts display.""" json_dumpable = [ { "type": msg.category, diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index 61740908dd..a68c8c423d 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -18,7 +18,7 @@ class MultiReporter: - """Reports messages and layouts in plain text""" + """Reports messages and layouts in plain text.""" __implements__ = IReporter name = "_internal_multi_reporter" @@ -80,22 +80,22 @@ def handle_message(self, msg: Message) -> None: rep.handle_message(msg) def writeln(self, string: str = "") -> None: - """write a line in the output buffer""" + """Write a line in the output buffer.""" for rep in self._sub_reporters: rep.writeln(string) def display_reports(self, layout: "Section") -> None: - """display results encapsulated in the layout tree""" + """Display results encapsulated in the layout tree.""" for rep in self._sub_reporters: rep.display_reports(layout) def display_messages(self, layout: Optional["Section"]) -> None: - """hook for displaying the messages of the reporter""" + """Hook for displaying the messages of the reporter.""" for rep in self._sub_reporters: rep.display_messages(layout) def on_set_current_module(self, module: str, filepath: Optional[str]) -> None: - """hook called when a module starts to be analysed""" + """Hook called when a module starts to be analysed.""" for rep in self._sub_reporters: rep.on_set_current_module(module, filepath) @@ -104,6 +104,6 @@ def on_close( stats: LinterStats, previous_stats: LinterStats, ) -> None: - """hook called when a module finished analyzing""" + """Hook called when a module finished analyzing.""" for rep in self._sub_reporters: rep.on_close(stats, previous_stats) diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index 245d2ded18..01d9947fd7 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -34,13 +34,13 @@ def __init__(self) -> None: self._reports_state: Dict[str, bool] = {} def report_order(self) -> MutableSequence["BaseChecker"]: - """Return a list of reporters""" + """Return a list of reporters.""" return list(self._reports) def register_report( self, reportid: str, r_title: str, r_cb: Callable, checker: "BaseChecker" ) -> None: - """Register a report + """Register a report. :param reportid: The unique identifier for the report :param r_title: The report's title @@ -51,12 +51,12 @@ def register_report( self._reports[checker].append((reportid, r_title, r_cb)) def enable_report(self, reportid: str) -> None: - """Enable the report of the given id""" + """Enable the report of the given id.""" reportid = reportid.upper() self._reports_state[reportid] = True def disable_report(self, reportid: str) -> None: - """Disable the report of the given id""" + """Disable the report of the given id.""" reportid = reportid.upper() self._reports_state[reportid] = False @@ -69,7 +69,7 @@ def make_reports( # type: ignore[misc] # ReportsHandlerMixIn is always mixed wi stats: LinterStats, old_stats: Optional[LinterStats], ) -> Section: - """Render registered reports""" + """Render registered reports.""" sect = Section("Report", f"{self.stats.statement} statements analysed.") for checker in self.report_order(): for reportid, r_title, r_cb in self._reports[checker]: diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index b0db63f11a..6743ab5b15 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -19,7 +19,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Plain text reporters: +"""Plain text reporters:. :text: the default one grouping messages by module :colorized: an ANSI colorized text reporter @@ -53,7 +53,7 @@ class MessageStyle(NamedTuple): - """Styling of a message""" + """Styling of a message.""" color: Optional[str] """The color name (see `ANSI_COLORS` for available values) @@ -93,7 +93,7 @@ class MessageStyle(NamedTuple): def _get_ansi_code(msg_style: MessageStyle) -> str: - """return ansi escape code corresponding to color and style + """Return ansi escape code corresponding to color and style. :param msg_style: the message style @@ -173,7 +173,7 @@ def colorize_ansi( class TextReporter(BaseReporter): - """Reports messages and layouts in plain text""" + """Reports messages and layouts in plain text.""" __implements__ = IReporter name = "text" @@ -185,7 +185,7 @@ def __init__(self, output: Optional[TextIO] = None) -> None: self._modules: Set[str] = set() self._template = self.line_format self._fixed_template = self.line_format - """The output format template with any unrecognized arguments removed""" + """The output format template with any unrecognized arguments removed.""" def on_set_current_module(self, module: str, filepath: Optional[str]) -> None: """Set the format template to be used and check for unrecognized arguments.""" @@ -210,7 +210,7 @@ def on_set_current_module(self, module: str, filepath: Optional[str]) -> None: self._fixed_template = template def write_message(self, msg: Message) -> None: - """Convenience method to write a formatted message with class default template""" + """Convenience method to write a formatted message with class default template.""" self_dict = msg._asdict() for key in ("end_line", "end_column"): self_dict[key] = self_dict[key] or "" @@ -218,7 +218,7 @@ def write_message(self, msg: Message) -> None: self.writeln(self._fixed_template.format(**self_dict)) def handle_message(self, msg: Message) -> None: - """manage message of different type and in the context of path""" + """Manage message of different type and in the context of path.""" if msg.module not in self._modules: if msg.module: self.writeln(f"************* Module {msg.module}") @@ -228,13 +228,13 @@ def handle_message(self, msg: Message) -> None: self.write_message(msg) def _display(self, layout: "Section") -> None: - """launch layouts display""" + """Launch layouts display.""" print(file=self.out) TextWriter().format(layout, self.out) class ParseableTextReporter(TextReporter): - """a reporter very similar to TextReporter, but display messages in a form + """A reporter very similar to TextReporter, but display messages in a form recognized by most text editors : :: @@ -252,14 +252,14 @@ def __init__(self, output: Optional[TextIO] = None) -> None: class VSTextReporter(ParseableTextReporter): - """Visual studio text reporter""" + """Visual studio text reporter.""" name = "msvs" line_format = "{path}({line}): [{msg_id}({symbol}){obj}] {msg}" class ColorizedTextReporter(TextReporter): - """Simple TextReporter that colorizes text output""" + """Simple TextReporter that colorizes text output.""" name = "colorized" COLOR_MAPPING: ColorMappingDict = { @@ -324,11 +324,11 @@ def __init__( self.out = colorama.AnsiToWin32(self.out) def _get_decoration(self, msg_id: str) -> MessageStyle: - """Returns the message style as defined in self.color_mapping""" + """Returns the message style as defined in self.color_mapping.""" return self.color_mapping.get(msg_id[0]) or MessageStyle(None) def handle_message(self, msg: Message) -> None: - """manage message of different types, and colorize output + """Manage message of different types, and colorize output using ansi escape codes """ if msg.module not in self._modules: diff --git a/pylint/reporters/ureports/base_writer.py b/pylint/reporters/ureports/base_writer.py index e87acd7eaf..dba57c86fe 100644 --- a/pylint/reporters/ureports/base_writer.py +++ b/pylint/reporters/ureports/base_writer.py @@ -30,10 +30,10 @@ class BaseWriter: - """base class for ureport writers""" + """Base class for ureport writers.""" def format(self, layout, stream: TextIO = sys.stdout, encoding=None) -> None: - """format and write the given layout into the stream object + """Format and write the given layout into the stream object. unicode policy: unicode strings may be found in the layout; try to call 'stream.write' with it, but give it back encoded using @@ -50,29 +50,29 @@ def format(self, layout, stream: TextIO = sys.stdout, encoding=None) -> None: def format_children( self, layout: Union["EvaluationSection", "Paragraph", "Section"] ) -> None: - """recurse on the layout children and call their accept method + """Recurse on the layout children and call their accept method (see the Visitor pattern) """ for child in getattr(layout, "children", ()): child.accept(self) def writeln(self, string: str = "") -> None: - """write a line in the output buffer""" + """Write a line in the output buffer.""" self.write(string + "\n") def write(self, string: str) -> None: - """write a string in the output buffer""" + """Write a string in the output buffer.""" self.out.write(string) def begin_format(self) -> None: - """begin to format a layout""" + """Begin to format a layout.""" self.section = 0 def end_format(self) -> None: - """Finished formatting a layout""" + """Finished formatting a layout.""" def get_table_content(self, table: "Table") -> List[List[str]]: - """trick to get table content without actually writing it + """Trick to get table content without actually writing it. return an aligned list of lists containing table cells values as string """ @@ -89,7 +89,7 @@ def get_table_content(self, table: "Table") -> List[List[str]]: return result def compute_content(self, layout) -> Iterator[str]: - """trick to compute the formatting of children layout before actually + """Trick to compute the formatting of children layout before actually writing it return an iterator on strings (one for each child element) diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index 280f97528e..a4e32f81e3 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -39,7 +39,7 @@ def leave(self, visitor, *args, **kwargs): class BaseLayout(VNode): - """base container node + """Base container node. attributes * children : components in this table (i.e. the table's cells) @@ -54,25 +54,25 @@ def __init__(self, children: Iterable[Union["Text", str]] = ()) -> None: self.add_text(child) def append(self, child: VNode) -> None: - """add a node to children""" + """Add a node to children.""" assert child not in self.parents() self.children.append(child) child.parent = self def insert(self, index: int, child: VNode) -> None: - """insert a child node""" + """Insert a child node.""" self.children.insert(index, child) child.parent = self def parents(self) -> List["BaseLayout"]: - """return the ancestor nodes""" + """Return the ancestor nodes.""" assert self.parent is not self if self.parent is None: return [] return [self.parent] + self.parent.parents() def add_text(self, text: str) -> None: - """shortcut to add text data""" + """Shortcut to add text data.""" self.children.append(Text(text)) @@ -80,7 +80,7 @@ def add_text(self, text: str) -> None: class Text(VNode): - """a text portion + """A text portion. attributes : * data : the text value as an encoded or unicode string @@ -93,7 +93,7 @@ def __init__(self, data: str, escaped: bool = True) -> None: class VerbatimText(Text): - """a verbatim text, display the raw data + """A verbatim text, display the raw data. attributes : * data : the text value as an encoded or unicode string @@ -104,7 +104,7 @@ class VerbatimText(Text): class Section(BaseLayout): - """a section + """A section. attributes : * BaseLayout attributes @@ -143,7 +143,7 @@ def __init__( class Title(BaseLayout): - """a title + """A title. attributes : * BaseLayout attributes @@ -153,7 +153,7 @@ class Title(BaseLayout): class Paragraph(BaseLayout): - """a simple text paragraph + """A simple text paragraph. attributes : * BaseLayout attributes @@ -163,7 +163,7 @@ class Paragraph(BaseLayout): class Table(BaseLayout): - """some tabular data + """Some tabular data. attributes : * BaseLayout attributes diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py index 392c86cc89..cb80e67713 100644 --- a/pylint/reporters/ureports/text_writer.py +++ b/pylint/reporters/ureports/text_writer.py @@ -10,7 +10,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Text formatting drivers for ureports""" +"""Text formatting drivers for ureports.""" from typing import TYPE_CHECKING, List @@ -32,7 +32,7 @@ class TextWriter(BaseWriter): - """format layouts as text + """Format layouts as text (ReStructured inspiration but not totally handled yet) """ @@ -41,7 +41,7 @@ def __init__(self): self.list_level = 0 def visit_section(self, layout: "Section") -> None: - """display a section as text""" + """Display a section as text.""" self.section += 1 self.writeln() self.format_children(layout) @@ -64,12 +64,12 @@ def visit_title(self, layout: "Title") -> None: print("FIXME TITLE TOO DEEP. TURNING TITLE INTO TEXT") def visit_paragraph(self, layout: "Paragraph") -> None: - """enter a paragraph""" + """Enter a paragraph.""" self.format_children(layout) self.writeln() def visit_table(self, layout: "Table") -> None: - """display a table as text""" + """Display a table as text.""" table_content = self.get_table_content(layout) # get columns width cols_width = [0] * len(table_content[0]) @@ -82,7 +82,7 @@ def visit_table(self, layout: "Table") -> None: def default_table( self, layout: "Table", table_content: List[List[str]], cols_width: List[int] ) -> None: - """format a table""" + """Format a table.""" cols_width = [size + 1 for size in cols_width] format_strings = " ".join(["%%-%ss"] * len(cols_width)) format_strings %= tuple(cols_width) @@ -103,12 +103,12 @@ def default_table( self.write(table_linesep) def visit_verbatimtext(self, layout: "VerbatimText") -> None: - """display a verbatim layout as text (so difficult ;)""" + """Display a verbatim layout as text (so difficult ;).""" self.writeln("::\n") for line in layout.data.splitlines(): self.writeln(" " + line) self.writeln() def visit_text(self, layout: "Text") -> None: - """add some text""" + """Add some text.""" self.write(f"{layout.data}") diff --git a/pylint/testutils/__init__.py b/pylint/testutils/__init__.py index 53e301da3d..2b392d0584 100644 --- a/pylint/testutils/__init__.py +++ b/pylint/testutils/__init__.py @@ -28,7 +28,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Functional/non regression tests for pylint""" +"""Functional/non regression tests for pylint.""" __all__ = [ "_get_tests_info", diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index 5fabf97c5b..f8324b9a9a 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -88,7 +88,7 @@ def assertAddsMessages( ) def walk(self, node): - """recursive walk on the given node""" + """Recursive walk on the given node.""" walker = ASTWalker(linter) walker.add_checker(self.checker) walker.walk(node) diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index 4e6f265bb4..b699f32428 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -55,7 +55,7 @@ def get_expected_or_default( def get_expected_configuration( configuration_path: str, default_configuration: PylintConfiguration ) -> PylintConfiguration: - """Get the expected parsed configuration of a configuration functional test""" + """Get the expected parsed configuration of a configuration functional test.""" result = copy.deepcopy(default_configuration) config_as_json = get_expected_or_default( configuration_path, suffix="result.json", default="{}" diff --git a/pylint/testutils/get_test_info.py b/pylint/testutils/get_test_info.py index 9900c7326c..32498b8ba2 100644 --- a/pylint/testutils/get_test_info.py +++ b/pylint/testutils/get_test_info.py @@ -11,7 +11,7 @@ def _get_tests_info( input_dir: str, msg_dir: str, prefix: str, suffix: str ) -> List[Tuple[str, str]]: - """get python input examples and output messages + """Get python input examples and output messages. We use following conventions for input files and messages: for different inputs: diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 9bbf00d2fa..ed20ed8604 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -259,7 +259,7 @@ def _check_output_text( expected_output: List[OutputLine], actual_output: List[OutputLine], ) -> None: - """This is a function because we want to be able to update the text in LintModuleOutputUpdate""" + """This is a function because we want to be able to update the text in LintModuleOutputUpdate.""" assert expected_output == actual_output, self.error_msg_for_unequal_output( expected_output, actual_output ) diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index 1061854bcc..e851ccfbe9 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -76,7 +76,7 @@ class OutputLine(NamedTuple): @classmethod def from_msg(cls, msg: Message, check_endline: bool = True) -> "OutputLine": - """Create an OutputLine from a Pylint Message""" + """Create an OutputLine from a Pylint Message.""" column = cls._get_column(msg.column) end_line = cls._get_py38_none_value(msg.end_line, check_endline) end_column = cls._get_py38_none_value(msg.end_column, check_endline) @@ -182,7 +182,7 @@ def to_csv(self) -> Tuple[str, str, str, str, str, str, str, str]: @staticmethod def _value_to_optional_int(value: Optional[str]) -> Optional[int]: - """Checks if a (stringified) value should be None or a Python integer""" + """Checks if a (stringified) value should be None or a Python integer.""" if value == "None" or not value: return None return int(value) diff --git a/pylint/testutils/primer.py b/pylint/testutils/primer.py index b8b33ae3e7..558ad582e0 100644 --- a/pylint/testutils/primer.py +++ b/pylint/testutils/primer.py @@ -8,25 +8,25 @@ class PackageToLint: - """Represents data about a package to be tested during primer tests""" + """Represents data about a package to be tested during primer tests.""" url: str - """URL of the repository to clone""" + """URL of the repository to clone.""" branch: str - """Branch of the repository to clone""" + """Branch of the repository to clone.""" directories: List[str] - """Directories within the repository to run pylint over""" + """Directories within the repository to run pylint over.""" commit: Optional[str] - """Commit hash to pin the repository on""" + """Commit hash to pin the repository on.""" pylint_additional_args: List[str] - """Arguments to give to pylint""" + """Arguments to give to pylint.""" pylintrc_relpath: Optional[str] - """Path relative to project's main directory to the pylintrc if it exists""" + """Path relative to project's main directory to the pylintrc if it exists.""" def __init__( self, @@ -52,13 +52,13 @@ def pylintrc(self) -> Optional[Path]: @property def clone_directory(self) -> Path: - """Directory to clone repository into""" + """Directory to clone repository into.""" clone_name = "/".join(self.url.split("/")[-2:]).replace(".git", "") return PRIMER_DIRECTORY_PATH / clone_name @property def paths_to_lint(self) -> List[str]: - """The paths we need to lint""" + """The paths we need to lint.""" return [str(self.clone_directory / path) for path in self.directories] @property @@ -70,7 +70,7 @@ def pylint_args(self) -> List[str]: return self.paths_to_lint + options + self.pylint_additional_args def lazy_clone(self) -> None: # pragma: no cover - """Concatenates the target directory and clones the file + """Concatenates the target directory and clones the file. Not expected to be tested as the primer won't work if it doesn't. It's tested in the continuous integration primers, only the coverage diff --git a/pylint/testutils/reporter_for_tests.py b/pylint/testutils/reporter_for_tests.py index 1487774068..0c2b456f30 100644 --- a/pylint/testutils/reporter_for_tests.py +++ b/pylint/testutils/reporter_for_tests.py @@ -14,7 +14,7 @@ class GenericTestReporter(BaseReporter): - """reporter storing plain text messages""" + """Reporter storing plain text messages.""" __implements__ = interfaces.IReporter out: StringIO @@ -30,11 +30,11 @@ def reset(self) -> None: self.messages: List[Message] = [] def handle_message(self, msg: Message) -> None: - """Append messages to the list of messages of the reporter""" + """Append messages to the list of messages of the reporter.""" self.messages.append(msg) def finalize(self) -> str: - """Format and print messages in the context of the path""" + """Format and print messages in the context of the path.""" messages: List[str] = [] for msg in self.messages: obj = "" @@ -56,7 +56,7 @@ def on_set_current_module(self, module: str, filepath: Optional[str]) -> None: # pylint: enable=unused-argument def display_reports(self, layout: "Section") -> None: - """ignore layouts""" + """Ignore layouts.""" def _display(self, layout: "Section") -> None: pass diff --git a/pylint/typing.py b/pylint/typing.py index 09e4df126c..84c49df558 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -12,7 +12,7 @@ class FileItem(NamedTuple): - """Represents data about a file handled by pylint + """Represents data about a file handled by pylint. Each file item has: - name: full name of the module @@ -26,7 +26,7 @@ class FileItem(NamedTuple): class ModuleDescriptionDict(TypedDict): - """Represents data about a checked module""" + """Represents data about a checked module.""" path: str name: str @@ -36,7 +36,7 @@ class ModuleDescriptionDict(TypedDict): class ErrorDescriptionDict(TypedDict): - """Represents data about errors collected during checking of a module""" + """Represents data about errors collected during checking of a module.""" key: Literal["fatal"] mod: str @@ -44,7 +44,7 @@ class ErrorDescriptionDict(TypedDict): class MessageLocationTuple(NamedTuple): - """Tuple with information about the location of a to-be-displayed message""" + """Tuple with information about the location of a to-be-displayed message.""" abspath: str path: str @@ -57,7 +57,7 @@ class MessageLocationTuple(NamedTuple): class ManagedMessage(NamedTuple): - """Tuple with information about a managed message of the linter""" + """Tuple with information about a managed message of the linter.""" name: Optional[str] msgid: str diff --git a/pylint/utils/__init__.py b/pylint/utils/__init__.py index 58072eece6..53d07825ec 100644 --- a/pylint/utils/__init__.py +++ b/pylint/utils/__init__.py @@ -40,7 +40,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""some various utilities and helper classes, most of them used in the +"""Some various utilities and helper classes, most of them used in the main pylint class """ diff --git a/pylint/utils/ast_walker.py b/pylint/utils/ast_walker.py index f846c9c21f..f9e573a95b 100644 --- a/pylint/utils/ast_walker.py +++ b/pylint/utils/ast_walker.py @@ -22,7 +22,7 @@ def _is_method_enabled(self, method): return any(self.linter.is_message_enabled(m) for m in method.checks_msgs) def add_checker(self, checker): - """walk to the checker's dir and collect visit and leave methods""" + """Walk to the checker's dir and collect visit and leave methods.""" vcids = set() lcids = set() visits = self.visit_events diff --git a/pylint/utils/docs.py b/pylint/utils/docs.py index 5b46a0bc1b..848a498684 100644 --- a/pylint/utils/docs.py +++ b/pylint/utils/docs.py @@ -1,6 +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/main/LICENSE -"""Various helper functions to create the docs of a linter object""" +"""Various helper functions to create the docs of a linter object.""" import sys from typing import TYPE_CHECKING, Dict, TextIO @@ -13,7 +13,7 @@ def _get_checkers_infos(linter: "PyLinter") -> Dict[str, Dict]: - """Get info from a checker and handle KeyError""" + """Get info from a checker and handle KeyError.""" by_checker: Dict[str, Dict] = {} for checker in linter.get_checkers(): name = checker.name @@ -34,7 +34,7 @@ def _get_checkers_infos(linter: "PyLinter") -> Dict[str, Dict]: def _get_checkers_documentation(linter: "PyLinter") -> str: - """Get documentation for individual checkers""" + """Get documentation for individual checkers.""" result = get_rst_title("Pylint global options and switches", "-") result += """ Pylint provides global options and switches. @@ -73,5 +73,5 @@ def _get_checkers_documentation(linter: "PyLinter") -> str: def print_full_documentation(linter: "PyLinter", stream: TextIO = sys.stdout) -> None: - """Output a full documentation in ReST format""" + """Output a full documentation in ReST format.""" print(_get_checkers_documentation(linter)[:-1], file=stream) diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index 4252f01542..654cb725b9 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -31,7 +31,7 @@ class FileState: - """Hold internal state specific to the currently analyzed file""" + """Hold internal state specific to the currently analyzed file.""" def __init__(self, modname: Optional[str] = None) -> None: self.base_name = modname @@ -122,7 +122,7 @@ def _collect_block_lines( del lines[lineno] def set_msg_status(self, msg: "MessageDefinition", line: int, status: bool) -> None: - """Set status (enabled/disable) for a given message at a given line""" + """Set status (enabled/disable) for a given message at a given line.""" assert line > 0 try: self._module_msgs_state[msg.msgid][line] = status diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py index 125954e4eb..54b98d533e 100644 --- a/pylint/utils/linterstats.py +++ b/pylint/utils/linterstats.py @@ -13,7 +13,7 @@ class BadNames(TypedDict): - """TypedDict to store counts of node types with bad names""" + """TypedDict to store counts of node types with bad names.""" argument: int attr: int @@ -29,7 +29,7 @@ class BadNames(TypedDict): class CodeTypeCount(TypedDict): - """TypedDict to store counts of lines of code types""" + """TypedDict to store counts of lines of code types.""" code: int comment: int @@ -39,14 +39,14 @@ class CodeTypeCount(TypedDict): class DuplicatedLines(TypedDict): - """TypedDict to store counts of lines of duplicated code""" + """TypedDict to store counts of lines of duplicated code.""" nb_duplicated_lines: int percent_duplicated_lines: float class NodeCount(TypedDict): - """TypedDict to store counts of different types of nodes""" + """TypedDict to store counts of different types of nodes.""" function: int klass: int @@ -55,7 +55,7 @@ class NodeCount(TypedDict): class UndocumentedNodes(TypedDict): - """TypedDict to store counts of undocumented node types""" + """TypedDict to store counts of undocumented node types.""" function: int klass: int @@ -64,7 +64,7 @@ class UndocumentedNodes(TypedDict): class ModuleStats(TypedDict): - """TypedDict to store counts of types of messages and statements""" + """TypedDict to store counts of types of messages and statements.""" convention: int error: int @@ -77,7 +77,7 @@ class ModuleStats(TypedDict): # pylint: disable-next=too-many-instance-attributes class LinterStats: - """Class used to linter stats""" + """Class used to linter stats.""" def __init__( self, @@ -173,13 +173,13 @@ def get_bad_names( "variable", ], ) -> int: - """Get a bad names node count""" + """Get a bad names node count.""" if node_name == "class": return self.bad_names.get("klass", 0) return self.bad_names.get(node_name, 0) def increase_bad_name(self, node_name: str, increase: int) -> None: - """Increase a bad names node count""" + """Increase a bad names node count.""" if node_name not in { "argument", "attr", @@ -217,7 +217,7 @@ def increase_bad_name(self, node_name: str, increase: int) -> None: self.bad_names[node_name] += increase def reset_bad_names(self) -> None: - """Resets the bad_names attribute""" + """Resets the bad_names attribute.""" self.bad_names = BadNames( argument=0, attr=0, @@ -235,17 +235,17 @@ def reset_bad_names(self) -> None: def get_code_count( self, type_name: Literal["code", "comment", "docstring", "empty", "total"] ) -> int: - """Get a code type count""" + """Get a code type count.""" return self.code_type_count.get(type_name, 0) def reset_code_count(self) -> None: - """Resets the code_type_count attribute""" + """Resets the code_type_count attribute.""" self.code_type_count = CodeTypeCount( code=0, comment=0, docstring=0, empty=0, total=0 ) def reset_duplicated_lines(self) -> None: - """Resets the duplicated_lines attribute""" + """Resets the duplicated_lines attribute.""" self.duplicated_lines = DuplicatedLines( nb_duplicated_lines=0, percent_duplicated_lines=0.0 ) @@ -253,47 +253,47 @@ def reset_duplicated_lines(self) -> None: def get_node_count( self, node_name: Literal["function", "class", "method", "module"] ) -> int: - """Get a node count while handling some extra conditions""" + """Get a node count while handling some extra conditions.""" if node_name == "class": return self.node_count.get("klass", 0) return self.node_count.get(node_name, 0) def reset_node_count(self) -> None: - """Resets the node count attribute""" + """Resets the node count attribute.""" self.node_count = NodeCount(function=0, klass=0, method=0, module=0) def get_undocumented( self, node_name: Literal["function", "class", "method", "module"] ) -> float: - """Get a undocumented node count""" + """Get a undocumented node count.""" if node_name == "class": return self.undocumented["klass"] return self.undocumented[node_name] def reset_undocumented(self) -> None: - """Resets the undocumented attribute""" + """Resets the undocumented attribute.""" self.undocumented = UndocumentedNodes(function=0, klass=0, method=0, module=0) def get_global_message_count(self, type_name: str) -> int: - """Get a global message count""" + """Get a global message count.""" return getattr(self, type_name, 0) def get_module_message_count(self, modname: str, type_name: str) -> int: - """Get a module message count""" + """Get a module message count.""" return getattr(self.by_module[modname], type_name, 0) def increase_single_message_count(self, type_name: str, increase: int) -> None: - """Increase the message type count of an individual message type""" + """Increase the message type count of an individual message type.""" setattr(self, type_name, getattr(self, type_name) + increase) def increase_single_module_message_count( self, modname: str, type_name: MessageTypesFullName, increase: int ) -> None: - """Increase the message type count of an individual message type of a module""" + """Increase the message type count of an individual message type of a module.""" self.by_module[modname][type_name] += increase def reset_message_count(self) -> None: - """Resets the message type count of the stats object""" + """Resets the message type count of the stats object.""" self.convention = 0 self.error = 0 self.fatal = 0 @@ -303,7 +303,7 @@ def reset_message_count(self) -> None: def merge_stats(stats: List[LinterStats]): - """Used to merge multiple stats objects into a new one when pylint is run in parallel mode""" + """Used to merge multiple stats objects into a new one when pylint is run in parallel mode.""" merged = LinterStats() for stat in stats: merged.bad_names["argument"] += stat.bad_names["argument"] diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index 5ef4ef481a..0bf25de7ce 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -61,7 +61,7 @@ def emit_pragma_representer(action: str, messages: List[str]) -> PragmaRepresent class PragmaParserError(Exception): - """A class for exceptions thrown by pragma_parser module""" + """A class for exceptions thrown by pragma_parser module.""" def __init__(self, message: str, token: str) -> None: """:args message: explain the reason why the exception has been thrown @@ -73,11 +73,11 @@ def __init__(self, message: str, token: str) -> None: class UnRecognizedOptionError(PragmaParserError): - """Thrown in case the of a valid but unrecognized option""" + """Thrown in case the of a valid but unrecognized option.""" class InvalidPragmaError(PragmaParserError): - """Thrown in case the pragma is invalid""" + """Thrown in case the pragma is invalid.""" def parse_pragma(pylint_pragma: str) -> Generator[PragmaRepresenter, None, None]: diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index f99690b33f..c3d120458e 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -98,7 +98,7 @@ def cmp(a, b): def diff_string(old, new): - """given an old and new int value, return a string representing the + """Given an old and new int value, return a string representing the difference """ diff = abs(old - new) @@ -107,7 +107,7 @@ def diff_string(old, new): def get_module_and_frameid(node): - """return the module name and the frame id in the module""" + """Return the module name and the frame id in the module.""" frame = node.frame(future=True) module, obj = "", [] while frame: @@ -129,7 +129,7 @@ def get_rst_title(title, character): def get_rst_section(section, options, doc=None): - """format an option's section using as a ReStructuredText formatted output""" + """Format an option's section using as a ReStructuredText formatted output.""" result = "" if section: result += get_rst_title(section, "'") @@ -167,7 +167,7 @@ def tokenize_module(node: nodes.Module) -> List[tokenize.TokenInfo]: def register_plugins(linter, directory): - """load all module and package in the given directory, looking for a + """Load all module and package in the given directory, looking for a 'register' function in each one, used to register pylint checkers """ imported = {} @@ -276,7 +276,7 @@ def get_global_option( def _splitstrip(string, sep=","): - """return a list of stripped string by splitting the string given as + """Return a list of stripped string by splitting the string given as argument on `sep` (',' by default). Empty string are discarded. >>> _splitstrip('a, b, c , 4,,') @@ -299,7 +299,7 @@ def _splitstrip(string, sep=","): def _unquote(string): - """remove optional quotes (simple or double) from the string + """Remove optional quotes (simple or double) from the string. :type string: str or unicode :param string: an optionally quoted string @@ -323,14 +323,14 @@ def _check_csv(value): def _comment(string: str) -> str: - """return string as a comment""" + """Return string as a comment.""" lines = [line.strip() for line in string.splitlines()] sep = "\n" return "# " + f"{sep}# ".join(lines) def _format_option_value(optdict, value): - """return the user input's value from a 'compiled' value""" + """Return the user input's value from a 'compiled' value.""" if optdict.get("type", None) == "py_version": value = ".".join(str(item) for item in value) elif isinstance(value, (list, tuple)): @@ -350,7 +350,7 @@ def _format_option_value(optdict, value): def format_section( stream: TextIO, section: str, options: List[Tuple], doc: Optional[str] = None ) -> None: - """Format an option's section using the INI format""" + """Format an option's section using the INI format.""" if doc: print(_comment(doc), file=stream) print(f"[{section}]", file=stream) @@ -358,7 +358,7 @@ def format_section( def _ini_format(stream: TextIO, options: List[Tuple]) -> None: - """format options using the INI format""" + """Format options using the INI format.""" for optname, optdict, value in options: value = _format_option_value(optdict, value) help_opt = optdict.get("help") diff --git a/script/fix_documentation.py b/script/fix_documentation.py index 36fd931f0f..0fc4e347e2 100644 --- a/script/fix_documentation.py +++ b/script/fix_documentation.py @@ -23,7 +23,7 @@ def fix_inline_code_blocks(file_content: str) -> str: def changelog_insert_empty_lines(file_content: str, subtitle_text: str) -> str: - """Insert up to two empty lines before `What's New` entry in ChangeLog""" + """Insert up to two empty lines before `What's New` entry in ChangeLog.""" lines = file_content.split("\n") subtitle_count = 0 for i, line in enumerate(lines): diff --git a/script/get_unused_message_id_category.py b/script/get_unused_message_id_category.py index 5ddcd0b3c4..2741148c04 100644 --- a/script/get_unused_message_id_category.py +++ b/script/get_unused_message_id_category.py @@ -1,4 +1,4 @@ -"""Small script to get a new unused message id category""" +"""Small script to get a new unused message id category.""" # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE @@ -10,7 +10,7 @@ def register_all_checkers_and_plugins(linter: "PyLinter") -> None: - """Registers all checkers and plugins""" + """Registers all checkers and plugins.""" linter.cmdline_parser.set_conflict_handler("resolve") initialize_checkers(linter) initialize_extensions(linter) diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index 5227ca5ae1..4865a3bd5f 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -1,4 +1,4 @@ -""" Profiles basic -jX functionality """ +"""Profiles basic -jX functionality.""" # Copyright (c) 2020-2021 Pierre Sassoulas # Copyright (c) 2020 hippo91 # Copyright (c) 2020 Claudiu Popa @@ -37,7 +37,7 @@ def _empty_filepath(): class SleepingChecker(BaseChecker): - """A checker that sleeps, the wall-clock time should reduce as we add workers + """A checker that sleeps, the wall-clock time should reduce as we add workers. As we apply a roughly constant amount of "work" in this checker any variance is likely to be caused by the pylint system. @@ -56,7 +56,7 @@ class SleepingChecker(BaseChecker): sleep_duration = 0.5 # the time to pretend we're doing work for def process_module(self, _node: nodes.Module) -> None: - """Sleeps for `sleep_duration` on each call + """Sleeps for `sleep_duration` on each call. This effectively means each file costs ~`sleep_duration`+framework overhead """ @@ -64,7 +64,7 @@ def process_module(self, _node: nodes.Module) -> None: class SleepingCheckerLong(BaseChecker): - """A checker that sleeps, the wall-clock time should reduce as we add workers + """A checker that sleeps, the wall-clock time should reduce as we add workers. As we apply a roughly constant amount of "work" in this checker any variance is likely to be caused by the pylint system. @@ -83,7 +83,7 @@ class SleepingCheckerLong(BaseChecker): sleep_duration = 0.5 # the time to pretend we're doing work for def process_module(self, _node: nodes.Module) -> None: - """Sleeps for `sleep_duration` on each call + """Sleeps for `sleep_duration` on each call. This effectively means each file costs ~`sleep_duration`+framework overhead """ @@ -91,7 +91,7 @@ def process_module(self, _node: nodes.Module) -> None: class NoWorkChecker(BaseChecker): - """A checker that sleeps, the wall-clock time should change as we add threads""" + """A checker that sleeps, the wall-clock time should change as we add threads.""" __implements__ = (pylint.interfaces.IRawChecker,) @@ -112,7 +112,7 @@ def process_module(self, _node: nodes.Module) -> None: group="baseline", ) class TestEstablishBaselineBenchmarks: - """Naive benchmarks for the high-level pylint framework + """Naive benchmarks for the high-level pylint framework. Because this benchmarks the fundamental and common parts and changes seen here will impact everything else @@ -127,7 +127,7 @@ class TestEstablishBaselineBenchmarks: lot_of_files = 500 def test_baseline_benchmark_j1(self, benchmark): - """Establish a baseline of pylint performance with no work + """Establish a baseline of pylint performance with no work. We will add extra Checkers in other benchmarks. @@ -143,7 +143,7 @@ def test_baseline_benchmark_j1(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j10(self, benchmark): - """Establish a baseline of pylint performance with no work across threads + """Establish a baseline of pylint performance with no work across threads. Same as `test_baseline_benchmark_j1` but we use -j10 with 10 fake files to ensure end-to-end-system invoked. @@ -165,7 +165,7 @@ def test_baseline_benchmark_j10(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_check_parallel_j10(self, benchmark): - """Should demonstrate times very close to `test_baseline_benchmark_j10`""" + """Should demonstrate times very close to `test_baseline_benchmark_j10`.""" linter = PyLinter(reporter=Reporter()) # Create file per worker, using all workers @@ -178,7 +178,7 @@ def test_baseline_benchmark_check_parallel_j10(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j1(self, benchmark): - """Establish a baseline with only 'master' checker being run in -j1 + """Establish a baseline with only 'master' checker being run in -j1. We do not register any checkers except the default 'master', so the cost is just that of the system with a lot of files registered @@ -197,7 +197,7 @@ def test_baseline_lots_of_files_j1(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j10(self, benchmark): - """Establish a baseline with only 'master' checker being run in -j10 + """Establish a baseline with only 'master' checker being run in -j10. As with the -j1 variant above `test_baseline_lots_of_files_j1`, we do not register any checkers except the default 'master', so the cost is just that of @@ -217,7 +217,7 @@ def test_baseline_lots_of_files_j10(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j1_empty_checker(self, benchmark): - """Baselines pylint for a single extra checker being run in -j1, for N-files + """Baselines pylint for a single extra checker being run in -j1, for N-files. We use a checker that does no work, so the cost is just that of the system at scale @@ -237,7 +237,7 @@ def test_baseline_lots_of_files_j1_empty_checker(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j10_empty_checker(self, benchmark): - """Baselines pylint for a single extra checker being run in -j10, for N-files + """Baselines pylint for a single extra checker being run in -j10, for N-files. We use a checker that does no work, so the cost is just that of the system at scale, across workers @@ -257,7 +257,7 @@ def test_baseline_lots_of_files_j10_empty_checker(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j1_single_working_checker(self, benchmark): - """Establish a baseline of single-worker performance for PyLinter + """Establish a baseline of single-worker performance for PyLinter. Here we mimic a single Checker that does some work so that we can see the impact of running a simple system with -j1 against the same system with -j10. @@ -283,7 +283,7 @@ def test_baseline_benchmark_j1_single_working_checker(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j10_single_working_checker(self, benchmark): - """Establishes baseline of multi-worker performance for PyLinter/check_parallel + """Establishes baseline of multi-worker performance for PyLinter/check_parallel. We expect this benchmark to take less time that test_baseline_benchmark_j1, `error_margin*(1/J)*(numfiles*SleepingChecker.sleep_duration)` @@ -310,7 +310,7 @@ def test_baseline_benchmark_j10_single_working_checker(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j1_all_checks_single_file(self, benchmark): - """Runs a single file, with -j1, against all plug-ins + """Runs a single file, with -j1, against all plug-ins. ... that's the intent at least. """ @@ -327,7 +327,7 @@ def test_baseline_benchmark_j1_all_checks_single_file(self, benchmark): ), f"Expected no errors to be thrown: {pprint.pformat(runner.linter.reporter.messages)}" def test_baseline_benchmark_j1_all_checks_lots_of_files(self, benchmark): - """Runs lots of files, with -j1, against all plug-ins + """Runs lots of files, with -j1, against all plug-ins. ... that's the intent at least. """ diff --git a/tests/checkers/unittest_design.py b/tests/checkers/unittest_design.py index faf8f6e9e0..b60106590e 100644 --- a/tests/checkers/unittest_design.py +++ b/tests/checkers/unittest_design.py @@ -47,7 +47,7 @@ class Eeee(Dddd): @set_config(exclude_too_few_public_methods="toml.*") def test_exclude_too_few_methods_with_value(self) -> None: - """Test exclude-too-few-public-methods option with value""" + """Test exclude-too-few-public-methods option with value.""" options = get_global_option(self.checker, "exclude-too-few-public-methods") assert any(i.match("toml") for i in options) diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 8aa7ec0c78..30972cb5c7 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -26,7 +26,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Check format checker helper functions""" +"""Check format checker helper functions.""" import os import tempfile @@ -120,7 +120,7 @@ def testNoSuperfluousParensWalrusOperatorIf(self) -> None: self.checker.process_tokens(_tokenize_str(code)) def testPositiveSuperfluousParensWalrusOperatorIf(self) -> None: - """Test positive superfluous parens cases with the walrus operator""" + """Test positive superfluous parens cases with the walrus operator.""" cases = [ ( MessageTest("superfluous-parens", line=1, args="if"), @@ -162,7 +162,7 @@ class TestCheckSpace(CheckerTestCase): CHECKER_CLASS = FormatChecker def test_encoding_token(self) -> None: - """Make sure the encoding token doesn't change the checker's behavior + """Make sure the encoding token doesn't change the checker's behavior. _tokenize_str doesn't produce an encoding token, but reading a file does diff --git a/tests/checkers/unittest_non_ascii_name.py b/tests/checkers/unittest_non_ascii_name.py index d0e3a85ab7..4d69a65194 100644 --- a/tests/checkers/unittest_non_ascii_name.py +++ b/tests/checkers/unittest_non_ascii_name.py @@ -18,7 +18,7 @@ class TestNonAsciiChecker(pylint.testutils.CheckerTestCase): sys.version_info < (3, 8), reason="requires python3.8 or higher" ) def test_kwargs_and_position_only(self): - """Even the new position only and keyword only should be found""" + """Even the new position only and keyword only should be found.""" node = astroid.extract_node( """ def name( @@ -131,7 +131,7 @@ def test_assignname( code: str, assign_type: str, ): - """Variables defined no matter where, should be checked for non ascii""" + """Variables defined no matter where, should be checked for non ascii.""" assign_node = astroid.extract_node(code) if not isinstance(assign_node, nodes.AssignName): @@ -256,7 +256,7 @@ def test_assignname( ], ) def test_check_import(self, import_statement: str, wrong_name: Optional[str]): - """We expect that for everything that user can change there is a message""" + """We expect that for everything that user can change there is a message.""" node = astroid.extract_node(f"{import_statement} #@") expected_msgs: Iterable[pylint.testutils.MessageTest] = tuple() diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index 5caaac7946..2f2b43115e 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -394,7 +394,7 @@ def test_no_args() -> None: def test_get_map_data() -> None: - """Tests that a SimilarChecker respects the MapReduceMixin interface""" + """Tests that a SimilarChecker respects the MapReduceMixin interface.""" linter = PyLinter(reporter=Reporter()) # Add a parallel checker to ensure it can map and reduce linter.register_checker(similar.SimilarChecker(linter)) diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index cafb3b40f9..4564a461ef 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -46,7 +46,7 @@ class TestTypeChecker(CheckerTestCase): - """Tests for pylint.checkers.typecheck""" + """Tests for pylint.checkers.typecheck.""" CHECKER_CLASS = typecheck.TypeChecker diff --git a/tests/checkers/unittest_unicode/unittest_bad_chars.py b/tests/checkers/unittest_unicode/unittest_bad_chars.py index 3605ac06da..1f1e1e8d96 100644 --- a/tests/checkers/unittest_unicode/unittest_bad_chars.py +++ b/tests/checkers/unittest_unicode/unittest_bad_chars.py @@ -16,7 +16,7 @@ @pytest.fixture() def bad_char_file_generator(tmp_path: Path) -> Callable[[str, bool, str], Path]: - """generates a test file for bad chars + """Generates a test file for bad chars. The generator also ensures that file generated is correct """ @@ -253,7 +253,7 @@ def test_bad_chars_that_would_currently_crash_python( ], ) def test___check_invalid_chars(self, char: str, msg: str, codec: str) -> None: - """Check function should deliver correct column no matter which codec we used""" + """Check function should deliver correct column no matter which codec we used.""" with self.assertAddsMessages( pylint.testutils.MessageTest( msg_id=msg, diff --git a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py index c76db73ea3..2416957c92 100644 --- a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py +++ b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py @@ -75,7 +75,7 @@ def test_finds_bidirectional_unicode_that_currently_not_parsed(self): ], ) def test_find_bidi_string(self, bad_string: str, codec: str): - """Ensure that all Bidirectional strings are detected + """Ensure that all Bidirectional strings are detected. Tests also UTF-16 and UTF-32. """ diff --git a/tests/checkers/unittest_unicode/unittest_functions.py b/tests/checkers/unittest_unicode/unittest_functions.py index a84c0be42e..d14d2fdadf 100644 --- a/tests/checkers/unittest_unicode/unittest_functions.py +++ b/tests/checkers/unittest_unicode/unittest_functions.py @@ -102,7 +102,7 @@ def test_map_positions_to_result( expected: Dict[int, pylint.checkers.unicode._BadChar], search_dict, ): - """test all possible outcomes for map position function in UTF-8 and ASCII""" + """Test all possible outcomes for map position function in UTF-8 and ASCII.""" if isinstance(line, bytes): newline = b"\n" else: @@ -212,7 +212,7 @@ def test__normalize_codec_name(codec: str, expected: str): def test___fix_utf16_32_line_stream( tmp_path: Path, codec: str, line_ending: str, final_new_line: bool ): - """content of stream should be the same as should be the length""" + """Content of stream should be the same as should be the length.""" def decode_line(line: bytes, codec: str) -> str: return line.decode(codec) diff --git a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py index a2e58dc831..5facf9a059 100644 --- a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py +++ b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py @@ -121,11 +121,11 @@ def test_invalid_unicode_files(self, tmp_path: Path, test_file: str, line_no: in ], ) def test__determine_codec(self, content: bytes, codec: str, line: int): - """The codec determined should be exact no matter what we throw at it""" + """The codec determined should be exact no matter what we throw at it.""" assert self.checker._determine_codec(io.BytesIO(content)) == (codec, line) def test__determine_codec_raises_syntax_error_on_invalid_input(self): - """invalid input should lead to a SyntaxError""" + """Invalid input should lead to a SyntaxError.""" with pytest.raises(SyntaxError): self.checker._determine_codec(io.BytesIO(b"\x80abc")) diff --git a/tests/checkers/unittest_utils.py b/tests/checkers/unittest_utils.py index 58ff5ed734..1b916b98a6 100644 --- a/tests/checkers/unittest_utils.py +++ b/tests/checkers/unittest_utils.py @@ -489,7 +489,7 @@ def test_is_empty_literal() -> None: def test_deprecation_is_inside_lambda() -> None: - """Test that is_inside_lambda is throwing a DeprecationWarning""" + """Test that is_inside_lambda is throwing a DeprecationWarning.""" with pytest.warns(DeprecationWarning) as records: utils.is_inside_lambda(nodes.NodeNG()) assert len(records) == 1 diff --git a/tests/config/file_to_lint.py b/tests/config/file_to_lint.py index e7c395dd97..bc3f722d99 100644 --- a/tests/config/file_to_lint.py +++ b/tests/config/file_to_lint.py @@ -1 +1 @@ -"""Perfect module with only documentation for configuration tests""" +"""Perfect module with only documentation for configuration tests.""" diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py index 72474abe3f..fd56a3f928 100644 --- a/tests/config/unittest_config.py +++ b/tests/config/unittest_config.py @@ -89,7 +89,7 @@ class Checker(BaseChecker): @set_config(ignore_paths=".*/tests/.*,.*\\ignore\\.*") def test_ignore_paths_with_value(self) -> None: - """Test ignore-paths option with value""" + """Test ignore-paths option with value.""" options = get_global_option(self.checker, "ignore-paths") assert any(i.match("dir/tests/file.py") for i in options) diff --git a/tests/conftest.py b/tests/conftest.py index bdd3e4516c..1f2347a1ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,7 +74,7 @@ def pytest_addoption(parser) -> None: def pytest_collection_modifyitems(config, items) -> None: - """Convert command line options to markers""" + """Convert command line options to markers.""" # Add skip_primer_external mark if not config.getoption("--primer-external"): skip_primer_external = pytest.mark.skip( diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py index b8851a1ffc..2fa5719fd5 100644 --- a/tests/extensions/test_check_docs_utils.py +++ b/tests/extensions/test_check_docs_utils.py @@ -24,7 +24,7 @@ [("abc", 0), ("", 0), (" abc", 2), ("\n abc", 0), (" \n abc", 3)], ) def test_space_indentation(string: str, count: int) -> None: - """Test for pylint_plugin.ParamDocChecker""" + """Test for pylint_plugin.ParamDocChecker.""" assert utils.space_indentation(string) == count diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 4d2cc812eb..bb00a67ace 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -78,10 +78,10 @@ def test__is_in_ignore_list_re_match() -> None: class TestExpandModules(CheckerTestCase): - """Test the expand_modules function while allowing options to be set""" + """Test the expand_modules function while allowing options to be set.""" class Checker(BaseChecker): - """This dummy checker is needed to allow options to be set""" + """This dummy checker is needed to allow options to be set.""" name = "checker" msgs: Dict[str, Tuple[str, ...]] = {} @@ -107,7 +107,7 @@ class Checker(BaseChecker): ) @set_config(ignore_paths="") def test_expand_modules(self, files_or_modules, expected): - """Test expand_modules with the default value of ignore-paths""" + """Test expand_modules with the default value of ignore-paths.""" ignore_list, ignore_list_re = [], [] modules, errors = expand_modules( files_or_modules, @@ -133,7 +133,7 @@ def test_expand_modules(self, files_or_modules, expected): ) @set_config(ignore_paths=".*/lint/.*") def test_expand_modules_with_ignore(self, files_or_modules, expected): - """Test expand_modules with a non-default value of ignore-paths""" + """Test expand_modules with a non-default value of ignore-paths.""" ignore_list, ignore_list_re = [], [] modules, errors = expand_modules( files_or_modules, diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 6ac33b830b..9dd6ae4dec 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -386,7 +386,7 @@ def test_enable_message_block(initialized_linter: PyLinter) -> None: def test_enable_by_symbol(initialized_linter: PyLinter) -> None: - """messages can be controlled by symbolic names. + """Messages can be controlled by symbolic names. The state is consistent across symbols and numbers. """ @@ -478,7 +478,7 @@ def test_disable_similar(initialized_linter: PyLinter) -> None: def test_disable_alot(linter: PyLinter) -> None: - """check that we disabled a lot of checkers""" + """Check that we disabled a lot of checkers.""" linter.set_option("reports", False) linter.set_option("disable", "R,C,W") checker_names = [c.name for c in linter.prepare_checkers()] diff --git a/tests/message/unittest_message_id_store.py b/tests/message/unittest_message_id_store.py index d1c140e9f7..b44e33c6db 100644 --- a/tests/message/unittest_message_id_store.py +++ b/tests/message/unittest_message_id_store.py @@ -97,7 +97,7 @@ def test_duplicate_msgid(msgid_store: MessageIdStore) -> None: def test_exclusivity_of_msgids() -> None: - """Test to see if all checkers have an exclusive message id prefix""" + """Test to see if all checkers have an exclusive message id prefix.""" err_msg = ( "{} has the same prefix ('{}') as the '{}' checker. Please make sure the prefix " "is unique for each checker. You can use 'script/get_unused_message_id_category.py' " diff --git a/tests/primer/test_primer_external.py b/tests/primer/test_primer_external.py index 12206b0fc4..193eabf1d8 100644 --- a/tests/primer/test_primer_external.py +++ b/tests/primer/test_primer_external.py @@ -30,7 +30,7 @@ def get_packages_to_lint_from_json( PACKAGES_TO_LINT_BATCH_ONE = get_packages_to_lint_from_json( PACKAGE_TO_LINT_JSON_BATCH_ONE ) -"""Dictionary of external packages used during the primer test in batch one""" +"""Dictionary of external packages used during the primer test in batch one.""" PACKAGE_TO_LINT_JSON_BATCH_TWO = ( Path(__file__).parent / "packages_to_lint_batch_two.json" @@ -38,7 +38,7 @@ def get_packages_to_lint_from_json( PACKAGES_TO_LINT_BATCH_TWO = get_packages_to_lint_from_json( PACKAGE_TO_LINT_JSON_BATCH_TWO ) -"""Dictionary of external packages used during the primer test in batch two""" +"""Dictionary of external packages used during the primer test in batch two.""" class TestPrimer: @@ -68,7 +68,7 @@ def test_primer_external_packages_no_crash_batch_two( @staticmethod def _primer_test(package: PackageToLint, caplog: LogCaptureFixture) -> None: - """Runs pylint over external packages to check for crashes and fatal messages + """Runs pylint over external packages to check for crashes and fatal messages. We only check for crashes (bit-encoded exit code 32) and fatal messages (bit-encoded exit code 1). We assume that these external repositories do not diff --git a/tests/primer/test_primer_stdlib.py b/tests/primer/test_primer_stdlib.py index 96c847bc8f..824c1feac9 100644 --- a/tests/primer/test_primer_stdlib.py +++ b/tests/primer/test_primer_stdlib.py @@ -46,7 +46,7 @@ def _patch_stdout(out): def test_primer_stdlib_no_crash( test_module_location: str, test_module_name: str, capsys: CaptureFixture ) -> None: - """Test that pylint does not produce any crashes or fatal errors on stdlib modules""" + """Test that pylint does not produce any crashes or fatal errors on stdlib modules.""" __tracebackhide__ = True # pylint: disable=unused-variable os.chdir(test_module_location) with _patch_stdout(io.StringIO()): diff --git a/tests/profile/test_profile_against_externals.py b/tests/profile/test_profile_against_externals.py index 128a34738f..7be4930e82 100644 --- a/tests/profile/test_profile_against_externals.py +++ b/tests/profile/test_profile_against_externals.py @@ -1,4 +1,4 @@ -""" Profiles basic -jX functionality """ +"""Profiles basic -jX functionality.""" # Copyright (c) 2020-2021 Pierre Sassoulas # Copyright (c) 2020 Frank Harrison # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> @@ -42,7 +42,7 @@ def _get_py_files(scanpath): "name,git_repo", [("numpy", "https://github.com/numpy/numpy.git")] ) def test_run(tmp_path, name, git_repo): - """Runs pylint against external sources""" + """Runs pylint against external sources.""" checkoutdir = tmp_path / name checkoutdir.mkdir() os.system(f"git clone --depth=1 {git_repo} {checkoutdir}") diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py index c83e74cbd4..ed3ad8f422 100644 --- a/tests/pyreverse/conftest.py +++ b/tests/pyreverse/conftest.py @@ -62,7 +62,7 @@ def html_config() -> PyreverseConfig: @pytest.fixture(scope="session") def get_project() -> Callable: def _get_project(module: str, name: Optional[str] = "No Name") -> Project: - """return an astroid project representation""" + """Return an astroid project representation.""" def _astroid_wrapper(func: Callable, modname: str) -> Module: return func(modname) diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index 5fe73bf36f..d9a3589360 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -21,7 +21,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Unit test for the extensions.diadefslib modules""" +"""Unit test for the extensions.diadefslib modules.""" # pylint: disable=redefined-outer-name import sys from pathlib import Path @@ -42,14 +42,14 @@ def _process_classes(classes: List[DiagramEntity]) -> List[Tuple[bool, str]]: - """extract class names of a list""" + """Extract class names of a list.""" return sorted((isinstance(c.node, nodes.ClassDef), c.title) for c in classes) def _process_relations( relations: Dict[str, List[Relationship]] ) -> List[Tuple[str, str, str]]: - """extract relation indices from a relation list""" + """Extract relation indices from a relation list.""" result = [] for rel_type, rels in relations.items(): for rel in rels: @@ -71,7 +71,7 @@ def PROJECT(get_project): def test_option_values( default_config: PyreverseConfig, HANDLER: DiadefsHandler, PROJECT: Project ) -> None: - """test for ancestor, associated and module options""" + """Test for ancestor, associated and module options.""" df_h = DiaDefGenerator(Linker(PROJECT), HANDLER) cl_config = default_config cl_config.classes = ["Specialization"] @@ -102,7 +102,7 @@ def test_option_values( def test_default_values() -> None: - """test default values for package or class diagrams""" + """Test default values for package or class diagrams.""" # TODO : should test difference between default values for package or class diagrams pylint: disable=fixme @@ -118,7 +118,7 @@ class TestDefaultDiadefGenerator: def test_exctract_relations( self, HANDLER: DiadefsHandler, PROJECT: Project ) -> None: - """test extract_relations between classes""" + """Test extract_relations between classes.""" cd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT)[1] cd.extract_relationships() relations = _process_relations(cd.relationships) @@ -127,7 +127,7 @@ def test_exctract_relations( def test_functional_relation_extraction( self, default_config: PyreverseConfig, get_project: Callable ) -> None: - """functional test of relations extraction; + """Functional test of relations extraction; different classes possibly in different modules """ # XXX should be catching pyreverse environment problem but doesn't diff --git a/tests/pyreverse/test_diagrams.py b/tests/pyreverse/test_diagrams.py index db8a4e645b..b307a37d8e 100644 --- a/tests/pyreverse/test_diagrams.py +++ b/tests/pyreverse/test_diagrams.py @@ -5,7 +5,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Unit test for the diagrams modules""" +"""Unit test for the diagrams modules.""" from typing import Callable from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py index 6fff4e5fc0..3a4ee423c2 100644 --- a/tests/pyreverse/test_inspector.py +++ b/tests/pyreverse/test_inspector.py @@ -13,7 +13,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""for the visitors.diadefs module""" +"""For the visitors.diadefs module.""" # pylint: disable=redefined-outer-name import os diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index 792bcbb8b9..01f3ccc099 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -1,4 +1,4 @@ -"""Unittest for the main module""" +"""Unittest for the main module.""" import os import sys from typing import Iterator diff --git a/tests/pyreverse/test_printer_factory.py b/tests/pyreverse/test_printer_factory.py index f39b90dade..73a5e2670b 100644 --- a/tests/pyreverse/test_printer_factory.py +++ b/tests/pyreverse/test_printer_factory.py @@ -3,7 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Unit tests for pylint.pyreverse.printer_factory""" +"""Unit tests for pylint.pyreverse.printer_factory.""" import pytest diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index c79d43b77e..f571b18473 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -8,7 +8,7 @@ # 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 -"""Tests for pylint.pyreverse.utils""" +"""Tests for pylint.pyreverse.utils.""" from typing import Any from unittest.mock import patch @@ -48,7 +48,7 @@ def test_get_visibility(names, expected): ], ) def test_get_annotation_annassign(assign, label): - """AnnAssign""" + """AnnAssign.""" node = astroid.extract_node(assign) got = get_annotation(node.value).name assert isinstance(node, nodes.AnnAssign) @@ -67,7 +67,7 @@ def test_get_annotation_annassign(assign, label): ], ) def test_get_annotation_assignattr(init_method, label): - """AssignAttr""" + """AssignAttr.""" assign = rf""" class A: {init_method} diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 426cef68e1..1226182138 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -17,7 +17,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""Unit test for ``DiagramWriter``""" +"""Unit test for ``DiagramWriter``.""" import codecs @@ -61,7 +61,7 @@ class Config: - """config object for tests""" + """Config object for tests.""" def __init__(self): for attr, value in _DEFAULTS.items(): diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index bb6e150a3e..f4ddef985b 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -1,4 +1,4 @@ -"""Puts the check_parallel system under test""" +"""Puts the check_parallel system under test.""" # Copyright (c) 2020-2021 Pierre Sassoulas # Copyright (c) 2020 Frank Harrison # Copyright (c) 2021 Jaehoon Hwang @@ -34,7 +34,7 @@ def _gen_file_data(idx: int = 0) -> FileItem: - """Generates a file to use as a stream""" + """Generates a file to use as a stream.""" filepath = os.path.abspath( os.path.join(os.path.dirname(__file__), "input", "similar1") ) @@ -51,7 +51,7 @@ def _gen_file_datas(count: int = 1) -> List[FileItem]: class SequentialTestChecker(BaseChecker): - """A checker that does not need to consolidate data across run invocations""" + """A checker that does not need to consolidate data across run invocations.""" __implements__ = (pylint.interfaces.IRawChecker,) @@ -71,7 +71,7 @@ def __init__(self, linter: PyLinter) -> None: self.linter = linter def process_module(self, _node: nodes.Module) -> None: - """Called once per stream/file/astroid object""" + """Called once per stream/file/astroid object.""" # record the number of invocations with the data object record = self.test_data + str(len(self.data)) self.data.append(record) @@ -110,7 +110,7 @@ def __init__(self, linter: PyLinter) -> None: self.linter = linter def open(self) -> None: - """init the checkers: reset statistics information""" + """Init the checkers: reset statistics information.""" self.linter.stats.reset_node_count() self.data = [] @@ -132,42 +132,42 @@ def reduce_map_data(self, linter: PyLinter, data: List[List[str]]) -> None: recombined.close() def process_module(self, _node: nodes.Module) -> None: - """Called once per stream/file/astroid object""" + """Called once per stream/file/astroid object.""" # record the number of invocations with the data object record = self.test_data + str(len(self.data)) self.data.append(record) class ExtraSequentialTestChecker(SequentialTestChecker): - """A checker that does not need to consolidate data across run invocations""" + """A checker that does not need to consolidate data across run invocations.""" name = "extra-sequential-checker" test_data = "extra-sequential" class ExtraParallelTestChecker(ParallelTestChecker): - """A checker that does need to consolidate data across run invocations""" + """A checker that does need to consolidate data across run invocations.""" name = "extra-parallel-checker" test_data = "extra-parallel" class ThirdSequentialTestChecker(SequentialTestChecker): - """A checker that does not need to consolidate data across run invocations""" + """A checker that does not need to consolidate data across run invocations.""" name = "third-sequential-checker" test_data = "third-sequential" class ThirdParallelTestChecker(ParallelTestChecker): - """A checker that does need to consolidate data across run invocations""" + """A checker that does need to consolidate data across run invocations.""" name = "third-parallel-checker" test_data = "third-parallel" class TestCheckParallelFramework: - """Tests the check_parallel() function's framework""" + """Tests the check_parallel() function's framework.""" def setup_class(self): self._prev_global_linter = pylint.lint.parallel._worker_linter @@ -240,7 +240,7 @@ def test_worker_check_single_file_no_checkers(self) -> None: assert stats.warning == 0 def test_worker_check_sequential_checker(self) -> None: - """Same as test_worker_check_single_file_no_checkers with SequentialTestChecker""" + """Same as test_worker_check_single_file_no_checkers with SequentialTestChecker.""" linter = PyLinter(reporter=Reporter()) worker_initialize(linter=dill.dumps(linter)) @@ -285,10 +285,10 @@ def test_worker_check_sequential_checker(self) -> None: class TestCheckParallel: - """Tests the check_parallel() function""" + """Tests the check_parallel() function.""" def test_sequential_checkers_work(self) -> None: - """Tests original basic types of checker works as expected in -jN + """Tests original basic types of checker works as expected in -jN. This means that a sequential checker should return the same data for a given file-stream irrespective of whether it's run in -j1 or -jN @@ -359,7 +359,7 @@ def test_sequential_checkers_work(self) -> None: assert linter.stats.warning == 0 def test_invoke_single_job(self) -> None: - """Tests basic checkers functionality using just a single workderdo + """Tests basic checkers functionality using just a single workderdo. This is *not* the same -j1 and does not happen under normal operation """ @@ -425,7 +425,7 @@ def test_invoke_single_job(self) -> None: ], ) def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers): - """Compares the 3 key parameters for check_parallel() produces the same results + """Compares the 3 key parameters for check_parallel() produces the same results. The intent here is to ensure that the check_parallel() operates on each file, without ordering issues, irrespective of the number of workers used and the @@ -525,7 +525,7 @@ def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers) ], ) def test_map_reduce(self, num_files, num_jobs, num_checkers): - """Compares the 3 key parameters for check_parallel() produces the same results + """Compares the 3 key parameters for check_parallel() produces the same results. The intent here is to validate the reduce step: no stats should be lost. diff --git a/tests/test_epylint.py b/tests/test_epylint.py index 4f2d2aad01..8e7ff9b79f 100644 --- a/tests/test_epylint.py +++ b/tests/test_epylint.py @@ -1,4 +1,4 @@ -"""Test for issue https://github.com/PyCQA/pylint/issues/4286""" +"""Test for issue https://github.com/PyCQA/pylint/issues/4286.""" # pylint: disable=redefined-outer-name from pathlib import PosixPath diff --git a/tests/test_func.py b/tests/test_func.py index 3e6bf36ce1..492797ec21 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -18,7 +18,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""functional/non regression tests for pylint""" +"""Functional/non regression tests for pylint.""" import re import sys @@ -38,7 +38,7 @@ def exception_str(self, ex) -> str: # pylint: disable=unused-argument - """function used to replace default __str__ method of exception instances + """Function used to replace default __str__ method of exception instances This function is not typed because it is legacy code """ return f"in {ex.file}\n:: {', '.join(ex.args)}" diff --git a/tests/test_import_graph.py b/tests/test_import_graph.py index 6035601bf5..9a4ea59a63 100644 --- a/tests/test_import_graph.py +++ b/tests/test_import_graph.py @@ -47,7 +47,7 @@ def dest(request: SubRequest) -> Iterator[Union[Iterator, Iterator[str]]]: @pytest.mark.parametrize("dest", POSSIBLE_DOT_FILENAMES, indirect=True) def test_dependencies_graph(dest: str) -> None: - """DOC files are correctly generated, and the graphname is the basename""" + """DOC files are correctly generated, and the graphname is the basename.""" imports._dependencies_graph(dest, {"labas": {"hoho", "yep"}, "hoho": {"yep"}}) with open(dest, encoding="utf-8") as stream: assert ( @@ -73,7 +73,7 @@ def test_dependencies_graph(dest: str) -> None: any(shutil.which(x) for x in ("dot", "gv")), reason="dot or gv is installed" ) def test_missing_graphviz(filename: str) -> None: - """Raises if graphviz is not installed, and defaults to png if no extension given""" + """Raises if graphviz is not installed, and defaults to png if no extension given.""" with pytest.raises(RuntimeError, match=r"Cannot generate `graph\.png`.*"): imports._dependencies_graph(filename, {"a": {"b", "c"}, "b": {"c"}}) diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py index 88113cb2c6..e4b20c0a87 100644 --- a/tests/test_pylint_runners.py +++ b/tests/test_pylint_runners.py @@ -27,7 +27,7 @@ def test_runner(runner: Callable, tmpdir: LocalPath) -> None: "runner", [run_epylint, run_pylint, run_pyreverse, run_symilar] ) def test_runner_with_arguments(runner: Callable, tmpdir: LocalPath) -> None: - """Check the runners with arguments as parameter instead of sys.argv""" + """Check the runners with arguments as parameter instead of sys.argv.""" filepath = os.path.abspath(__file__) testargs = [filepath] with tmpdir.as_cwd(): diff --git a/tests/test_regr.py b/tests/test_regr.py index 99b6002755..3ece9c9873 100644 --- a/tests/test_regr.py +++ b/tests/test_regr.py @@ -17,7 +17,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""non regression tests for pylint, which requires a too specific configuration +"""Non regression tests for pylint, which requires a too specific configuration to be incorporated in the automatic functional test framework """ # pylint: disable=redefined-outer-name diff --git a/tests/test_self.py b/tests/test_self.py index 8b41654d20..61aa35d325 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -292,7 +292,7 @@ def test_error_missing_arguments(self) -> None: self._runtest([], code=32) def test_no_out_encoding(self) -> None: - """test redirection of stdout with non ascii characters""" + """Test redirection of stdout with non ascii characters.""" # This test reproduces bug #48066 ; it happens when stdout is redirected # through '>' : the sys.stdout.encoding becomes then None, and if the # output contains non ascii, pylint will crash @@ -1264,7 +1264,7 @@ def test_output_file_specified_in_rcfile(self, tmpdir: LocalPath) -> None: @staticmethod def test_enable_all_extensions() -> None: - """Test to see if --enable-all-extensions does indeed load all extensions""" + """Test to see if --enable-all-extensions does indeed load all extensions.""" # Record all extensions plugins = [] for filename in os.listdir(os.path.dirname(extensions.__file__)): @@ -1280,7 +1280,7 @@ def test_enable_all_extensions() -> None: @staticmethod def test_load_text_repoter_if_not_provided() -> None: - """Test if PyLinter.reporter is a TextReporter if no reporter is provided""" + """Test if PyLinter.reporter is a TextReporter if no reporter is provided.""" linter = PyLinter() assert isinstance(linter.reporter, TextReporter) diff --git a/tests/testutils/test_decorator.py b/tests/testutils/test_decorator.py index 0bd1fb1199..4ab9d04f04 100644 --- a/tests/testutils/test_decorator.py +++ b/tests/testutils/test_decorator.py @@ -8,7 +8,7 @@ def test_deprecation_of_set_config_directly() -> None: - """Test that the deprecation of set_config_directly works as expected""" + """Test that the deprecation of set_config_directly works as expected.""" with pytest.warns(DeprecationWarning) as records: set_config_directly() diff --git a/tests/unittest_reporters_json.py b/tests/unittest_reporters_json.py index a1b76fe104..be7aadabac 100644 --- a/tests/unittest_reporters_json.py +++ b/tests/unittest_reporters_json.py @@ -28,7 +28,7 @@ def test_simple_json_output_no_score() -> None: - """Test JSON reporter with no score""" + """Test JSON reporter with no score.""" message = { "msg": "line-too-long", "line": 1, @@ -57,7 +57,7 @@ def test_simple_json_output_no_score() -> None: def test_simple_json_output_no_score_with_end_line() -> None: - """Test JSON reporter with no score with end_line and end_column""" + """Test JSON reporter with no score with end_line and end_column.""" message = { "msg": "line-too-long", "line": 1, diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 1ee9477593..d6cb182206 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -58,7 +58,7 @@ def test_template_option(linter): def test_template_option_default(linter) -> None: - """Test the default msg-template setting""" + """Test the default msg-template setting.""" output = StringIO() linter.reporter.out = output linter.open() @@ -72,7 +72,7 @@ def test_template_option_default(linter) -> None: def test_template_option_end_line(linter) -> None: - """Test the msg-template option with end_line and end_column""" + """Test the msg-template option with end_line and end_column.""" output = StringIO() linter.reporter.out = output linter.set_option( @@ -125,7 +125,7 @@ def test_template_option_non_existing(linter) -> None: def test_deprecation_set_output(recwarn): - """TODO remove in 3.0""" + """TODO remove in 3.0.""" reporter = BaseReporter() # noinspection PyDeprecation reporter.set_output(sys.stdout) diff --git a/tests/utils/unittest_utils.py b/tests/utils/unittest_utils.py index 6514de872c..485756e7da 100644 --- a/tests/utils/unittest_utils.py +++ b/tests/utils/unittest_utils.py @@ -25,7 +25,7 @@ def test_decoding_stream_unknown_encoding() -> None: - """decoding_stream should fall back to *some* decoding when given an + """Decoding_stream should fall back to *some* decoding when given an unknown encoding. """ binary_io = io.BytesIO(b"foo\nbar") From 93cf02f1d824453c5592c57092e9ba79cc75e3de Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 10 Feb 2022 22:31:40 +0100 Subject: [PATCH 207/357] Fix the period added in docstring automatically by mistake (#5790) Due to https://github.com/DanielNoord/pydocstringformatter/pull/52 --- .pre-commit-config.yaml | 2 +- pylint/checkers/format.py | 2 +- pylint/lint/run.py | 5 +---- pylint/pyreverse/diadefslib.py | 7 ++----- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33de05026c..0757c41c10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -96,7 +96,7 @@ repos: args: [--prose-wrap=always, --print-width=88] exclude: tests(/.*)*/data - repo: https://github.com/DanielNoord/pydocstringformatter - rev: v0.4.0 + rev: a9f94bf13b08fe33f784ed7f0a0fc39e2a8549e2 hooks: - id: pydocstringformatter exclude: *fixtures diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index fe505a66d5..799d97cbc5 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -471,7 +471,7 @@ def _prepare_token_dispatcher(self): return dispatch def process_tokens(self, tokens): - """Process tokens and search for :. + """Process tokens and search for : _ too long lines (i.e. longer than ) _ optionally bad construct (if given, bad_construct must be a compiled diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 25311024a2..6cbdc5cc71 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -55,10 +55,7 @@ def cb_init_hook(optname, value): class Run: - """Helper class to use as main for pylint :. - - run(*sys.argv[1:]) - """ + """Helper class to use as main for pylint with 'run(*sys.argv[1:])'.""" LinterClass = PyLinter option_groups = ( diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index 44a593849c..6fa8ea107b 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -126,7 +126,7 @@ def extract_classes(self, klass_node, anc_level, association_level): class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): - """Generate minimum diagram definition for the project :. + """Generate minimum diagram definition for the project : * a package diagram including project's modules * a class diagram including project's classes @@ -210,10 +210,7 @@ def class_diagram(self, project, klass): class DiadefsHandler: - """Handle diagram definitions :. - - get it from user (i.e. xml files) or generate them - """ + """Get diagram definitions from user (i.e. xml files) or generate them.""" def __init__(self, config): self.config = config From 4b61446eadd400e5e1b710d07c7e2f49c17c673c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 9 Feb 2022 00:06:31 +0100 Subject: [PATCH 208/357] Move functional test files to new structure --- tests/functional/a/{assign => }/assign_to_new_keyword.py | 0 tests/functional/a/{assign => }/assign_to_new_keyword.rc | 0 tests/functional/a/{assign => }/assign_to_new_keyword.txt | 0 .../functional/a/{assign => assigning}/assigning_non_slot.py | 0 .../functional/a/{assign => assigning}/assigning_non_slot.txt | 0 .../a/{assign => assigning}/assigning_non_slot_4509.py | 0 .../a/{assign => assigning}/assigning_non_slot_4509.rc | 0 .../a/{assign => assigning}/assigning_non_slot_4509.txt | 0 .../a/{assign => assignment}/assignment_expression.py | 0 .../a/{assign => assignment}/assignment_expression.rc | 0 .../a/{assign => assignment}/assignment_expression.txt | 0 .../a/{assign => assignment}/assignment_from_no_return.py | 0 .../a/{assign => assignment}/assignment_from_no_return.txt | 0 .../a/{assign => assignment}/assignment_from_no_return_2.py | 0 .../a/{assign => assignment}/assignment_from_no_return_2.txt | 0 .../a/{assign => assignment}/assignment_from_no_return_py3.py | 0 tests/functional/d/{deprecated => }/dataclass_typecheck.py | 0 tests/functional/d/{deprecated => }/dataclass_typecheck.rc | 0 tests/functional/d/{deprecated => }/dataclass_typecheck.txt | 0 tests/functional/i/import_error.py | 2 +- tests/functional/i/import_error.txt | 4 ++-- tests/functional/i/invalid/{b => }/invalid_bool_returned.py | 0 tests/functional/i/invalid/{b => }/invalid_bool_returned.txt | 0 tests/functional/i/invalid/{b => }/invalid_bytes_returned.py | 0 tests/functional/i/invalid/{b => }/invalid_bytes_returned.txt | 0 tests/functional/i/invalid/{e => }/invalid_envvar_value.py | 0 tests/functional/i/invalid/{e => }/invalid_envvar_value.txt | 0 .../{e => invalid_exceptions}/invalid_exceptions_caught.py | 0 .../{e => invalid_exceptions}/invalid_exceptions_caught.rc | 0 .../{e => invalid_exceptions}/invalid_exceptions_caught.txt | 0 .../{e => invalid_exceptions}/invalid_exceptions_raised.py | 0 .../{e => invalid_exceptions}/invalid_exceptions_raised.txt | 0 .../invalid_getnewargs_ex_returned.py | 0 .../invalid_getnewargs_ex_returned.txt | 0 .../{g => invalid_getnewargs}/invalid_getnewargs_returned.py | 0 .../{g => invalid_getnewargs}/invalid_getnewargs_returned.txt | 0 .../{l => invalid_length}/invalid_length_hint_returned.py | 0 .../{l => invalid_length}/invalid_length_hint_returned.txt | 0 .../invalid/{l => invalid_length}/invalid_length_returned.py | 0 .../invalid/{l => invalid_length}/invalid_length_returned.txt | 0 tests/functional/i/invalid/{m => }/invalid_metaclass.py | 0 tests/functional/i/invalid/{m => }/invalid_metaclass.txt | 0 tests/functional/i/invalid/{m => }/invalid_metaclass_py3.py | 0 .../i/invalid/{ => invalid_name}/invalid_name_issue_3405.py | 0 .../i/invalid/{ => invalid_name}/invalid_name_issue_3405.rc | 0 .../i/invalid/{ => invalid_name}/invalid_name_issue_3405.txt | 0 .../i/invalid/{ => invalid_name}/invalid_name_module_level.py | 0 .../invalid/{ => invalid_name}/invalid_name_module_level.txt | 0 .../{ => invalid_name}/invalid_name_multinaming_style.py | 0 .../{ => invalid_name}/invalid_name_multinaming_style.rc | 0 .../{ => invalid_name}/invalid_name_multinaming_style.txt | 0 .../i/invalid/{ => invalid_name}/invalid_name_property.py | 0 .../i/invalid/{ => invalid_name}/invalid_name_property.rc | 0 .../i/invalid/{ => invalid_name}/invalid_name_property.txt | 0 tests/functional/i/invalid/{s => }/invalid_sequence_index.py | 0 tests/functional/i/invalid/{s => }/invalid_sequence_index.txt | 0 tests/functional/i/invalid/{s => }/invalid_slice_index.py | 0 tests/functional/i/invalid/{s => }/invalid_slice_index.txt | 0 .../i/invalid/{s => }/invalid_star_assignment_target.py | 0 .../i/invalid/{s => }/invalid_star_assignment_target.txt | 0 tests/functional/i/invalid/{s => }/invalid_str_returned.py | 0 tests/functional/i/invalid/{s => }/invalid_str_returned.txt | 0 tests/functional/m/{member => }/membership_protocol.py | 0 tests/functional/m/{member => }/membership_protocol.txt | 0 tests/functional/m/{member => }/membership_protocol_py3.py | 0 tests/functional/m/{member => }/membership_protocol_py3.txt | 0 tests/functional/n/{name => }/namePresetCamelCase.py | 0 tests/functional/n/{name => }/namePresetCamelCase.rc | 0 tests/functional/n/{name => }/namePresetCamelCase.txt | 0 tests/functional/n/{name => }/namedtuple_member_inference.py | 0 tests/functional/n/{name => }/namedtuple_member_inference.txt | 0 tests/functional/n/{name => }/names_in__all__.py | 0 tests/functional/n/{name => }/names_in__all__.txt | 0 .../functional/n/non_ascii_name/non_ascii_name_lo\305\202.py" | 0 .../n/non_ascii_name/non_ascii_name_lo\305\202.txt" | 0 tests/functional/r/{raise => }/raise_missing_from.py | 0 tests/functional/r/{raise => }/raise_missing_from.txt | 0 tests/functional/r/{raise => raising}/raising_bad_type.py | 0 tests/functional/r/{raise => raising}/raising_bad_type.txt | 0 tests/functional/r/{raise => raising}/raising_format_tuple.py | 0 .../functional/r/{raise => raising}/raising_format_tuple.txt | 0 .../functional/r/{raise => raising}/raising_non_exception.py | 0 .../functional/r/{raise => raising}/raising_non_exception.txt | 0 tests/functional/r/{raise => raising}/raising_self.py | 0 .../r/{ => redefined}/redefined_argument_from_local.py | 0 .../r/{ => redefined}/redefined_argument_from_local.txt | 0 tests/functional/r/{ => redefined}/redefined_builtin.py | 0 tests/functional/r/{ => redefined}/redefined_builtin.rc | 0 tests/functional/r/{ => redefined}/redefined_builtin.txt | 0 .../functional/r/{ => redefined}/redefined_builtin_allowed.py | 0 .../functional/r/{ => redefined}/redefined_builtin_allowed.rc | 0 .../r/{ => redefined}/redefined_builtin_allowed.txt | 0 .../functional/r/{ => redefined}/redefined_except_handler.py | 0 .../functional/r/{ => redefined}/redefined_except_handler.txt | 0 .../r/{ => redefined}/redefined_outer_name_type_checking.py | 0 .../r/{ => redefined}/redefined_outer_name_type_checking.rc | 0 tests/functional/r/{ => redefined}/redefined_slots.py | 0 tests/functional/r/{ => redefined}/redefined_slots.txt | 0 .../regression_too_many_arguments_2335.py | 0 .../s/{simplifiable => }/simplify_chained_comparison.py | 0 .../s/{simplifiable => }/simplify_chained_comparison.txt | 0 tests/functional/s/{super => }/superfluous_parens.py | 0 tests/functional/s/{super => }/superfluous_parens.txt | 0 .../s/{super => }/superfluous_parens_walrus_py38.py | 0 .../s/{super => }/superfluous_parens_walrus_py38.rc | 0 .../s/{super => }/superfluous_parens_walrus_py38.txt | 0 tests/functional/s/symlink/_binding/__init__.py | 1 + tests/functional/s/symlink/_binding/symlink_module.py | 1 + tests/functional/s/symlink/binding/__init__.py | 1 - tests/functional/s/symlink/binding/symlinked_module.py | 1 - .../s/symlink/{module => symlink_module}/__init__.py | 0 .../symlinked_module.py => symlink_module/symlink_module.py} | 0 tests/functional/s/{ => syntax}/syntax_error.py | 0 tests/functional/s/{ => syntax}/syntax_error.rc | 0 tests/functional/s/{ => syntax}/syntax_error.txt | 0 tests/functional/s/{ => syntax}/syntax_error_jython.py | 0 tests/functional/s/{ => syntax}/syntax_error_jython.rc | 0 tests/functional/s/{ => syntax}/syntax_error_jython.txt | 0 tests/functional/u/unbalanced_tuple_unpacking.py | 2 +- tests/functional/u/unbalanced_tuple_unpacking.txt | 4 ++-- tests/functional/u/{ => unpacking}/unpacking.py | 0 .../functional/u/{ => unpacking}/unpacking_generalizations.py | 0 .../u/{ => unpacking}/unpacking_generalizations.txt | 0 tests/functional/u/{ => unpacking}/unpacking_non_sequence.py | 2 +- tests/functional/u/{ => unpacking}/unpacking_non_sequence.txt | 4 ++-- .../u/{ => unpacking}/unpacking_non_sequence_py37.py | 0 .../u/{ => unpacking}/unpacking_non_sequence_py37.rc | 0 tests/functional/u/{use => used}/used_before_assignment.py | 0 tests/functional/u/{use => used}/used_before_assignment.txt | 0 .../functional/u/{use => used}/used_before_assignment_488.py | 0 ...ed_before_assignment_except_handler_for_try_with_return.py | 0 ...d_before_assignment_except_handler_for_try_with_return.txt | 0 .../used_before_assignment_filtered_comprehension.py | 0 .../u/{use => used}/used_before_assignment_issue1081.py | 0 .../u/{use => used}/used_before_assignment_issue1081.txt | 0 .../u/{use => used}/used_before_assignment_issue2615.py | 0 .../u/{use => used}/used_before_assignment_issue2615.txt | 0 .../u/{use => used}/used_before_assignment_issue4761.py | 0 .../u/{use => used}/used_before_assignment_issue4761.txt | 0 .../u/{use => used}/used_before_assignment_issue626.py | 0 .../u/{use => used}/used_before_assignment_issue626.txt | 0 .../u/{use => used}/used_before_assignment_issue85.py | 0 .../u/{use => used}/used_before_assignment_issue85.txt | 0 .../u/{use => used}/used_before_assignment_issue853.py | 0 .../u/{use => used}/used_before_assignment_nonlocal.py | 0 .../u/{use => used}/used_before_assignment_nonlocal.txt | 0 .../functional/u/{use => used}/used_before_assignment_py37.py | 0 .../functional/u/{use => used}/used_before_assignment_py37.rc | 0 .../u/{use => used}/used_before_assignment_py37.txt | 0 .../{use => used}/used_before_assignment_type_annotations.py | 0 .../{use => used}/used_before_assignment_type_annotations.txt | 0 .../u/{use => used}/used_before_assignment_typing.py | 0 .../u/{use => used}/used_before_assignment_typing.txt | 0 .../u/{use => used}/used_prior_global_declaration.py | 0 .../u/{use => used}/used_prior_global_declaration.txt | 0 tests/functional/u/{use => }/using_constant_test.py | 0 tests/functional/u/{use => }/using_constant_test.txt | 0 157 files changed, 11 insertions(+), 11 deletions(-) rename tests/functional/a/{assign => }/assign_to_new_keyword.py (100%) rename tests/functional/a/{assign => }/assign_to_new_keyword.rc (100%) rename tests/functional/a/{assign => }/assign_to_new_keyword.txt (100%) rename tests/functional/a/{assign => assigning}/assigning_non_slot.py (100%) rename tests/functional/a/{assign => assigning}/assigning_non_slot.txt (100%) rename tests/functional/a/{assign => assigning}/assigning_non_slot_4509.py (100%) rename tests/functional/a/{assign => assigning}/assigning_non_slot_4509.rc (100%) rename tests/functional/a/{assign => assigning}/assigning_non_slot_4509.txt (100%) rename tests/functional/a/{assign => assignment}/assignment_expression.py (100%) rename tests/functional/a/{assign => assignment}/assignment_expression.rc (100%) rename tests/functional/a/{assign => assignment}/assignment_expression.txt (100%) rename tests/functional/a/{assign => assignment}/assignment_from_no_return.py (100%) rename tests/functional/a/{assign => assignment}/assignment_from_no_return.txt (100%) rename tests/functional/a/{assign => assignment}/assignment_from_no_return_2.py (100%) rename tests/functional/a/{assign => assignment}/assignment_from_no_return_2.txt (100%) rename tests/functional/a/{assign => assignment}/assignment_from_no_return_py3.py (100%) rename tests/functional/d/{deprecated => }/dataclass_typecheck.py (100%) rename tests/functional/d/{deprecated => }/dataclass_typecheck.rc (100%) rename tests/functional/d/{deprecated => }/dataclass_typecheck.txt (100%) rename tests/functional/i/invalid/{b => }/invalid_bool_returned.py (100%) rename tests/functional/i/invalid/{b => }/invalid_bool_returned.txt (100%) rename tests/functional/i/invalid/{b => }/invalid_bytes_returned.py (100%) rename tests/functional/i/invalid/{b => }/invalid_bytes_returned.txt (100%) rename tests/functional/i/invalid/{e => }/invalid_envvar_value.py (100%) rename tests/functional/i/invalid/{e => }/invalid_envvar_value.txt (100%) rename tests/functional/i/invalid/{e => invalid_exceptions}/invalid_exceptions_caught.py (100%) rename tests/functional/i/invalid/{e => invalid_exceptions}/invalid_exceptions_caught.rc (100%) rename tests/functional/i/invalid/{e => invalid_exceptions}/invalid_exceptions_caught.txt (100%) rename tests/functional/i/invalid/{e => invalid_exceptions}/invalid_exceptions_raised.py (100%) rename tests/functional/i/invalid/{e => invalid_exceptions}/invalid_exceptions_raised.txt (100%) rename tests/functional/i/invalid/{g => invalid_getnewargs}/invalid_getnewargs_ex_returned.py (100%) rename tests/functional/i/invalid/{g => invalid_getnewargs}/invalid_getnewargs_ex_returned.txt (100%) rename tests/functional/i/invalid/{g => invalid_getnewargs}/invalid_getnewargs_returned.py (100%) rename tests/functional/i/invalid/{g => invalid_getnewargs}/invalid_getnewargs_returned.txt (100%) rename tests/functional/i/invalid/{l => invalid_length}/invalid_length_hint_returned.py (100%) rename tests/functional/i/invalid/{l => invalid_length}/invalid_length_hint_returned.txt (100%) rename tests/functional/i/invalid/{l => invalid_length}/invalid_length_returned.py (100%) rename tests/functional/i/invalid/{l => invalid_length}/invalid_length_returned.txt (100%) rename tests/functional/i/invalid/{m => }/invalid_metaclass.py (100%) rename tests/functional/i/invalid/{m => }/invalid_metaclass.txt (100%) rename tests/functional/i/invalid/{m => }/invalid_metaclass_py3.py (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_issue_3405.py (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_issue_3405.rc (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_issue_3405.txt (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_module_level.py (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_module_level.txt (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_multinaming_style.py (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_multinaming_style.rc (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_multinaming_style.txt (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_property.py (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_property.rc (100%) rename tests/functional/i/invalid/{ => invalid_name}/invalid_name_property.txt (100%) rename tests/functional/i/invalid/{s => }/invalid_sequence_index.py (100%) rename tests/functional/i/invalid/{s => }/invalid_sequence_index.txt (100%) rename tests/functional/i/invalid/{s => }/invalid_slice_index.py (100%) rename tests/functional/i/invalid/{s => }/invalid_slice_index.txt (100%) rename tests/functional/i/invalid/{s => }/invalid_star_assignment_target.py (100%) rename tests/functional/i/invalid/{s => }/invalid_star_assignment_target.txt (100%) rename tests/functional/i/invalid/{s => }/invalid_str_returned.py (100%) rename tests/functional/i/invalid/{s => }/invalid_str_returned.txt (100%) rename tests/functional/m/{member => }/membership_protocol.py (100%) rename tests/functional/m/{member => }/membership_protocol.txt (100%) rename tests/functional/m/{member => }/membership_protocol_py3.py (100%) rename tests/functional/m/{member => }/membership_protocol_py3.txt (100%) rename tests/functional/n/{name => }/namePresetCamelCase.py (100%) rename tests/functional/n/{name => }/namePresetCamelCase.rc (100%) rename tests/functional/n/{name => }/namePresetCamelCase.txt (100%) rename tests/functional/n/{name => }/namedtuple_member_inference.py (100%) rename tests/functional/n/{name => }/namedtuple_member_inference.txt (100%) rename tests/functional/n/{name => }/names_in__all__.py (100%) rename tests/functional/n/{name => }/names_in__all__.txt (100%) rename "tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" => "tests/functional/n/non_ascii_name/non_ascii_name_lo\305\202.py" (100%) rename "tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" => "tests/functional/n/non_ascii_name/non_ascii_name_lo\305\202.txt" (100%) rename tests/functional/r/{raise => }/raise_missing_from.py (100%) rename tests/functional/r/{raise => }/raise_missing_from.txt (100%) rename tests/functional/r/{raise => raising}/raising_bad_type.py (100%) rename tests/functional/r/{raise => raising}/raising_bad_type.txt (100%) rename tests/functional/r/{raise => raising}/raising_format_tuple.py (100%) rename tests/functional/r/{raise => raising}/raising_format_tuple.txt (100%) rename tests/functional/r/{raise => raising}/raising_non_exception.py (100%) rename tests/functional/r/{raise => raising}/raising_non_exception.txt (100%) rename tests/functional/r/{raise => raising}/raising_self.py (100%) rename tests/functional/r/{ => redefined}/redefined_argument_from_local.py (100%) rename tests/functional/r/{ => redefined}/redefined_argument_from_local.txt (100%) rename tests/functional/r/{ => redefined}/redefined_builtin.py (100%) rename tests/functional/r/{ => redefined}/redefined_builtin.rc (100%) rename tests/functional/r/{ => redefined}/redefined_builtin.txt (100%) rename tests/functional/r/{ => redefined}/redefined_builtin_allowed.py (100%) rename tests/functional/r/{ => redefined}/redefined_builtin_allowed.rc (100%) rename tests/functional/r/{ => redefined}/redefined_builtin_allowed.txt (100%) rename tests/functional/r/{ => redefined}/redefined_except_handler.py (100%) rename tests/functional/r/{ => redefined}/redefined_except_handler.txt (100%) rename tests/functional/r/{ => redefined}/redefined_outer_name_type_checking.py (100%) rename tests/functional/r/{ => redefined}/redefined_outer_name_type_checking.rc (100%) rename tests/functional/r/{ => redefined}/redefined_slots.py (100%) rename tests/functional/r/{ => redefined}/redefined_slots.txt (100%) rename tests/functional/r/{regression => regression_02}/regression_too_many_arguments_2335.py (100%) rename tests/functional/s/{simplifiable => }/simplify_chained_comparison.py (100%) rename tests/functional/s/{simplifiable => }/simplify_chained_comparison.txt (100%) rename tests/functional/s/{super => }/superfluous_parens.py (100%) rename tests/functional/s/{super => }/superfluous_parens.txt (100%) rename tests/functional/s/{super => }/superfluous_parens_walrus_py38.py (100%) rename tests/functional/s/{super => }/superfluous_parens_walrus_py38.rc (100%) rename tests/functional/s/{super => }/superfluous_parens_walrus_py38.txt (100%) create mode 120000 tests/functional/s/symlink/_binding/__init__.py create mode 120000 tests/functional/s/symlink/_binding/symlink_module.py delete mode 120000 tests/functional/s/symlink/binding/__init__.py delete mode 120000 tests/functional/s/symlink/binding/symlinked_module.py rename tests/functional/s/symlink/{module => symlink_module}/__init__.py (100%) rename tests/functional/s/symlink/{module/symlinked_module.py => symlink_module/symlink_module.py} (100%) rename tests/functional/s/{ => syntax}/syntax_error.py (100%) rename tests/functional/s/{ => syntax}/syntax_error.rc (100%) rename tests/functional/s/{ => syntax}/syntax_error.txt (100%) rename tests/functional/s/{ => syntax}/syntax_error_jython.py (100%) rename tests/functional/s/{ => syntax}/syntax_error_jython.rc (100%) rename tests/functional/s/{ => syntax}/syntax_error_jython.txt (100%) rename tests/functional/u/{ => unpacking}/unpacking.py (100%) rename tests/functional/u/{ => unpacking}/unpacking_generalizations.py (100%) rename tests/functional/u/{ => unpacking}/unpacking_generalizations.txt (100%) rename tests/functional/u/{ => unpacking}/unpacking_non_sequence.py (98%) rename tests/functional/u/{ => unpacking}/unpacking_non_sequence.txt (86%) rename tests/functional/u/{ => unpacking}/unpacking_non_sequence_py37.py (100%) rename tests/functional/u/{ => unpacking}/unpacking_non_sequence_py37.rc (100%) rename tests/functional/u/{use => used}/used_before_assignment.py (100%) rename tests/functional/u/{use => used}/used_before_assignment.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_488.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_except_handler_for_try_with_return.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_except_handler_for_try_with_return.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_filtered_comprehension.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue1081.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue1081.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue2615.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue2615.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue4761.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue4761.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue626.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue626.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue85.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue85.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_issue853.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_nonlocal.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_nonlocal.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_py37.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_py37.rc (100%) rename tests/functional/u/{use => used}/used_before_assignment_py37.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_type_annotations.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_type_annotations.txt (100%) rename tests/functional/u/{use => used}/used_before_assignment_typing.py (100%) rename tests/functional/u/{use => used}/used_before_assignment_typing.txt (100%) rename tests/functional/u/{use => used}/used_prior_global_declaration.py (100%) rename tests/functional/u/{use => used}/used_prior_global_declaration.txt (100%) rename tests/functional/u/{use => }/using_constant_test.py (100%) rename tests/functional/u/{use => }/using_constant_test.txt (100%) diff --git a/tests/functional/a/assign/assign_to_new_keyword.py b/tests/functional/a/assign_to_new_keyword.py similarity index 100% rename from tests/functional/a/assign/assign_to_new_keyword.py rename to tests/functional/a/assign_to_new_keyword.py diff --git a/tests/functional/a/assign/assign_to_new_keyword.rc b/tests/functional/a/assign_to_new_keyword.rc similarity index 100% rename from tests/functional/a/assign/assign_to_new_keyword.rc rename to tests/functional/a/assign_to_new_keyword.rc diff --git a/tests/functional/a/assign/assign_to_new_keyword.txt b/tests/functional/a/assign_to_new_keyword.txt similarity index 100% rename from tests/functional/a/assign/assign_to_new_keyword.txt rename to tests/functional/a/assign_to_new_keyword.txt diff --git a/tests/functional/a/assign/assigning_non_slot.py b/tests/functional/a/assigning/assigning_non_slot.py similarity index 100% rename from tests/functional/a/assign/assigning_non_slot.py rename to tests/functional/a/assigning/assigning_non_slot.py diff --git a/tests/functional/a/assign/assigning_non_slot.txt b/tests/functional/a/assigning/assigning_non_slot.txt similarity index 100% rename from tests/functional/a/assign/assigning_non_slot.txt rename to tests/functional/a/assigning/assigning_non_slot.txt diff --git a/tests/functional/a/assign/assigning_non_slot_4509.py b/tests/functional/a/assigning/assigning_non_slot_4509.py similarity index 100% rename from tests/functional/a/assign/assigning_non_slot_4509.py rename to tests/functional/a/assigning/assigning_non_slot_4509.py diff --git a/tests/functional/a/assign/assigning_non_slot_4509.rc b/tests/functional/a/assigning/assigning_non_slot_4509.rc similarity index 100% rename from tests/functional/a/assign/assigning_non_slot_4509.rc rename to tests/functional/a/assigning/assigning_non_slot_4509.rc diff --git a/tests/functional/a/assign/assigning_non_slot_4509.txt b/tests/functional/a/assigning/assigning_non_slot_4509.txt similarity index 100% rename from tests/functional/a/assign/assigning_non_slot_4509.txt rename to tests/functional/a/assigning/assigning_non_slot_4509.txt diff --git a/tests/functional/a/assign/assignment_expression.py b/tests/functional/a/assignment/assignment_expression.py similarity index 100% rename from tests/functional/a/assign/assignment_expression.py rename to tests/functional/a/assignment/assignment_expression.py diff --git a/tests/functional/a/assign/assignment_expression.rc b/tests/functional/a/assignment/assignment_expression.rc similarity index 100% rename from tests/functional/a/assign/assignment_expression.rc rename to tests/functional/a/assignment/assignment_expression.rc diff --git a/tests/functional/a/assign/assignment_expression.txt b/tests/functional/a/assignment/assignment_expression.txt similarity index 100% rename from tests/functional/a/assign/assignment_expression.txt rename to tests/functional/a/assignment/assignment_expression.txt diff --git a/tests/functional/a/assign/assignment_from_no_return.py b/tests/functional/a/assignment/assignment_from_no_return.py similarity index 100% rename from tests/functional/a/assign/assignment_from_no_return.py rename to tests/functional/a/assignment/assignment_from_no_return.py diff --git a/tests/functional/a/assign/assignment_from_no_return.txt b/tests/functional/a/assignment/assignment_from_no_return.txt similarity index 100% rename from tests/functional/a/assign/assignment_from_no_return.txt rename to tests/functional/a/assignment/assignment_from_no_return.txt diff --git a/tests/functional/a/assign/assignment_from_no_return_2.py b/tests/functional/a/assignment/assignment_from_no_return_2.py similarity index 100% rename from tests/functional/a/assign/assignment_from_no_return_2.py rename to tests/functional/a/assignment/assignment_from_no_return_2.py diff --git a/tests/functional/a/assign/assignment_from_no_return_2.txt b/tests/functional/a/assignment/assignment_from_no_return_2.txt similarity index 100% rename from tests/functional/a/assign/assignment_from_no_return_2.txt rename to tests/functional/a/assignment/assignment_from_no_return_2.txt diff --git a/tests/functional/a/assign/assignment_from_no_return_py3.py b/tests/functional/a/assignment/assignment_from_no_return_py3.py similarity index 100% rename from tests/functional/a/assign/assignment_from_no_return_py3.py rename to tests/functional/a/assignment/assignment_from_no_return_py3.py diff --git a/tests/functional/d/deprecated/dataclass_typecheck.py b/tests/functional/d/dataclass_typecheck.py similarity index 100% rename from tests/functional/d/deprecated/dataclass_typecheck.py rename to tests/functional/d/dataclass_typecheck.py diff --git a/tests/functional/d/deprecated/dataclass_typecheck.rc b/tests/functional/d/dataclass_typecheck.rc similarity index 100% rename from tests/functional/d/deprecated/dataclass_typecheck.rc rename to tests/functional/d/dataclass_typecheck.rc diff --git a/tests/functional/d/deprecated/dataclass_typecheck.txt b/tests/functional/d/dataclass_typecheck.txt similarity index 100% rename from tests/functional/d/deprecated/dataclass_typecheck.txt rename to tests/functional/d/dataclass_typecheck.txt diff --git a/tests/functional/i/import_error.py b/tests/functional/i/import_error.py index a2183fcfba..2ce63544ba 100644 --- a/tests/functional/i/import_error.py +++ b/tests/functional/i/import_error.py @@ -30,7 +30,7 @@ pass -from functional.s.syntax_error import toto # [no-name-in-module,syntax-error] +from functional.s.syntax.syntax_error import toto # [no-name-in-module,syntax-error] # Don't emit `import-error` or `no-name-in-module` diff --git a/tests/functional/i/import_error.txt b/tests/functional/i/import_error.txt index 4e7ab24235..28e94dc72e 100644 --- a/tests/functional/i/import_error.txt +++ b/tests/functional/i/import_error.txt @@ -1,5 +1,5 @@ import-error:3:0:3:22::Unable to import 'totally_missing':UNDEFINED import-error:21:4:21:26::Unable to import 'maybe_missing_2':UNDEFINED -no-name-in-module:33:0:33:42::No name 'syntax_error' in module 'functional.s':UNDEFINED -syntax-error:33:0:None:None::Cannot import 'functional.s.syntax_error' due to syntax error 'invalid syntax (, line 1)':UNDEFINED +no-name-in-module:33:0:33:49::No name 'syntax_error' in module 'functional.s.syntax':UNDEFINED +syntax-error:33:0:None:None::Cannot import 'functional.s.syntax.syntax_error' due to syntax error 'invalid syntax (, line 1)':UNDEFINED multiple-imports:78:0:78:15::Multiple imports on one line (foo, bar):UNDEFINED diff --git a/tests/functional/i/invalid/b/invalid_bool_returned.py b/tests/functional/i/invalid/invalid_bool_returned.py similarity index 100% rename from tests/functional/i/invalid/b/invalid_bool_returned.py rename to tests/functional/i/invalid/invalid_bool_returned.py diff --git a/tests/functional/i/invalid/b/invalid_bool_returned.txt b/tests/functional/i/invalid/invalid_bool_returned.txt similarity index 100% rename from tests/functional/i/invalid/b/invalid_bool_returned.txt rename to tests/functional/i/invalid/invalid_bool_returned.txt diff --git a/tests/functional/i/invalid/b/invalid_bytes_returned.py b/tests/functional/i/invalid/invalid_bytes_returned.py similarity index 100% rename from tests/functional/i/invalid/b/invalid_bytes_returned.py rename to tests/functional/i/invalid/invalid_bytes_returned.py diff --git a/tests/functional/i/invalid/b/invalid_bytes_returned.txt b/tests/functional/i/invalid/invalid_bytes_returned.txt similarity index 100% rename from tests/functional/i/invalid/b/invalid_bytes_returned.txt rename to tests/functional/i/invalid/invalid_bytes_returned.txt diff --git a/tests/functional/i/invalid/e/invalid_envvar_value.py b/tests/functional/i/invalid/invalid_envvar_value.py similarity index 100% rename from tests/functional/i/invalid/e/invalid_envvar_value.py rename to tests/functional/i/invalid/invalid_envvar_value.py diff --git a/tests/functional/i/invalid/e/invalid_envvar_value.txt b/tests/functional/i/invalid/invalid_envvar_value.txt similarity index 100% rename from tests/functional/i/invalid/e/invalid_envvar_value.txt rename to tests/functional/i/invalid/invalid_envvar_value.txt diff --git a/tests/functional/i/invalid/e/invalid_exceptions_caught.py b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_caught.py similarity index 100% rename from tests/functional/i/invalid/e/invalid_exceptions_caught.py rename to tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_caught.py diff --git a/tests/functional/i/invalid/e/invalid_exceptions_caught.rc b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_caught.rc similarity index 100% rename from tests/functional/i/invalid/e/invalid_exceptions_caught.rc rename to tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_caught.rc diff --git a/tests/functional/i/invalid/e/invalid_exceptions_caught.txt b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_caught.txt similarity index 100% rename from tests/functional/i/invalid/e/invalid_exceptions_caught.txt rename to tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_caught.txt diff --git a/tests/functional/i/invalid/e/invalid_exceptions_raised.py b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.py similarity index 100% rename from tests/functional/i/invalid/e/invalid_exceptions_raised.py rename to tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.py diff --git a/tests/functional/i/invalid/e/invalid_exceptions_raised.txt b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt similarity index 100% rename from tests/functional/i/invalid/e/invalid_exceptions_raised.txt rename to tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt diff --git a/tests/functional/i/invalid/g/invalid_getnewargs_ex_returned.py b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py similarity index 100% rename from tests/functional/i/invalid/g/invalid_getnewargs_ex_returned.py rename to tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py diff --git a/tests/functional/i/invalid/g/invalid_getnewargs_ex_returned.txt b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.txt similarity index 100% rename from tests/functional/i/invalid/g/invalid_getnewargs_ex_returned.txt rename to tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.txt diff --git a/tests/functional/i/invalid/g/invalid_getnewargs_returned.py b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py similarity index 100% rename from tests/functional/i/invalid/g/invalid_getnewargs_returned.py rename to tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py diff --git a/tests/functional/i/invalid/g/invalid_getnewargs_returned.txt b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.txt similarity index 100% rename from tests/functional/i/invalid/g/invalid_getnewargs_returned.txt rename to tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.txt diff --git a/tests/functional/i/invalid/l/invalid_length_hint_returned.py b/tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.py similarity index 100% rename from tests/functional/i/invalid/l/invalid_length_hint_returned.py rename to tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.py diff --git a/tests/functional/i/invalid/l/invalid_length_hint_returned.txt b/tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.txt similarity index 100% rename from tests/functional/i/invalid/l/invalid_length_hint_returned.txt rename to tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.txt diff --git a/tests/functional/i/invalid/l/invalid_length_returned.py b/tests/functional/i/invalid/invalid_length/invalid_length_returned.py similarity index 100% rename from tests/functional/i/invalid/l/invalid_length_returned.py rename to tests/functional/i/invalid/invalid_length/invalid_length_returned.py diff --git a/tests/functional/i/invalid/l/invalid_length_returned.txt b/tests/functional/i/invalid/invalid_length/invalid_length_returned.txt similarity index 100% rename from tests/functional/i/invalid/l/invalid_length_returned.txt rename to tests/functional/i/invalid/invalid_length/invalid_length_returned.txt diff --git a/tests/functional/i/invalid/m/invalid_metaclass.py b/tests/functional/i/invalid/invalid_metaclass.py similarity index 100% rename from tests/functional/i/invalid/m/invalid_metaclass.py rename to tests/functional/i/invalid/invalid_metaclass.py diff --git a/tests/functional/i/invalid/m/invalid_metaclass.txt b/tests/functional/i/invalid/invalid_metaclass.txt similarity index 100% rename from tests/functional/i/invalid/m/invalid_metaclass.txt rename to tests/functional/i/invalid/invalid_metaclass.txt diff --git a/tests/functional/i/invalid/m/invalid_metaclass_py3.py b/tests/functional/i/invalid/invalid_metaclass_py3.py similarity index 100% rename from tests/functional/i/invalid/m/invalid_metaclass_py3.py rename to tests/functional/i/invalid/invalid_metaclass_py3.py diff --git a/tests/functional/i/invalid/invalid_name_issue_3405.py b/tests/functional/i/invalid/invalid_name/invalid_name_issue_3405.py similarity index 100% rename from tests/functional/i/invalid/invalid_name_issue_3405.py rename to tests/functional/i/invalid/invalid_name/invalid_name_issue_3405.py diff --git a/tests/functional/i/invalid/invalid_name_issue_3405.rc b/tests/functional/i/invalid/invalid_name/invalid_name_issue_3405.rc similarity index 100% rename from tests/functional/i/invalid/invalid_name_issue_3405.rc rename to tests/functional/i/invalid/invalid_name/invalid_name_issue_3405.rc diff --git a/tests/functional/i/invalid/invalid_name_issue_3405.txt b/tests/functional/i/invalid/invalid_name/invalid_name_issue_3405.txt similarity index 100% rename from tests/functional/i/invalid/invalid_name_issue_3405.txt rename to tests/functional/i/invalid/invalid_name/invalid_name_issue_3405.txt diff --git a/tests/functional/i/invalid/invalid_name_module_level.py b/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py similarity index 100% rename from tests/functional/i/invalid/invalid_name_module_level.py rename to tests/functional/i/invalid/invalid_name/invalid_name_module_level.py diff --git a/tests/functional/i/invalid/invalid_name_module_level.txt b/tests/functional/i/invalid/invalid_name/invalid_name_module_level.txt similarity index 100% rename from tests/functional/i/invalid/invalid_name_module_level.txt rename to tests/functional/i/invalid/invalid_name/invalid_name_module_level.txt diff --git a/tests/functional/i/invalid/invalid_name_multinaming_style.py b/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.py similarity index 100% rename from tests/functional/i/invalid/invalid_name_multinaming_style.py rename to tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.py diff --git a/tests/functional/i/invalid/invalid_name_multinaming_style.rc b/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.rc similarity index 100% rename from tests/functional/i/invalid/invalid_name_multinaming_style.rc rename to tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.rc diff --git a/tests/functional/i/invalid/invalid_name_multinaming_style.txt b/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.txt similarity index 100% rename from tests/functional/i/invalid/invalid_name_multinaming_style.txt rename to tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.txt diff --git a/tests/functional/i/invalid/invalid_name_property.py b/tests/functional/i/invalid/invalid_name/invalid_name_property.py similarity index 100% rename from tests/functional/i/invalid/invalid_name_property.py rename to tests/functional/i/invalid/invalid_name/invalid_name_property.py diff --git a/tests/functional/i/invalid/invalid_name_property.rc b/tests/functional/i/invalid/invalid_name/invalid_name_property.rc similarity index 100% rename from tests/functional/i/invalid/invalid_name_property.rc rename to tests/functional/i/invalid/invalid_name/invalid_name_property.rc diff --git a/tests/functional/i/invalid/invalid_name_property.txt b/tests/functional/i/invalid/invalid_name/invalid_name_property.txt similarity index 100% rename from tests/functional/i/invalid/invalid_name_property.txt rename to tests/functional/i/invalid/invalid_name/invalid_name_property.txt diff --git a/tests/functional/i/invalid/s/invalid_sequence_index.py b/tests/functional/i/invalid/invalid_sequence_index.py similarity index 100% rename from tests/functional/i/invalid/s/invalid_sequence_index.py rename to tests/functional/i/invalid/invalid_sequence_index.py diff --git a/tests/functional/i/invalid/s/invalid_sequence_index.txt b/tests/functional/i/invalid/invalid_sequence_index.txt similarity index 100% rename from tests/functional/i/invalid/s/invalid_sequence_index.txt rename to tests/functional/i/invalid/invalid_sequence_index.txt diff --git a/tests/functional/i/invalid/s/invalid_slice_index.py b/tests/functional/i/invalid/invalid_slice_index.py similarity index 100% rename from tests/functional/i/invalid/s/invalid_slice_index.py rename to tests/functional/i/invalid/invalid_slice_index.py diff --git a/tests/functional/i/invalid/s/invalid_slice_index.txt b/tests/functional/i/invalid/invalid_slice_index.txt similarity index 100% rename from tests/functional/i/invalid/s/invalid_slice_index.txt rename to tests/functional/i/invalid/invalid_slice_index.txt diff --git a/tests/functional/i/invalid/s/invalid_star_assignment_target.py b/tests/functional/i/invalid/invalid_star_assignment_target.py similarity index 100% rename from tests/functional/i/invalid/s/invalid_star_assignment_target.py rename to tests/functional/i/invalid/invalid_star_assignment_target.py diff --git a/tests/functional/i/invalid/s/invalid_star_assignment_target.txt b/tests/functional/i/invalid/invalid_star_assignment_target.txt similarity index 100% rename from tests/functional/i/invalid/s/invalid_star_assignment_target.txt rename to tests/functional/i/invalid/invalid_star_assignment_target.txt diff --git a/tests/functional/i/invalid/s/invalid_str_returned.py b/tests/functional/i/invalid/invalid_str_returned.py similarity index 100% rename from tests/functional/i/invalid/s/invalid_str_returned.py rename to tests/functional/i/invalid/invalid_str_returned.py diff --git a/tests/functional/i/invalid/s/invalid_str_returned.txt b/tests/functional/i/invalid/invalid_str_returned.txt similarity index 100% rename from tests/functional/i/invalid/s/invalid_str_returned.txt rename to tests/functional/i/invalid/invalid_str_returned.txt diff --git a/tests/functional/m/member/membership_protocol.py b/tests/functional/m/membership_protocol.py similarity index 100% rename from tests/functional/m/member/membership_protocol.py rename to tests/functional/m/membership_protocol.py diff --git a/tests/functional/m/member/membership_protocol.txt b/tests/functional/m/membership_protocol.txt similarity index 100% rename from tests/functional/m/member/membership_protocol.txt rename to tests/functional/m/membership_protocol.txt diff --git a/tests/functional/m/member/membership_protocol_py3.py b/tests/functional/m/membership_protocol_py3.py similarity index 100% rename from tests/functional/m/member/membership_protocol_py3.py rename to tests/functional/m/membership_protocol_py3.py diff --git a/tests/functional/m/member/membership_protocol_py3.txt b/tests/functional/m/membership_protocol_py3.txt similarity index 100% rename from tests/functional/m/member/membership_protocol_py3.txt rename to tests/functional/m/membership_protocol_py3.txt diff --git a/tests/functional/n/name/namePresetCamelCase.py b/tests/functional/n/namePresetCamelCase.py similarity index 100% rename from tests/functional/n/name/namePresetCamelCase.py rename to tests/functional/n/namePresetCamelCase.py diff --git a/tests/functional/n/name/namePresetCamelCase.rc b/tests/functional/n/namePresetCamelCase.rc similarity index 100% rename from tests/functional/n/name/namePresetCamelCase.rc rename to tests/functional/n/namePresetCamelCase.rc diff --git a/tests/functional/n/name/namePresetCamelCase.txt b/tests/functional/n/namePresetCamelCase.txt similarity index 100% rename from tests/functional/n/name/namePresetCamelCase.txt rename to tests/functional/n/namePresetCamelCase.txt diff --git a/tests/functional/n/name/namedtuple_member_inference.py b/tests/functional/n/namedtuple_member_inference.py similarity index 100% rename from tests/functional/n/name/namedtuple_member_inference.py rename to tests/functional/n/namedtuple_member_inference.py diff --git a/tests/functional/n/name/namedtuple_member_inference.txt b/tests/functional/n/namedtuple_member_inference.txt similarity index 100% rename from tests/functional/n/name/namedtuple_member_inference.txt rename to tests/functional/n/namedtuple_member_inference.txt diff --git a/tests/functional/n/name/names_in__all__.py b/tests/functional/n/names_in__all__.py similarity index 100% rename from tests/functional/n/name/names_in__all__.py rename to tests/functional/n/names_in__all__.py diff --git a/tests/functional/n/name/names_in__all__.txt b/tests/functional/n/names_in__all__.txt similarity index 100% rename from tests/functional/n/name/names_in__all__.txt rename to tests/functional/n/names_in__all__.txt diff --git "a/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" "b/tests/functional/n/non_ascii_name/non_ascii_name_lo\305\202.py" similarity index 100% rename from "tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.py" rename to "tests/functional/n/non_ascii_name/non_ascii_name_lo\305\202.py" diff --git "a/tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" "b/tests/functional/n/non_ascii_name/non_ascii_name_lo\305\202.txt" similarity index 100% rename from "tests/functional/n/non_ascii_import/non_ascii_name_lo\305\202.txt" rename to "tests/functional/n/non_ascii_name/non_ascii_name_lo\305\202.txt" diff --git a/tests/functional/r/raise/raise_missing_from.py b/tests/functional/r/raise_missing_from.py similarity index 100% rename from tests/functional/r/raise/raise_missing_from.py rename to tests/functional/r/raise_missing_from.py diff --git a/tests/functional/r/raise/raise_missing_from.txt b/tests/functional/r/raise_missing_from.txt similarity index 100% rename from tests/functional/r/raise/raise_missing_from.txt rename to tests/functional/r/raise_missing_from.txt diff --git a/tests/functional/r/raise/raising_bad_type.py b/tests/functional/r/raising/raising_bad_type.py similarity index 100% rename from tests/functional/r/raise/raising_bad_type.py rename to tests/functional/r/raising/raising_bad_type.py diff --git a/tests/functional/r/raise/raising_bad_type.txt b/tests/functional/r/raising/raising_bad_type.txt similarity index 100% rename from tests/functional/r/raise/raising_bad_type.txt rename to tests/functional/r/raising/raising_bad_type.txt diff --git a/tests/functional/r/raise/raising_format_tuple.py b/tests/functional/r/raising/raising_format_tuple.py similarity index 100% rename from tests/functional/r/raise/raising_format_tuple.py rename to tests/functional/r/raising/raising_format_tuple.py diff --git a/tests/functional/r/raise/raising_format_tuple.txt b/tests/functional/r/raising/raising_format_tuple.txt similarity index 100% rename from tests/functional/r/raise/raising_format_tuple.txt rename to tests/functional/r/raising/raising_format_tuple.txt diff --git a/tests/functional/r/raise/raising_non_exception.py b/tests/functional/r/raising/raising_non_exception.py similarity index 100% rename from tests/functional/r/raise/raising_non_exception.py rename to tests/functional/r/raising/raising_non_exception.py diff --git a/tests/functional/r/raise/raising_non_exception.txt b/tests/functional/r/raising/raising_non_exception.txt similarity index 100% rename from tests/functional/r/raise/raising_non_exception.txt rename to tests/functional/r/raising/raising_non_exception.txt diff --git a/tests/functional/r/raise/raising_self.py b/tests/functional/r/raising/raising_self.py similarity index 100% rename from tests/functional/r/raise/raising_self.py rename to tests/functional/r/raising/raising_self.py diff --git a/tests/functional/r/redefined_argument_from_local.py b/tests/functional/r/redefined/redefined_argument_from_local.py similarity index 100% rename from tests/functional/r/redefined_argument_from_local.py rename to tests/functional/r/redefined/redefined_argument_from_local.py diff --git a/tests/functional/r/redefined_argument_from_local.txt b/tests/functional/r/redefined/redefined_argument_from_local.txt similarity index 100% rename from tests/functional/r/redefined_argument_from_local.txt rename to tests/functional/r/redefined/redefined_argument_from_local.txt diff --git a/tests/functional/r/redefined_builtin.py b/tests/functional/r/redefined/redefined_builtin.py similarity index 100% rename from tests/functional/r/redefined_builtin.py rename to tests/functional/r/redefined/redefined_builtin.py diff --git a/tests/functional/r/redefined_builtin.rc b/tests/functional/r/redefined/redefined_builtin.rc similarity index 100% rename from tests/functional/r/redefined_builtin.rc rename to tests/functional/r/redefined/redefined_builtin.rc diff --git a/tests/functional/r/redefined_builtin.txt b/tests/functional/r/redefined/redefined_builtin.txt similarity index 100% rename from tests/functional/r/redefined_builtin.txt rename to tests/functional/r/redefined/redefined_builtin.txt diff --git a/tests/functional/r/redefined_builtin_allowed.py b/tests/functional/r/redefined/redefined_builtin_allowed.py similarity index 100% rename from tests/functional/r/redefined_builtin_allowed.py rename to tests/functional/r/redefined/redefined_builtin_allowed.py diff --git a/tests/functional/r/redefined_builtin_allowed.rc b/tests/functional/r/redefined/redefined_builtin_allowed.rc similarity index 100% rename from tests/functional/r/redefined_builtin_allowed.rc rename to tests/functional/r/redefined/redefined_builtin_allowed.rc diff --git a/tests/functional/r/redefined_builtin_allowed.txt b/tests/functional/r/redefined/redefined_builtin_allowed.txt similarity index 100% rename from tests/functional/r/redefined_builtin_allowed.txt rename to tests/functional/r/redefined/redefined_builtin_allowed.txt diff --git a/tests/functional/r/redefined_except_handler.py b/tests/functional/r/redefined/redefined_except_handler.py similarity index 100% rename from tests/functional/r/redefined_except_handler.py rename to tests/functional/r/redefined/redefined_except_handler.py diff --git a/tests/functional/r/redefined_except_handler.txt b/tests/functional/r/redefined/redefined_except_handler.txt similarity index 100% rename from tests/functional/r/redefined_except_handler.txt rename to tests/functional/r/redefined/redefined_except_handler.txt diff --git a/tests/functional/r/redefined_outer_name_type_checking.py b/tests/functional/r/redefined/redefined_outer_name_type_checking.py similarity index 100% rename from tests/functional/r/redefined_outer_name_type_checking.py rename to tests/functional/r/redefined/redefined_outer_name_type_checking.py diff --git a/tests/functional/r/redefined_outer_name_type_checking.rc b/tests/functional/r/redefined/redefined_outer_name_type_checking.rc similarity index 100% rename from tests/functional/r/redefined_outer_name_type_checking.rc rename to tests/functional/r/redefined/redefined_outer_name_type_checking.rc diff --git a/tests/functional/r/redefined_slots.py b/tests/functional/r/redefined/redefined_slots.py similarity index 100% rename from tests/functional/r/redefined_slots.py rename to tests/functional/r/redefined/redefined_slots.py diff --git a/tests/functional/r/redefined_slots.txt b/tests/functional/r/redefined/redefined_slots.txt similarity index 100% rename from tests/functional/r/redefined_slots.txt rename to tests/functional/r/redefined/redefined_slots.txt diff --git a/tests/functional/r/regression/regression_too_many_arguments_2335.py b/tests/functional/r/regression_02/regression_too_many_arguments_2335.py similarity index 100% rename from tests/functional/r/regression/regression_too_many_arguments_2335.py rename to tests/functional/r/regression_02/regression_too_many_arguments_2335.py diff --git a/tests/functional/s/simplifiable/simplify_chained_comparison.py b/tests/functional/s/simplify_chained_comparison.py similarity index 100% rename from tests/functional/s/simplifiable/simplify_chained_comparison.py rename to tests/functional/s/simplify_chained_comparison.py diff --git a/tests/functional/s/simplifiable/simplify_chained_comparison.txt b/tests/functional/s/simplify_chained_comparison.txt similarity index 100% rename from tests/functional/s/simplifiable/simplify_chained_comparison.txt rename to tests/functional/s/simplify_chained_comparison.txt diff --git a/tests/functional/s/super/superfluous_parens.py b/tests/functional/s/superfluous_parens.py similarity index 100% rename from tests/functional/s/super/superfluous_parens.py rename to tests/functional/s/superfluous_parens.py diff --git a/tests/functional/s/super/superfluous_parens.txt b/tests/functional/s/superfluous_parens.txt similarity index 100% rename from tests/functional/s/super/superfluous_parens.txt rename to tests/functional/s/superfluous_parens.txt diff --git a/tests/functional/s/super/superfluous_parens_walrus_py38.py b/tests/functional/s/superfluous_parens_walrus_py38.py similarity index 100% rename from tests/functional/s/super/superfluous_parens_walrus_py38.py rename to tests/functional/s/superfluous_parens_walrus_py38.py diff --git a/tests/functional/s/super/superfluous_parens_walrus_py38.rc b/tests/functional/s/superfluous_parens_walrus_py38.rc similarity index 100% rename from tests/functional/s/super/superfluous_parens_walrus_py38.rc rename to tests/functional/s/superfluous_parens_walrus_py38.rc diff --git a/tests/functional/s/super/superfluous_parens_walrus_py38.txt b/tests/functional/s/superfluous_parens_walrus_py38.txt similarity index 100% rename from tests/functional/s/super/superfluous_parens_walrus_py38.txt rename to tests/functional/s/superfluous_parens_walrus_py38.txt diff --git a/tests/functional/s/symlink/_binding/__init__.py b/tests/functional/s/symlink/_binding/__init__.py new file mode 120000 index 0000000000..dda2f5dc5f --- /dev/null +++ b/tests/functional/s/symlink/_binding/__init__.py @@ -0,0 +1 @@ +../symlink_module/__init__.py \ No newline at end of file diff --git a/tests/functional/s/symlink/_binding/symlink_module.py b/tests/functional/s/symlink/_binding/symlink_module.py new file mode 120000 index 0000000000..af65afbc35 --- /dev/null +++ b/tests/functional/s/symlink/_binding/symlink_module.py @@ -0,0 +1 @@ +../symlink_module/symlink_module.py \ No newline at end of file diff --git a/tests/functional/s/symlink/binding/__init__.py b/tests/functional/s/symlink/binding/__init__.py deleted file mode 120000 index 70a3e97c5e..0000000000 --- a/tests/functional/s/symlink/binding/__init__.py +++ /dev/null @@ -1 +0,0 @@ -../module/__init__.py \ No newline at end of file diff --git a/tests/functional/s/symlink/binding/symlinked_module.py b/tests/functional/s/symlink/binding/symlinked_module.py deleted file mode 120000 index 09382e43ee..0000000000 --- a/tests/functional/s/symlink/binding/symlinked_module.py +++ /dev/null @@ -1 +0,0 @@ -../module/symlinked_module.py \ No newline at end of file diff --git a/tests/functional/s/symlink/module/__init__.py b/tests/functional/s/symlink/symlink_module/__init__.py similarity index 100% rename from tests/functional/s/symlink/module/__init__.py rename to tests/functional/s/symlink/symlink_module/__init__.py diff --git a/tests/functional/s/symlink/module/symlinked_module.py b/tests/functional/s/symlink/symlink_module/symlink_module.py similarity index 100% rename from tests/functional/s/symlink/module/symlinked_module.py rename to tests/functional/s/symlink/symlink_module/symlink_module.py diff --git a/tests/functional/s/syntax_error.py b/tests/functional/s/syntax/syntax_error.py similarity index 100% rename from tests/functional/s/syntax_error.py rename to tests/functional/s/syntax/syntax_error.py diff --git a/tests/functional/s/syntax_error.rc b/tests/functional/s/syntax/syntax_error.rc similarity index 100% rename from tests/functional/s/syntax_error.rc rename to tests/functional/s/syntax/syntax_error.rc diff --git a/tests/functional/s/syntax_error.txt b/tests/functional/s/syntax/syntax_error.txt similarity index 100% rename from tests/functional/s/syntax_error.txt rename to tests/functional/s/syntax/syntax_error.txt diff --git a/tests/functional/s/syntax_error_jython.py b/tests/functional/s/syntax/syntax_error_jython.py similarity index 100% rename from tests/functional/s/syntax_error_jython.py rename to tests/functional/s/syntax/syntax_error_jython.py diff --git a/tests/functional/s/syntax_error_jython.rc b/tests/functional/s/syntax/syntax_error_jython.rc similarity index 100% rename from tests/functional/s/syntax_error_jython.rc rename to tests/functional/s/syntax/syntax_error_jython.rc diff --git a/tests/functional/s/syntax_error_jython.txt b/tests/functional/s/syntax/syntax_error_jython.txt similarity index 100% rename from tests/functional/s/syntax_error_jython.txt rename to tests/functional/s/syntax/syntax_error_jython.txt diff --git a/tests/functional/u/unbalanced_tuple_unpacking.py b/tests/functional/u/unbalanced_tuple_unpacking.py index 20e3c29288..d3816f9a9b 100644 --- a/tests/functional/u/unbalanced_tuple_unpacking.py +++ b/tests/functional/u/unbalanced_tuple_unpacking.py @@ -1,7 +1,7 @@ """Check possible unbalanced tuple unpacking """ from __future__ import absolute_import from typing import NamedTuple -from functional.u.unpacking import unpack +from functional.u.unpacking.unpacking import unpack # pylint: disable=missing-class-docstring, missing-function-docstring, using-constant-test, useless-object-inheritance,import-outside-toplevel diff --git a/tests/functional/u/unbalanced_tuple_unpacking.txt b/tests/functional/u/unbalanced_tuple_unpacking.txt index d2df38cfd7..6298dd5315 100644 --- a/tests/functional/u/unbalanced_tuple_unpacking.txt +++ b/tests/functional/u/unbalanced_tuple_unpacking.txt @@ -1,8 +1,8 @@ unbalanced-tuple-unpacking:11:4:11:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED unbalanced-tuple-unpacking:17:4:17:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED unbalanced-tuple-unpacking:23:4:23:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:82:4:82:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED -unbalanced-tuple-unpacking:96:8:96:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:82:4:82:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED +unbalanced-tuple-unpacking:96:8:96:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED unbalanced-tuple-unpacking:140:8:140:43:MyClass.sum_unpack_3_into_4:"Possible unbalanced tuple unpacking with sequence defined at line 128: left side has 4 label(s), right side has 3 value(s)":UNDEFINED unbalanced-tuple-unpacking:145:8:145:28:MyClass.sum_unpack_3_into_2:"Possible unbalanced tuple unpacking with sequence defined at line 128: left side has 2 label(s), right side has 3 value(s)":UNDEFINED unbalanced-tuple-unpacking:157:0:157:24::"Possible unbalanced tuple unpacking with sequence defined at line 151: left side has 2 label(s), right side has 0 value(s)":UNDEFINED diff --git a/tests/functional/u/unpacking.py b/tests/functional/u/unpacking/unpacking.py similarity index 100% rename from tests/functional/u/unpacking.py rename to tests/functional/u/unpacking/unpacking.py diff --git a/tests/functional/u/unpacking_generalizations.py b/tests/functional/u/unpacking/unpacking_generalizations.py similarity index 100% rename from tests/functional/u/unpacking_generalizations.py rename to tests/functional/u/unpacking/unpacking_generalizations.py diff --git a/tests/functional/u/unpacking_generalizations.txt b/tests/functional/u/unpacking/unpacking_generalizations.txt similarity index 100% rename from tests/functional/u/unpacking_generalizations.txt rename to tests/functional/u/unpacking/unpacking_generalizations.txt diff --git a/tests/functional/u/unpacking_non_sequence.py b/tests/functional/u/unpacking/unpacking_non_sequence.py similarity index 98% rename from tests/functional/u/unpacking_non_sequence.py rename to tests/functional/u/unpacking/unpacking_non_sequence.py index e9c23b388d..0299af762d 100644 --- a/tests/functional/u/unpacking_non_sequence.py +++ b/tests/functional/u/unpacking/unpacking_non_sequence.py @@ -3,7 +3,7 @@ # pylint: disable=too-few-public-methods, invalid-name, attribute-defined-outside-init, unused-variable # pylint: disable=using-constant-test, no-init, missing-docstring, wrong-import-order,wrong-import-position,no-else-return, useless-object-inheritance from os import rename as nonseq_func -from functional.u.unpacking import nonseq +from functional.u.unpacking.unpacking import nonseq from typing import NamedTuple __revision__ = 0 diff --git a/tests/functional/u/unpacking_non_sequence.txt b/tests/functional/u/unpacking/unpacking_non_sequence.txt similarity index 86% rename from tests/functional/u/unpacking_non_sequence.txt rename to tests/functional/u/unpacking/unpacking_non_sequence.txt index d657af1776..c3c65050cb 100644 --- a/tests/functional/u/unpacking_non_sequence.txt +++ b/tests/functional/u/unpacking/unpacking_non_sequence.txt @@ -2,8 +2,8 @@ unpacking-non-sequence:78:0:78:15::Attempting to unpack a non-sequence defined a unpacking-non-sequence:79:0:79:17::Attempting to unpack a non-sequence:UNDEFINED unpacking-non-sequence:80:0:80:11::Attempting to unpack a non-sequence None:UNDEFINED unpacking-non-sequence:81:0:81:8::Attempting to unpack a non-sequence 1:UNDEFINED -unpacking-non-sequence:82:0:82:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED -unpacking-non-sequence:83:0:83:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED +unpacking-non-sequence:82:0:82:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking.unpacking:UNDEFINED +unpacking-non-sequence:83:0:83:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking.unpacking:UNDEFINED unpacking-non-sequence:84:0:84:18::Attempting to unpack a non-sequence:UNDEFINED unpacking-non-sequence:99:8:99:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 75:UNDEFINED unpacking-non-sequence:100:8:100:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED diff --git a/tests/functional/u/unpacking_non_sequence_py37.py b/tests/functional/u/unpacking/unpacking_non_sequence_py37.py similarity index 100% rename from tests/functional/u/unpacking_non_sequence_py37.py rename to tests/functional/u/unpacking/unpacking_non_sequence_py37.py diff --git a/tests/functional/u/unpacking_non_sequence_py37.rc b/tests/functional/u/unpacking/unpacking_non_sequence_py37.rc similarity index 100% rename from tests/functional/u/unpacking_non_sequence_py37.rc rename to tests/functional/u/unpacking/unpacking_non_sequence_py37.rc diff --git a/tests/functional/u/use/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py similarity index 100% rename from tests/functional/u/use/used_before_assignment.py rename to tests/functional/u/used/used_before_assignment.py diff --git a/tests/functional/u/use/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment.txt rename to tests/functional/u/used/used_before_assignment.txt diff --git a/tests/functional/u/use/used_before_assignment_488.py b/tests/functional/u/used/used_before_assignment_488.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_488.py rename to tests/functional/u/used/used_before_assignment_488.py diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py b/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.py rename to tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py diff --git a/tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt b/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_except_handler_for_try_with_return.txt rename to tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.txt diff --git a/tests/functional/u/use/used_before_assignment_filtered_comprehension.py b/tests/functional/u/used/used_before_assignment_filtered_comprehension.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_filtered_comprehension.py rename to tests/functional/u/used/used_before_assignment_filtered_comprehension.py diff --git a/tests/functional/u/use/used_before_assignment_issue1081.py b/tests/functional/u/used/used_before_assignment_issue1081.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue1081.py rename to tests/functional/u/used/used_before_assignment_issue1081.py diff --git a/tests/functional/u/use/used_before_assignment_issue1081.txt b/tests/functional/u/used/used_before_assignment_issue1081.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue1081.txt rename to tests/functional/u/used/used_before_assignment_issue1081.txt diff --git a/tests/functional/u/use/used_before_assignment_issue2615.py b/tests/functional/u/used/used_before_assignment_issue2615.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue2615.py rename to tests/functional/u/used/used_before_assignment_issue2615.py diff --git a/tests/functional/u/use/used_before_assignment_issue2615.txt b/tests/functional/u/used/used_before_assignment_issue2615.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue2615.txt rename to tests/functional/u/used/used_before_assignment_issue2615.txt diff --git a/tests/functional/u/use/used_before_assignment_issue4761.py b/tests/functional/u/used/used_before_assignment_issue4761.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue4761.py rename to tests/functional/u/used/used_before_assignment_issue4761.py diff --git a/tests/functional/u/use/used_before_assignment_issue4761.txt b/tests/functional/u/used/used_before_assignment_issue4761.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue4761.txt rename to tests/functional/u/used/used_before_assignment_issue4761.txt diff --git a/tests/functional/u/use/used_before_assignment_issue626.py b/tests/functional/u/used/used_before_assignment_issue626.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue626.py rename to tests/functional/u/used/used_before_assignment_issue626.py diff --git a/tests/functional/u/use/used_before_assignment_issue626.txt b/tests/functional/u/used/used_before_assignment_issue626.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue626.txt rename to tests/functional/u/used/used_before_assignment_issue626.txt diff --git a/tests/functional/u/use/used_before_assignment_issue85.py b/tests/functional/u/used/used_before_assignment_issue85.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue85.py rename to tests/functional/u/used/used_before_assignment_issue85.py diff --git a/tests/functional/u/use/used_before_assignment_issue85.txt b/tests/functional/u/used/used_before_assignment_issue85.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue85.txt rename to tests/functional/u/used/used_before_assignment_issue85.txt diff --git a/tests/functional/u/use/used_before_assignment_issue853.py b/tests/functional/u/used/used_before_assignment_issue853.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_issue853.py rename to tests/functional/u/used/used_before_assignment_issue853.py diff --git a/tests/functional/u/use/used_before_assignment_nonlocal.py b/tests/functional/u/used/used_before_assignment_nonlocal.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_nonlocal.py rename to tests/functional/u/used/used_before_assignment_nonlocal.py diff --git a/tests/functional/u/use/used_before_assignment_nonlocal.txt b/tests/functional/u/used/used_before_assignment_nonlocal.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_nonlocal.txt rename to tests/functional/u/used/used_before_assignment_nonlocal.txt diff --git a/tests/functional/u/use/used_before_assignment_py37.py b/tests/functional/u/used/used_before_assignment_py37.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_py37.py rename to tests/functional/u/used/used_before_assignment_py37.py diff --git a/tests/functional/u/use/used_before_assignment_py37.rc b/tests/functional/u/used/used_before_assignment_py37.rc similarity index 100% rename from tests/functional/u/use/used_before_assignment_py37.rc rename to tests/functional/u/used/used_before_assignment_py37.rc diff --git a/tests/functional/u/use/used_before_assignment_py37.txt b/tests/functional/u/used/used_before_assignment_py37.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_py37.txt rename to tests/functional/u/used/used_before_assignment_py37.txt diff --git a/tests/functional/u/use/used_before_assignment_type_annotations.py b/tests/functional/u/used/used_before_assignment_type_annotations.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_type_annotations.py rename to tests/functional/u/used/used_before_assignment_type_annotations.py diff --git a/tests/functional/u/use/used_before_assignment_type_annotations.txt b/tests/functional/u/used/used_before_assignment_type_annotations.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_type_annotations.txt rename to tests/functional/u/used/used_before_assignment_type_annotations.txt diff --git a/tests/functional/u/use/used_before_assignment_typing.py b/tests/functional/u/used/used_before_assignment_typing.py similarity index 100% rename from tests/functional/u/use/used_before_assignment_typing.py rename to tests/functional/u/used/used_before_assignment_typing.py diff --git a/tests/functional/u/use/used_before_assignment_typing.txt b/tests/functional/u/used/used_before_assignment_typing.txt similarity index 100% rename from tests/functional/u/use/used_before_assignment_typing.txt rename to tests/functional/u/used/used_before_assignment_typing.txt diff --git a/tests/functional/u/use/used_prior_global_declaration.py b/tests/functional/u/used/used_prior_global_declaration.py similarity index 100% rename from tests/functional/u/use/used_prior_global_declaration.py rename to tests/functional/u/used/used_prior_global_declaration.py diff --git a/tests/functional/u/use/used_prior_global_declaration.txt b/tests/functional/u/used/used_prior_global_declaration.txt similarity index 100% rename from tests/functional/u/use/used_prior_global_declaration.txt rename to tests/functional/u/used/used_prior_global_declaration.txt diff --git a/tests/functional/u/use/using_constant_test.py b/tests/functional/u/using_constant_test.py similarity index 100% rename from tests/functional/u/use/using_constant_test.py rename to tests/functional/u/using_constant_test.py diff --git a/tests/functional/u/use/using_constant_test.txt b/tests/functional/u/using_constant_test.txt similarity index 100% rename from tests/functional/u/use/using_constant_test.txt rename to tests/functional/u/using_constant_test.txt From ea4560c53655d5c932ec402677877e35a4b7e7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 9 Feb 2022 00:06:42 +0100 Subject: [PATCH 209/357] Check functional test directory for correct structure --- .../functional/find_functional_tests.py | 68 ++++++++++++++++--- tests/test_functional_directories.py | 12 ++++ tests/testutils/data/u/use/use_len.py | 0 tests/testutils/data/u/use/using_dir.py | 0 tests/testutils/data/u/use_dir.py | 0 tests/testutils/data/u/using/using_len.py | 0 tests/testutils/test_functional_testutils.py | 11 ++- 7 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 tests/test_functional_directories.py create mode 100644 tests/testutils/data/u/use/use_len.py create mode 100644 tests/testutils/data/u/use/using_dir.py create mode 100644 tests/testutils/data/u/use_dir.py create mode 100644 tests/testutils/data/u/using/using_len.py diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py index 610b05db1a..cddf0ade73 100644 --- a/pylint/testutils/functional/find_functional_tests.py +++ b/pylint/testutils/functional/find_functional_tests.py @@ -3,13 +3,29 @@ import os from pathlib import Path -from typing import List, Union +from typing import List, Set, Union from pylint.testutils.functional.test_file import FunctionalTestFile -# 'Wet finger' number of files that are reasonable to display by an IDE -# 'Wet finger' as in 'in my settings there are precisely this many'. REASONABLY_DISPLAYABLE_VERTICALLY = 48 +"""'Wet finger' number of files that are reasonable to display by an IDE.""" +SHOULD_BE_IN_THE_SAME_DIRECTORY = 5 +"""'Wet finger' as in 'in my settings there are precisely this many'.""" + +IGNORED_PARENT_DIRS = { + "deprecated_relative_import", + "ext", + "regression", + "regression_02", +} +"""Direct parent directories that should be ignored.""" + +IGNORED_PARENT_PARENT_DIRS = { + "docparams", + "deprecated_relative_import", + "ext", +} +"""Parents of direct parent directories that should be ignored.""" def get_functional_test_files_from_directory( @@ -17,15 +33,51 @@ def get_functional_test_files_from_directory( ) -> List[FunctionalTestFile]: """Get all functional tests in the input_dir.""" suite = [] + + _check_functional_tests_structure(Path(input_dir)) + for dirpath, _, filenames in os.walk(input_dir): if dirpath.endswith("__pycache__"): continue - - assert ( - len(filenames) <= REASONABLY_DISPLAYABLE_VERTICALLY - ), f"{dirpath} contains too many functional tests files." - for filename in filenames: if filename != "__init__.py" and filename.endswith(".py"): suite.append(FunctionalTestFile(dirpath, filename)) return suite + + +def _check_functional_tests_structure(directory: Path) -> None: + """Check if test directories follow correct file/folder structure.""" + # Ignore underscored directories + if Path(directory).stem.startswith("_"): + return + + files: Set[Path] = set() + dirs: Set[Path] = set() + + # Collect all subdirectories and files in directory + for file_or_dir in directory.iterdir(): + if file_or_dir.is_file(): + if file_or_dir.suffix == ".py" and not file_or_dir.stem.startswith("_"): + files.add(file_or_dir) + elif file_or_dir.is_dir(): + dirs.add(file_or_dir) + _check_functional_tests_structure(file_or_dir) + + assert len(files) <= REASONABLY_DISPLAYABLE_VERTICALLY, ( + f"{directory} contains too many functional tests files " + + f"({len(files)} > {REASONABLY_DISPLAYABLE_VERTICALLY})." + ) + + for file in files: + possible_dir = file.parent / file.stem.split("_")[0] + assert not possible_dir.exists(), f"{file} should go in {possible_dir}." + + # Exclude some directories as they follow a different structure + if ( + not len(file.parent.stem) == 1 # First letter subdirectories + and file.parent.stem not in IGNORED_PARENT_DIRS + and file.parent.parent.stem not in IGNORED_PARENT_PARENT_DIRS + ): + assert file.stem.startswith( + file.parent.stem + ), f"{file} should not go in {file.parent}" diff --git a/tests/test_functional_directories.py b/tests/test_functional_directories.py new file mode 100644 index 0000000000..a01f19cdd1 --- /dev/null +++ b/tests/test_functional_directories.py @@ -0,0 +1,12 @@ +"""Test that the directory structure of the functional tests is correct.""" +from pathlib import Path + +from pylint.testutils.functional.find_functional_tests import ( + get_functional_test_files_from_directory, +) + + +def test_directories() -> None: + """Test that the directory structure of the functional tests is correct.""" + functional_dir = Path(__file__).parent / "functional" + get_functional_test_files_from_directory(functional_dir) diff --git a/tests/testutils/data/u/use/use_len.py b/tests/testutils/data/u/use/use_len.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/testutils/data/u/use/using_dir.py b/tests/testutils/data/u/use/using_dir.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/testutils/data/u/use_dir.py b/tests/testutils/data/u/use_dir.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/testutils/data/u/using/using_len.py b/tests/testutils/data/u/using/using_len.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/testutils/test_functional_testutils.py b/tests/testutils/test_functional_testutils.py index c1e852e15b..032f6ac120 100644 --- a/tests/testutils/test_functional_testutils.py +++ b/tests/testutils/test_functional_testutils.py @@ -8,7 +8,10 @@ import pytest from pylint import testutils -from pylint.testutils.functional import FunctionalTestFile +from pylint.testutils.functional import ( + FunctionalTestFile, + get_functional_test_files_from_directory, +) HERE = Path(__file__).parent DATA_DIRECTORY = HERE / "data" @@ -19,3 +22,9 @@ def test_parsing_of_pylintrc_init_hook() -> None: with pytest.raises(RuntimeError): test_file = FunctionalTestFile(str(DATA_DIRECTORY), "init_hook.py") testutils.LintModuleTest(test_file) + + +def test_get_functional_test_files_from_directory() -> None: + """Test that we correctly check the functional test directory structures.""" + with pytest.raises(AssertionError, match="using_dir.py should not go in"): + get_functional_test_files_from_directory(DATA_DIRECTORY) From b31dd5623966069386b9df3ccc08e2834aca621d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 11 Feb 2022 09:51:22 +0100 Subject: [PATCH 210/357] Update Testing docs to reflect enforced folder structure (#5792) * Update Testing docs to reflect enforced folder structure * Some formatting changes --- doc/development_guide/testing.rst | 32 +++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/doc/development_guide/testing.rst b/doc/development_guide/testing.rst index c75293d27e..af14ad4aba 100644 --- a/doc/development_guide/testing.rst +++ b/doc/development_guide/testing.rst @@ -83,12 +83,32 @@ can have sections of Pylint's configuration. The .rc file can also contain a section ``[testoptions]`` to pass options for the functional test runner. The following options are currently supported: - "min_pyver": Minimal python version required to run the test - "max_pyver": Python version from which the test won't be run. If the last supported version is 3.9 this setting should be set to 3.10. - "min_pyver_end_position": Minimal python version required to check the end_line and end_column attributes of the message - "requires": Packages required to be installed locally to run the test - "except_implementations": List of python implementations on which the test should not run - "exclude_platforms": List of operating systems on which the test should not run +- "min_pyver": Minimal python version required to run the test +- "max_pyver": Python version from which the test won't be run. If the last supported version is 3.9 this setting should be set to 3.10. +- "min_pyver_end_position": Minimal python version required to check the end_line and end_column attributes of the message +- "requires": Packages required to be installed locally to run the test +- "except_implementations": List of python implementations on which the test should not run +- "exclude_platforms": List of operating systems on which the test should not run + +**Functional test file locations** + +For existing checkers, new test cases should preferably be appended to the existing test file. +For new checkers, a new file ``new_checker_message.py`` should be created (Note the use of +underscores). This file should then be placed in the ``test/functional/n`` sub-directory. + +Some additional notes: + +- If the checker is part of an extension the test should go in ``test/functional/ext/extension_name`` +- If the test is a regression test it should go in ``test/r/regression`` or ``test/r/regression_02``. + The file name should start with ``regression_``. +- For some sub-directories, such as ``test/functional/u``, there are additional sub-directories (``test/functional/u/use``). + Please check if your test file should be placed in any of these directories. It should be placed there + if the sub-directory name matches the word before the first underscore of your test file name. + +The folder structure is enforced when running the test suite, so you might be directed to put the file +in a different sub-directory. + +**Running and updating functional tests** During development, it's sometimes helpful to run all functional tests in your current environment in order to have faster feedback. Run from Pylint root directory with:: From d9328ac60784b3288d8d912dc677298591f1d714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 14 Feb 2022 12:35:57 +0100 Subject: [PATCH 211/357] Make ``lru-cache-decorating-method`` look for ``maxsize`` argument (#5791) Co-authored-by: Pierre Sassoulas --- diofant | 1 + pylint/checkers/stdlib.py | 24 +++++-- .../l/lru_cache_decorating_method.py | 62 +++++++++++++++++++ .../l/lru_cache_decorating_method.txt | 22 ++++--- 4 files changed, 93 insertions(+), 16 deletions(-) create mode 160000 diofant diff --git a/diofant b/diofant new file mode 160000 index 0000000000..6e280e9d0c --- /dev/null +++ b/diofant @@ -0,0 +1 @@ +Subproject commit 6e280e9d0c974a239f850b95e0162f1dd209da20 diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 1a0fab57ea..fe6113953e 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -454,12 +454,12 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "from code that is not actively being debugged.", ), "W1516": ( - "lru_cache shouldn't be used on a method as it creates memory leaks", + "'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self'", "lru-cache-decorating-method", "By decorating a method with lru_cache the 'self' argument will be linked to " - "to the lru_cache function and therefore never garbage collected. Unless your instance " + "the lru_cache function and therefore never garbage collected. Unless your instance " "will never need to be garbage collected (singleton) it is recommended to refactor " - "code to avoid this pattern.", + "code to avoid this pattern or add a maxsize to the cache.", ), } @@ -598,9 +598,21 @@ def _check_lru_cache_decorators(self, decorators: nodes.Decorators) -> None: q_name = infered_node.qname() if q_name in NON_INSTANCE_METHODS: return - if q_name in LRU_CACHE: - lru_cache_nodes.append(d_node) - break + if q_name not in LRU_CACHE: + return + + # Check if there is a maxsize argument to the call + if isinstance(d_node, nodes.Call): + try: + utils.get_argument_from_call( + d_node, position=0, keyword="maxsize" + ) + return + except utils.NoSuchArgumentError: + pass + + lru_cache_nodes.append(d_node) + break except astroid.InferenceError: pass for lru_cache_node in lru_cache_nodes: diff --git a/tests/functional/l/lru_cache_decorating_method.py b/tests/functional/l/lru_cache_decorating_method.py index ea9e994fbd..67b648fcd1 100644 --- a/tests/functional/l/lru_cache_decorating_method.py +++ b/tests/functional/l/lru_cache_decorating_method.py @@ -61,3 +61,65 @@ def my_func(self, param): @aliased_cache() # [lru-cache-decorating-method] def my_func(self, param): return param + 1 + + +class MyClassWithMethodsAndMaxSize: + @lru_cache(maxsize=1) + def my_func(self, param): + return param + 1 + + @lru_cache(maxsize=1) + def my_func(self, param): + return param + 1 + + @lru_cache(typed=True) # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @lru_cache(typed=True) # [lru-cache-decorating-method] + def my_func(self, param): + return param + 1 + + @lru_cache(1) + def my_func(self, param): + return param + 1 + + @lru_cache(1) + def my_func(self, param): + return param + 1 + + @lru_cache(typed=True, maxsize=1) + def my_func(self, param): + return param + 1 + + @lru_cache(typed=True, maxsize=1) + def my_func(self, param): + return param + 1 + + @functools.lru_cache(1) + def my_func(self, param): + return param + 1 + + @aliased_functools.lru_cache(1) + def my_func(self, param): + return param + 1 + + @aliased_cache(1) + def my_func(self, param): + return param + 1 + + @lru_cache(1) + @staticmethod + def my_func(param): + return param + 1 + + @lru_cache(1) + @classmethod + def my_func(cls, param): + return param + 1 + + # Check double decorating to check robustness of checker itself + @aliased_cache(1) + @aliased_cache(1) + def my_func(self, param): + return param + 1 diff --git a/tests/functional/l/lru_cache_decorating_method.txt b/tests/functional/l/lru_cache_decorating_method.txt index 8186d319f5..97779750d9 100644 --- a/tests/functional/l/lru_cache_decorating_method.txt +++ b/tests/functional/l/lru_cache_decorating_method.txt @@ -1,10 +1,12 @@ -lru-cache-decorating-method:27:5:27:14:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:31:5:31:24:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:35:5:35:32:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:39:5:39:18:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:43:5:43:16:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:47:5:47:26:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:51:5:51:34:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:55:5:55:20:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:60:5:60:20:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE -lru-cache-decorating-method:61:5:61:20:MyClassWithMethods.my_func:lru_cache shouldn't be used on a method as it creates memory leaks:INFERENCE +lru-cache-decorating-method:27:5:27:14:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:31:5:31:24:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:35:5:35:32:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:39:5:39:18:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:43:5:43:16:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:47:5:47:26:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:51:5:51:34:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:55:5:55:20:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:60:5:60:20:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:61:5:61:20:MyClassWithMethods.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:75:5:75:26:MyClassWithMethodsAndMaxSize.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE +lru-cache-decorating-method:79:5:79:26:MyClassWithMethodsAndMaxSize.my_func:'lru_cache' without 'maxsize' will keep all method args alive indefinitely, including 'self':INFERENCE From 8c0062f5ac0cfd80a74568e36d1e68e5a128c7f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 14 Feb 2022 12:37:46 +0100 Subject: [PATCH 212/357] Remove erroneously added ``diofant`` submodule --- diofant | 1 - 1 file changed, 1 deletion(-) delete mode 160000 diofant diff --git a/diofant b/diofant deleted file mode 160000 index 6e280e9d0c..0000000000 --- a/diofant +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6e280e9d0c974a239f850b95e0162f1dd209da20 From 67055f4221324a5aba057c0e7e3a5ffdc41a7e35 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 17 Feb 2022 10:27:29 -0500 Subject: [PATCH 213/357] Fix `used-before-assignment` false positive for except handler names shared by comprehension test (#5818) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/variables.py | 7 ++++++- tests/functional/u/used/used_before_assignment_issue626.py | 7 +++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index e27ef4cb27..b42d81a8f9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -287,6 +287,11 @@ Release date: TBA Closes #4716 +* Fix false positive for ``used-before-assignment`` when an except handler + shares a name with a test in a filtered comprehension. + + Closes #5817 + * Fix crash in ``unnecessary-dict-index-lookup`` checker if the output of ``items()`` is assigned to a 1-tuple. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 57afc70fa2..fa5b162ce8 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -184,6 +184,11 @@ Other Changes Closes #4716 +* Fix false positive for ``used-before-assignment`` when an except handler + shares a name with a test in a filtered comprehension. + + Closes #5817 + * Fix a crash in ``unused-private-member`` checker when analyzing code using ``type(self)`` in bound methods. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 90eee481f0..836d1947e3 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -646,7 +646,12 @@ def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]: found_nodes = None # Filter out assignments in ExceptHandlers that node is not contained in - if found_nodes: + # unless this is a test in a filtered comprehension + # Example: [e for e in range(3) if e] <--- followed by except e: + if found_nodes and ( + not isinstance(parent_node, nodes.Comprehension) + or node not in parent_node.ifs + ): found_nodes = [ n for n in found_nodes diff --git a/tests/functional/u/used/used_before_assignment_issue626.py b/tests/functional/u/used/used_before_assignment_issue626.py index c98632b7f7..cb9fc100e6 100644 --- a/tests/functional/u/used/used_before_assignment_issue626.py +++ b/tests/functional/u/used/used_before_assignment_issue626.py @@ -42,3 +42,10 @@ def main4(): pass print(e) # [used-before-assignment] + + +def main5(): + try: + print([e for e in range(3) if e]) + except ValueError as e: + print(e) From 3253f4bba04415f125132f281208ce538712ab34 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Thu, 17 Feb 2022 15:35:07 -0500 Subject: [PATCH 214/357] Fix #5112: Prevent `used-before-assignment` if named expression found first in container (#5812) Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 +++ doc/whatsnew/2.13.rst | 5 +++ pylint/checkers/variables.py | 44 ++++++++++--------- .../u/undefined/undefined_variable_py38.py | 14 ++++++ .../u/undefined/undefined_variable_py38.txt | 1 + 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index b42d81a8f9..6d1a12bcf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -205,6 +205,11 @@ Release date: TBA Closes #367 +* Fixed a false positive for ``used-before-assignment`` when a named expression + appears as the first value in a container. + + Closes #5112 + * ``used-before-assignment`` now assumes that assignments in except blocks may not have occurred and warns accordingly. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index fa5b162ce8..676860ee54 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -246,6 +246,11 @@ Other Changes Closes #3793 +* Fixed a false positive for ``used-before-assignment`` when a named expression + appears as the first value in a container. + + Closes #5112 + * Fixed false positive for ``used-before-assignment`` with self-referential type annotation in conditional statements within class methods. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 836d1947e3..1781866aa2 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1976,7 +1976,7 @@ def _is_variable_violation( # same line as the function definition maybe_before_assign = False elif ( - isinstance( # pylint: disable=too-many-boolean-expressions + isinstance( defstmt, ( nodes.Assign, @@ -1986,26 +1986,7 @@ def _is_variable_violation( nodes.Return, ), ) - and ( - isinstance(defstmt.value, nodes.IfExp) - or ( - isinstance(defstmt.value, nodes.Lambda) - and isinstance(defstmt.value.body, nodes.IfExp) - ) - or ( - isinstance(defstmt.value, nodes.Call) - and ( - any( - isinstance(kwarg.value, nodes.IfExp) - for kwarg in defstmt.value.keywords - ) - or any( - isinstance(arg, nodes.IfExp) - for arg in defstmt.value.args - ) - ) - ) - ) + and VariablesChecker._maybe_used_and_assigned_at_once(defstmt) and frame is defframe and defframe.parent_of(node) and stmt is defstmt @@ -2079,6 +2060,27 @@ def _is_variable_violation( return maybe_before_assign, annotation_return, use_outer_definition + @staticmethod + def _maybe_used_and_assigned_at_once(defstmt: nodes.Statement) -> bool: + """Check if `defstmt` has the potential to use and assign a name in the + same statement. + """ + if isinstance(defstmt.value, nodes.BaseContainer) and defstmt.value.elts: + # The assignment must happen as part of the first element + # e.g. "assert (x:= True), x" + # NOT "assert x, (x:= True)" + value = defstmt.value.elts[0] + else: + value = defstmt.value + if isinstance(value, nodes.IfExp): + return True + if isinstance(value, nodes.Lambda) and isinstance(value.body, nodes.IfExp): + return True + return isinstance(value, nodes.Call) and ( + any(isinstance(kwarg.value, nodes.IfExp) for kwarg in value.keywords) + or any(isinstance(arg, nodes.IfExp) for arg in value.args) + ) + @staticmethod def _is_only_type_assignment(node: nodes.Name, defstmt: nodes.Statement) -> bool: """Check if variable only gets assigned a type and never a value.""" diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py index 924eb4a431..01e0201a81 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.py +++ b/tests/functional/u/undefined/undefined_variable_py38.py @@ -155,3 +155,17 @@ def __init__(self, value): dummy = Dummy(value=val if (val := 'something') else 'anything') + +def expression_in_ternary_operator_inside_container(): + """Named expression in ternary operator: inside container""" + return [val2 if (val2 := 'something') else 'anything'] + + +def expression_in_ternary_operator_inside_container_tuple(): + """Same case, using a tuple inside a 1-element list""" + return [(val3, val3) if (val3 := 'something') else 'anything'] + + +def expression_in_ternary_operator_inside_container_wrong_position(): + """2-element list where named expression comes too late""" + return [val3, val3 if (val3 := 'something') else 'anything'] # [used-before-assignment] diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index 43a2b68290..4cce67ecc6 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -5,3 +5,4 @@ undefined-variable:99:6:99:19::Undefined variable 'else_assign_2':INFERENCE unused-variable:126:4:126:10:type_annotation_unused_after_comprehension:Unused variable 'my_int':UNDEFINED used-before-assignment:134:10:134:16:type_annotation_used_improperly_after_comprehension:Using variable 'my_int' before assignment:HIGH used-before-assignment:141:10:141:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH +used-before-assignment:171:12:171:16:expression_in_ternary_operator_inside_container_wrong_position:Using variable 'val3' before assignment:HIGH From b81380275e9e5109ec36dca81008ab7c0847db2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 17 Feb 2022 11:27:30 +0100 Subject: [PATCH 215/357] Don't assume test runners have more than 2 cores available --- tests/test_check_parallel.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index f4ddef985b..5bc1a6f279 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -413,15 +413,9 @@ def test_invoke_single_job(self) -> None: (3, 1, 1), (3, 1, 2), (3, 1, 3), - (3, 5, 1), - (3, 5, 2), - (3, 5, 3), (10, 2, 1), (10, 2, 2), (10, 2, 3), - (2, 10, 1), - (2, 10, 2), - (2, 10, 3), ], ) def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers): @@ -513,15 +507,9 @@ def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers) (3, 1, 1), (3, 1, 2), (3, 1, 3), - (3, 5, 1), - (3, 5, 2), - (3, 5, 3), (10, 2, 1), (10, 2, 2), (10, 2, 3), - (2, 10, 1), - (2, 10, 2), - (2, 10, 3), ], ) def test_map_reduce(self, num_files, num_jobs, num_checkers): From ea8645e19b6e020854c0bfac584dbd48a2da04dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:07:36 +0100 Subject: [PATCH 216/357] Update pytest requirement from ~=6.2 to ~=7.0 (#5778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update pytest requirement from ~=6.2 to ~=7.0 Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...7.0.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- requirements_test_min.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 4b71e937a1..c2c82c5546 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e . # astroid dependency is also defined in setup.cfg astroid==2.9.3 # Pinned to a specific version for tests -pytest~=6.2 +pytest~=7.0 pytest-benchmark~=3.4 gitpython>3 From 47a947cdcae21a8803719cd38fc014b7e5978f58 Mon Sep 17 00:00:00 2001 From: Kurian Benoy <70306694+kurianbenoy-aot@users.noreply.github.com> Date: Mon, 21 Feb 2022 13:40:18 +0530 Subject: [PATCH 217/357] Add note on how to share the badge for pylint (#5820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Jacob Walls Co-authored-by: Pierre Sassoulas --- CONTRIBUTORS.txt | 2 ++ README.rst | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 8bfe42a471..66480ea6af 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -216,6 +216,8 @@ contributors: * Kosarchuk Sergey: contributor +* Kurian Benoy: contributor + * Carey Metcalfe: demoted `try-except-raise` from error to warning * Marcus Näslund (naslundx): contributor diff --git a/README.rst b/README.rst index 32871e01cc..fb24114476 100644 --- a/README.rst +++ b/README.rst @@ -149,7 +149,28 @@ Do not forget to clone astroid_ and install the last version:: cd astroid python3 -m pip install -e . +Show your usage +----------------- +You can place this badge in your README to let others know your project uses pylint. + + .. image:: https://img.shields.io/badge/linting-pylint-yellowgreen + :target: https://github.com/PyCQA/pylint + +Use the badge in your project's README.md (or any other Markdown file):: + + [![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/PyCQA/pylint) + +Use the badge in your project's README.rst (or any other rst file):: + + .. image:: https://img.shields.io/badge/linting-pylint-yellowgreen + :target: https://github.com/PyCQA/pylint + + +If you use GitHub Actions, and one of your CI workflows begins with "name: pylint", you +can use GitHub's +[workflow status badges](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge#using-the-workflow-file-name) +to show an up-to-date indication of whether pushes to your default branch pass pylint. For more detailed information, check the documentation. .. _here: https://pylint.pycqa.org/en/latest/user_guide/installation.html From 6b1413949deaebf95b2cdc0c1f67e28e16b3205e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 21:39:17 +0100 Subject: [PATCH 218/357] Bump types-toml from 0.10.3 to 0.10.4 (#5805) Bumps [types-toml](https://github.com/python/typeshed) from 0.10.3 to 0.10.4. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-toml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e39fb4fb22..052ac74d55 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,4 +10,4 @@ pytest-profiling~=1.7 pytest-xdist~=2.5 # Type packages for mypy types-pkg_resources==0.1.3 -types-toml==0.10.3 +types-toml==0.10.4 From 8dbb2eaf9c1e7a19f0d53995ae4600b24e7f9e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 22 Feb 2022 10:19:29 +0100 Subject: [PATCH 219/357] Don't assume runners have more than 2 cores available for benchmarking --- tests/benchmark/test_baseline_benchmarks.py | 48 ++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index 4865a3bd5f..5617996dfd 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -142,37 +142,37 @@ def test_baseline_benchmark_j1(self, benchmark): linter.msg_status == 0 ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" - def test_baseline_benchmark_j10(self, benchmark): + def test_baseline_benchmark_j2(self, benchmark): """Establish a baseline of pylint performance with no work across threads. - Same as `test_baseline_benchmark_j1` but we use -j10 with 10 fake files to + Same as `test_baseline_benchmark_j1` but we use -j2 with 2 fake files to ensure end-to-end-system invoked. Because this is also so simple, if this regresses something very serious has happened. """ linter = PyLinter(reporter=Reporter()) - linter.config.jobs = 10 + linter.config.jobs = 2 # Create file per worker, using all workers fileinfos = [self.empty_filepath for _ in range(linter.config.jobs)] - assert linter.config.jobs == 10 + assert linter.config.jobs == 2 assert len(linter._checkers) == 1, "Should have 'master'" benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" - def test_baseline_benchmark_check_parallel_j10(self, benchmark): - """Should demonstrate times very close to `test_baseline_benchmark_j10`.""" + def test_baseline_benchmark_check_parallel_j2(self, benchmark): + """Should demonstrate times very close to `test_baseline_benchmark_j2`.""" linter = PyLinter(reporter=Reporter()) # Create file per worker, using all workers fileinfos = [self.empty_file_info for _ in range(linter.config.jobs)] assert len(linter._checkers) == 1, "Should have 'master'" - benchmark(check_parallel, linter, jobs=10, files=fileinfos) + benchmark(check_parallel, linter, jobs=2, files=fileinfos) assert ( linter.msg_status == 0 ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" @@ -196,20 +196,20 @@ def test_baseline_lots_of_files_j1(self, benchmark): linter.msg_status == 0 ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" - def test_baseline_lots_of_files_j10(self, benchmark): - """Establish a baseline with only 'master' checker being run in -j10. + def test_baseline_lots_of_files_j2(self, benchmark): + """Establish a baseline with only 'master' checker being run in -j2. As with the -j1 variant above `test_baseline_lots_of_files_j1`, we do not register any checkers except the default 'master', so the cost is just that of - the check_parallel system across 10 workers, plus the overhead of PyLinter + the check_parallel system across 2 workers, plus the overhead of PyLinter """ if benchmark.disabled: benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling linter = PyLinter(reporter=Reporter()) - linter.config.jobs = 10 + linter.config.jobs = 2 fileinfos = [self.empty_filepath for _ in range(self.lot_of_files)] - assert linter.config.jobs == 10 + assert linter.config.jobs == 2 assert len(linter._checkers) == 1, "Should have 'master'" benchmark(linter.check, fileinfos) assert ( @@ -236,8 +236,8 @@ def test_baseline_lots_of_files_j1_empty_checker(self, benchmark): linter.msg_status == 0 ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" - def test_baseline_lots_of_files_j10_empty_checker(self, benchmark): - """Baselines pylint for a single extra checker being run in -j10, for N-files. + def test_baseline_lots_of_files_j2_empty_checker(self, benchmark): + """Baselines pylint for a single extra checker being run in -j2, for N-files. We use a checker that does no work, so the cost is just that of the system at scale, across workers @@ -246,10 +246,10 @@ def test_baseline_lots_of_files_j10_empty_checker(self, benchmark): benchmark(print, "skipping, only benchmark large file counts") return # _only_ run this test is profiling linter = PyLinter(reporter=Reporter()) - linter.config.jobs = 10 + linter.config.jobs = 2 linter.register_checker(NoWorkChecker(linter)) fileinfos = [self.empty_filepath for _ in range(self.lot_of_files)] - assert linter.config.jobs == 10 + assert linter.config.jobs == 2 assert len(linter._checkers) == 2, "Should have 'master' and 'sleeper'" benchmark(linter.check, fileinfos) assert ( @@ -260,7 +260,7 @@ def test_baseline_benchmark_j1_single_working_checker(self, benchmark): """Establish a baseline of single-worker performance for PyLinter. Here we mimic a single Checker that does some work so that we can see the - impact of running a simple system with -j1 against the same system with -j10. + impact of running a simple system with -j1 against the same system with -j2. We expect this benchmark to take very close to `numfiles*SleepingChecker.sleep_duration` @@ -272,8 +272,8 @@ def test_baseline_benchmark_j1_single_working_checker(self, benchmark): linter.register_checker(SleepingChecker(linter)) # Check the same number of files as - # `test_baseline_benchmark_j10_single_working_checker` - fileinfos = [self.empty_filepath for _ in range(10)] + # `test_baseline_benchmark_j2_single_working_checker` + fileinfos = [self.empty_filepath for _ in range(2)] assert linter.config.jobs == 1 assert len(linter._checkers) == 2, "Should have 'master' and 'sleeper'" @@ -282,27 +282,27 @@ def test_baseline_benchmark_j1_single_working_checker(self, benchmark): linter.msg_status == 0 ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" - def test_baseline_benchmark_j10_single_working_checker(self, benchmark): + def test_baseline_benchmark_j2_single_working_checker(self, benchmark): """Establishes baseline of multi-worker performance for PyLinter/check_parallel. We expect this benchmark to take less time that test_baseline_benchmark_j1, `error_margin*(1/J)*(numfiles*SleepingChecker.sleep_duration)` Because of the cost of the framework and system the performance difference will - *not* be 1/10 of -j1 versions. + *not* be 1/2 of -j1 versions. """ if benchmark.disabled: benchmark(print, "skipping, do not want to sleep in main tests") return # _only_ run this test is profiling linter = PyLinter(reporter=Reporter()) - linter.config.jobs = 10 + linter.config.jobs = 2 linter.register_checker(SleepingChecker(linter)) # Check the same number of files as # `test_baseline_benchmark_j1_single_working_checker` - fileinfos = [self.empty_filepath for _ in range(10)] + fileinfos = [self.empty_filepath for _ in range(2)] - assert linter.config.jobs == 10 + assert linter.config.jobs == 2 assert len(linter._checkers) == 2, "Should have 'master' and 'sleeper'" benchmark(linter.check, fileinfos) assert ( From 6622d101ab09a23a8529ce01aea285d636235a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 22 Feb 2022 17:53:10 +0100 Subject: [PATCH 220/357] Revert "Increase timeouts" for CI (#5828) * Revert "Increase timeouts (#5755)" This reverts commit ade493aadfe7c62ca1cbdf0cb6e00e49dfdb4497. Co-authored-by: Pierre Sassoulas --- .github/workflows/ci.yaml | 20 ++++++++++---------- .github/workflows/primer-test.yaml | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ec9d771feb..c735bb338e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: prepare-base: name: Prepare base dependencies runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 5 outputs: python-key: ${{ steps.generate-python-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} @@ -77,7 +77,7 @@ jobs: formatting: name: checks / pre-commit runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 needs: prepare-base steps: - name: Check out code from GitHub @@ -120,7 +120,7 @@ jobs: spelling: name: checks / spelling runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 needs: prepare-base steps: - name: Check out code from GitHub @@ -151,7 +151,7 @@ jobs: prepare-tests-linux: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 5 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -194,7 +194,7 @@ jobs: pytest-linux: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 needs: prepare-tests-linux strategy: fail-fast: false @@ -279,7 +279,7 @@ jobs: benchmark-linux: name: tests / run benchmark / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 5 needs: prepare-tests-linux strategy: fail-fast: false @@ -331,7 +331,7 @@ jobs: prepare-tests-windows: name: tests / prepare / ${{ matrix.python-version }} / Windows runs-on: windows-latest - timeout-minutes: 15 + timeout-minutes: 5 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -374,7 +374,7 @@ jobs: pytest-windows: name: tests / run / ${{ matrix.python-version }} / Windows runs-on: windows-latest - timeout-minutes: 30 + timeout-minutes: 10 needs: prepare-tests-windows strategy: fail-fast: false @@ -413,7 +413,7 @@ jobs: prepare-tests-pypy: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 5 strategy: matrix: python-version: ["pypy3"] @@ -456,7 +456,7 @@ jobs: pytest-pypy: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 needs: prepare-tests-pypy strategy: fail-fast: false diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index 2e12d597b2..d32c68d9dd 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -24,7 +24,7 @@ jobs: prepare-tests-linux: name: prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 5 strategy: matrix: python-version: [3.8, 3.9, "3.10"] @@ -67,7 +67,7 @@ jobs: pytest-primer-stdlib: name: run on stdlib / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 10 needs: prepare-tests-linux strategy: matrix: @@ -102,7 +102,7 @@ jobs: pytest-primer-external-batch-one: name: run on batch one / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 90 + timeout-minutes: 60 needs: prepare-tests-linux strategy: matrix: @@ -137,7 +137,7 @@ jobs: pytest-primer-external-batch-two: name: run on batch two / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 90 + timeout-minutes: 60 needs: prepare-tests-linux strategy: matrix: From b4264112ec10186d5465f7d6af9b2ab91a236216 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 25 Feb 2022 17:36:40 +0100 Subject: [PATCH 221/357] Add title in running pylint's documentation (#5837) * Easier to see title for how to run pylint Refer to #5836 * Clarify exit codes documentation and remove useless stderr output * Add import in the example with a TextReporter Closes #5836 * Move the Reporter explanation where Run is documented --- doc/user_guide/output.rst | 21 ------------- doc/user_guide/run.rst | 65 ++++++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/doc/user_guide/output.rst b/doc/user_guide/output.rst index 1aa18f3b31..75b92ff80d 100644 --- a/doc/user_guide/output.rst +++ b/doc/user_guide/output.rst @@ -21,27 +21,6 @@ a colorized report to stdout at the same time: --output-format=json:somefile.json,colorized -Finally, it is possible to invoke pylint programmatically with a -reporter initialized with a custom stream: - -:: - - pylint_output = StringIO() # Custom open stream - reporter = text.TextReporter(pylint_output) - Run(["test_file.py"], reporter=reporter, do_exit=False) - print(pylint_output.getvalue()) # Retrieve and print the text report - -The reporter can accept any stream object as as parameter. In this example, -the stream outputs to a file: - -:: - - with open("report.out", "w") as f: - reporter = text.TextReporter(f) - Run(["test_file.py"], reporter=reporter, do_exit=False) - -This would be useful to capture pylint output in an open stream which -can be passed onto another program. Custom message formats '''''''''''''''''''''''''''' diff --git a/doc/user_guide/run.rst b/doc/user_guide/run.rst index 5501e23065..e49da5e517 100644 --- a/doc/user_guide/run.rst +++ b/doc/user_guide/run.rst @@ -2,8 +2,8 @@ Running Pylint ================ -Invoking Pylint ---------------- +From the command line +--------------------- Pylint is meant to be called from the command line. The usage is :: @@ -39,6 +39,9 @@ subtree of a directory, the ``--recursive=y`` option must be provided. For more details on this see the :ref:`faq`. +From another python program +--------------------------- + It is also possible to call Pylint from another Python program, thanks to the ``Run()`` function in the ``pylint.lint`` module (assuming Pylint options are stored in a list of strings ``pylint_options``) as: @@ -58,9 +61,7 @@ called by the command line. You can either patch ``sys.argv`` or supply argument sys.argv = ["pylint", "your_file"] pylint.run_pylint() - # Or: - pylint.run_pylint(argv=["your_file"]) To silently run Pylint on a ``module_name.py`` module, @@ -69,6 +70,7 @@ and get its standard output and error: .. sourcecode:: python from pylint import epylint as lint + (pylint_stdout, pylint_stderr) = lint.py_run('module_name.py', return_std=True) It is also possible to include additional Pylint options in the first argument to ``py_run``: @@ -76,11 +78,43 @@ It is also possible to include additional Pylint options in the first argument t .. sourcecode:: python from pylint import epylint as lint + (pylint_stdout, pylint_stderr) = lint.py_run('module_name.py --disable C0114', return_std=True) The options ``--msg-template="{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}"`` and ``--reports=n`` are set implicitly inside the ``epylint`` module. +Finally, it is possible to invoke pylint programmatically with a +reporter initialized with a custom stream: + +.. sourcecode:: python + + from io import StringIO + + from pylint.lint import Run + from pylint.reporters.text import TextReporter + + pylint_output = StringIO() # Custom open stream + reporter = TextReporter(pylint_output) + Run(["test_file.py"], reporter=reporter, do_exit=False) + print(pylint_output.getvalue()) # Retrieve and print the text report + +The reporter can accept any stream object as as parameter. In this example, +the stream outputs to a file: + +.. sourcecode:: python + + from pylint.lint import Run + from pylint.reporters.text import TextReporter + + with open("report.out", "w") as f: + reporter = TextReporter(f) + Run(["test_file.py"], reporter=reporter, do_exit=False) + +This would be useful to capture pylint output in an open stream which +can be passed onto another program. + + Command line options -------------------- @@ -183,24 +217,19 @@ initialization hooks (i.e. the ``--init-hook`` option). Exit codes ---------- -Pylint returns bit-encoded exit codes. If applicable, the table below lists the related -stderr stream message output. +Pylint returns bit-encoded exit codes. -========= ========================= ========================================== -exit code meaning stderr stream message -========= ========================= ========================================== +========= ========================= +exit code meaning +========= ========================= 0 no error 1 fatal message issued 2 error message issued 4 warning message issued 8 refactor message issued 16 convention message issued -32 usage error - "internal error while receiving results\ - from child linter" "Error occurred, - stopping the linter." - - "" - - "Jobs number <#> should be greater \ - than 0" - - "" -========= ========================= ========================================== +32 usage error +========= ========================= + +For example, an exit code of ``20`` means there was at least one warning message (4) +and at least one convention message (16) and nothing else. From 15040ee7c42958606ae27aa32ccbe54371b83049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 26 Feb 2022 10:30:21 +0100 Subject: [PATCH 222/357] Add some flags showing information to the CI pytest runs (#5841) --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c735bb338e..e9f1ad2fb7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -224,7 +224,7 @@ jobs: - name: Run pytest run: | . venv/bin/activate - pytest --benchmark-disable --cov --cov-report= tests/ + pytest -vv --durations=20 --benchmark-disable --cov --cov-report= tests/ - name: Upload coverage artifact uses: actions/upload-artifact@v2.3.1 with: @@ -408,7 +408,7 @@ jobs: - name: Run pytest run: | . venv\\Scripts\\activate - pytest --benchmark-disable tests/ + pytest -vv --durations=20 --benchmark-disable tests/ prepare-tests-pypy: name: tests / prepare / ${{ matrix.python-version }} / Linux @@ -486,4 +486,4 @@ jobs: - name: Run pytest run: | . venv/bin/activate - pytest --benchmark-disable tests/ + pytest -vv --durations=20 --benchmark-disable tests/ From 5bdd5034c556d2d4986a365a666e923bccef2a94 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 26 Feb 2022 12:35:25 +0100 Subject: [PATCH 223/357] Add a test to check that no old msgid or symbol are used (#5839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add deleted msgid and symbol from the Python 3K+ checker and other deleted checks. See https://github.com/PyCQA/pylint/pull/4942 Closes #5729 Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 8 ++ doc/whatsnew/2.13.rst | 8 ++ pylint/checkers/__init__.py | 5 +- pylint/checkers/unsupported_version.py | 4 +- pylint/constants.py | 106 +++++++++++++++++- script/get_unused_message_id_category.py | 3 + .../test_no_removed_msgid_or_symbol_used.py | 17 +++ 7 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 tests/message/test_no_removed_msgid_or_symbol_used.py diff --git a/ChangeLog b/ChangeLog index 6d1a12bcf8..e566e1da39 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,14 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* ``using-f-string-in-unsupported-version`` and ``using-final-decorator-in-unsupported-version`` msgids + were renamed from ``W1601`` and ``W1602`` to ``W2601`` and ``W2602``. Disabling using these msgids will break. + This is done in order to restore consistency with the already existing msgids for ``apply-builtin`` and + ``basestring-builtin`` from the now deleted python 3K+ checker. There is now a check that we're not using + existing msgids or symbols from deleted checkers. + + Closes #5729 + * Add ``--recursive`` option to allow recursive discovery of all modules and packages in subtree. Running pylint with ``--recursive=y`` option will check all discovered ``.py`` files and packages found inside subtree of directory provided as parameter to pylint. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 676860ee54..580f0b69df 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -92,6 +92,14 @@ Extensions Other Changes ============= +* ``using-f-string-in-unsupported-version`` and ``using-final-decorator-in-unsupported-version`` msgids + were renamed from ``W1601`` and ``W1602`` to ``W2601`` and ``W2602``. Disables using these msgids will break. + This is done in order to restore consistency with the already existing msgids for ``apply-builtin`` and + ``basestring-builtin`` from the now deleted python 3K+ checker. There is now a check that we're not using + existing msgids or symbols from deleted checkers. + + Closes #5729 + * Add ``--recursive`` option to allow recursive discovery of all modules and packages in subtree. Running pylint with ``--recursive=y`` option will check all discovered ``.py`` files and packages found inside subtree of directory provided as parameter to pylint. diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index e99560faa3..a79a381941 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -37,14 +37,15 @@ 13: string_format 14: string_constant 15: stdlib -16: python3 +16: python3 (This one was deleted but needs to be reserved for consistency with old messages) 17: refactoring . . . 24: non-ascii-names 25: unicode -26-50: not yet used: reserved for future internal checkers. +26: unsupported_version +27-50: not yet used: reserved for future internal checkers. This file is not updated. Use script/get_unused_message_id_category.py to get the next free checker id. diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index b3369b877f..7c82817c00 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -35,13 +35,13 @@ class UnsupportedVersionChecker(BaseChecker): __implements__ = (IAstroidChecker,) name = "unsupported_version" msgs = { - "W1601": ( + "W2601": ( "F-strings are not supported by all versions included in the py-version setting", "using-f-string-in-unsupported-version", "Used when the py-version set by the user is lower than 3.6 and pylint encounters " "a f-string.", ), - "W1602": ( + "W2602": ( "typing.final is not supported by all versions included in the py-version setting", "using-final-decorator-in-unsupported-version", "Used when the py-version set by the user is lower than 3.8 and pylint encounters " diff --git a/pylint/constants.py b/pylint/constants.py index 813cd54dd8..c3b94863dc 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -2,7 +2,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import platform import sys -from typing import Dict +from typing import Dict, List, NamedTuple, Tuple import astroid import platformdirs @@ -72,3 +72,107 @@ class WarningScope: "class_const": "class constant", "inlinevar": "inline iteration", } + + +class DeletedMessage(NamedTuple): + msgid: str + symbol: str + old_names: List[Tuple[str, str]] = [] + + +DELETED_MSGID_PREFIXES = [ + 16, # the PY3K+ checker, see https://github.com/PyCQA/pylint/pull/4942 +] + +DELETED_MESSAGES = [ + # Everything until the next comment is from the + # PY3K+ checker, see https://github.com/PyCQA/pylint/pull/4942 + DeletedMessage("W1601", "apply-builtin"), + DeletedMessage("E1601", "print-statement"), + DeletedMessage("E1602", "parameter-unpacking"), + DeletedMessage( + "E1603", "unpacking-in-except", [("W0712", "old-unpacking-in-except")] + ), + DeletedMessage("E1604", "old-raise-syntax", [("W0121", "old-old-raise-syntax")]), + DeletedMessage("E1605", "backtick", [("W0333", "old-backtick")]), + DeletedMessage("E1609", "import-star-module-level"), + DeletedMessage("W1601", "apply-builtin"), + DeletedMessage("W1602", "basestring-builtin"), + DeletedMessage("W1603", "buffer-builtin"), + DeletedMessage("W1604", "cmp-builtin"), + DeletedMessage("W1605", "coerce-builtin"), + DeletedMessage("W1606", "execfile-builtin"), + DeletedMessage("W1607", "file-builtin"), + DeletedMessage("W1608", "long-builtin"), + DeletedMessage("W1609", "raw_input-builtin"), + DeletedMessage("W1610", "reduce-builtin"), + DeletedMessage("W1611", "standarderror-builtin"), + DeletedMessage("W1612", "unicode-builtin"), + DeletedMessage("W1613", "xrange-builtin"), + DeletedMessage("W1614", "coerce-method"), + DeletedMessage("W1615", "delslice-method"), + DeletedMessage("W1616", "getslice-method"), + DeletedMessage("W1617", "setslice-method"), + DeletedMessage("W1618", "no-absolute-import"), + DeletedMessage("W1619", "old-division"), + DeletedMessage("W1620", "dict-iter-method"), + DeletedMessage("W1621", "dict-view-method"), + DeletedMessage("W1622", "next-method-called"), + DeletedMessage("W1623", "metaclass-assignment"), + DeletedMessage( + "W1624", "indexing-exception", [("W0713", "old-indexing-exception")] + ), + DeletedMessage("W1625", "raising-string", [("W0701", "old-raising-string")]), + DeletedMessage("W1626", "reload-builtin"), + DeletedMessage("W1627", "oct-method"), + DeletedMessage("W1628", "hex-method"), + DeletedMessage("W1629", "nonzero-method"), + DeletedMessage("W1630", "cmp-method"), + DeletedMessage("W1632", "input-builtin"), + DeletedMessage("W1633", "round-builtin"), + DeletedMessage("W1634", "intern-builtin"), + DeletedMessage("W1635", "unichr-builtin"), + DeletedMessage( + "W1636", "map-builtin-not-iterating", [("W1631", "implicit-map-evaluation")] + ), + DeletedMessage("W1637", "zip-builtin-not-iterating"), + DeletedMessage("W1638", "range-builtin-not-iterating"), + DeletedMessage("W1639", "filter-builtin-not-iterating"), + DeletedMessage("W1640", "using-cmp-argument"), + DeletedMessage("W1641", "eq-without-hash"), + DeletedMessage("W1642", "div-method"), + DeletedMessage("W1643", "idiv-method"), + DeletedMessage("W1644", "rdiv-method"), + DeletedMessage("W1645", "exception-message-attribute"), + DeletedMessage("W1646", "invalid-str-codec"), + DeletedMessage("W1647", "sys-max-int"), + DeletedMessage("W1648", "bad-python3-import"), + DeletedMessage("W1649", "deprecated-string-function"), + DeletedMessage("W1650", "deprecated-str-translate-call"), + DeletedMessage("W1651", "deprecated-itertools-function"), + DeletedMessage("W1652", "deprecated-types-field"), + DeletedMessage("W1653", "next-method-defined"), + DeletedMessage("W1654", "dict-items-not-iterating"), + DeletedMessage("W1655", "dict-keys-not-iterating"), + DeletedMessage("W1656", "dict-values-not-iterating"), + DeletedMessage("W1657", "deprecated-operator-function"), + DeletedMessage("W1658", "deprecated-urllib-function"), + DeletedMessage("W1659", "xreadlines-attribute"), + DeletedMessage("W1660", "deprecated-sys-function"), + DeletedMessage("W1661", "exception-escape"), + DeletedMessage("W1662", "comprehension-escape"), + # https://github.com/PyCQA/pylint/pull/3578 + DeletedMessage("W0312", "mixed-indentation"), + # https://github.com/PyCQA/pylint/pull/3577 + DeletedMessage( + "C0326", + "bad-whitespace", + [ + ("C0323", "no-space-after-operator"), + ("C0324", "no-space-after-comma"), + ("C0322", "no-space-before-operator"), + ], + ), + # https://github.com/PyCQA/pylint/pull/3571 + DeletedMessage("C0330", "bad-continuation"), +] diff --git a/script/get_unused_message_id_category.py b/script/get_unused_message_id_category.py index 2741148c04..95d7ac3e30 100644 --- a/script/get_unused_message_id_category.py +++ b/script/get_unused_message_id_category.py @@ -5,6 +5,7 @@ from typing import List from pylint.checkers import initialize as initialize_checkers +from pylint.constants import DELETED_MSGID_PREFIXES from pylint.extensions import initialize as initialize_extensions from pylint.lint.pylinter import PyLinter @@ -18,6 +19,8 @@ def register_all_checkers_and_plugins(linter: "PyLinter") -> None: def get_next_code_category(message_ids: List[str]) -> int: categories = sorted({int(i[:2]) for i in message_ids}) + # We add the prefixes for deleted checkers + categories += DELETED_MSGID_PREFIXES for i in categories: if i + 1 not in categories: return i + 1 diff --git a/tests/message/test_no_removed_msgid_or_symbol_used.py b/tests/message/test_no_removed_msgid_or_symbol_used.py new file mode 100644 index 0000000000..c6ece36794 --- /dev/null +++ b/tests/message/test_no_removed_msgid_or_symbol_used.py @@ -0,0 +1,17 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +from pylint.constants import DELETED_MESSAGES +from pylint.lint import PyLinter + + +def test_no_removed_msgid_or_symbol_used(linter: PyLinter) -> None: + """Tests that we're not using deleted msgid or symbol. + + This could cause occasional bugs, but more importantly confusion and inconsistencies + when searching for old msgids online. See https://github.com/PyCQA/pylint/issues/5729 + """ + for msgid, symbol, old_names in DELETED_MESSAGES: + linter.msgs_store.message_id_store.register_message_definition( + msgid, symbol, old_names + ) From 90dafaef06809eab8fb1dda8bc0955b959fe1e23 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 27 Feb 2022 10:38:30 +0100 Subject: [PATCH 224/357] Add a testutil extra-require and add gitpython to it (#5842) Closes #5486 --- ChangeLog | 6 ++++++ doc/whatsnew/2.13.rst | 6 ++++++ requirements_test_min.txt | 3 +-- setup.cfg | 3 +++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index e566e1da39..b6ba4becf2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -43,6 +43,12 @@ Release date: TBA Closes #5322 +* Added a ``testutil`` extra require to the packaging, as ``gitpython`` should not be a dependency + all the time but is still required to use the primer helper code in ``pylint.testutil``. You can + install it with ``pip install pylint[testutil]``. + + Closes #5486 + * Added several checkers to deal with unicode security issues (see `Trojan Sources `_ and `PEP 672 `_ for details) that also diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 580f0b69df..21f13c0048 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -254,6 +254,12 @@ Other Changes Closes #3793 +* Added a ``testutil`` extra require to the packaging, as ``gitpython`` should not be a dependency + all the time but is still required to use the primer helper code in ``pylint.testutil``. You can + install it with ``pip install pylint[testutil]``. + + Closes #5486 + * Fixed a false positive for ``used-before-assignment`` when a named expression appears as the first value in a container. diff --git a/requirements_test_min.txt b/requirements_test_min.txt index c2c82c5546..79f99044b4 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,5 @@ --e . +-e .[testutil] # astroid dependency is also defined in setup.cfg astroid==2.9.3 # Pinned to a specific version for tests pytest~=7.0 pytest-benchmark~=3.4 -gitpython>3 diff --git a/setup.cfg b/setup.cfg index 1699b1f32d..26318dd7cb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,6 +56,9 @@ install_requires = typing-extensions>=3.10.0;python_version<"3.10" python_requires = >=3.6.2 +[options.extras_require] +testutil=gitpython>3 + [options.packages.find] include = pylint* From 843a8ff803c09e3963cdd93800400a181a5cf0b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 15:32:10 +0100 Subject: [PATCH 225/357] Bump actions/setup-python from 2.3.2 to 3 (#5849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump actions/setup-python from 2.3.2 to 3 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.2 to 3. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.2...v3) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump Cache Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- .github/workflows/ci.yaml | 28 ++++++++++++++-------------- .github/workflows/primer-test.yaml | 10 +++++----- .github/workflows/release.yml | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e9f1ad2fb7..3679aea615 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ on: env: # Also change CACHE_VERSION in the primer workflow - CACHE_VERSION: 4 + CACHE_VERSION: 5 DEFAULT_PYTHON: 3.8 PRE_COMMIT_CACHE: ~/.cache/pre-commit @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Generate partial Python venv restore key @@ -84,7 +84,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment @@ -164,7 +164,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -205,7 +205,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -246,7 +246,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -290,7 +290,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -344,7 +344,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -389,7 +389,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -416,7 +416,7 @@ jobs: timeout-minutes: 5 strategy: matrix: - python-version: ["pypy3"] + python-version: ["pypy-3.6"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -426,7 +426,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -461,13 +461,13 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3"] + python-version: ["pypy-3.6"] steps: - name: Check out code from GitHub uses: actions/checkout@v2.4.0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index d32c68d9dd..ba2bc06b2e 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -14,7 +14,7 @@ on: env: # Also change CACHE_VERSION in the CI workflow - CACHE_VERSION: 4 + CACHE_VERSION: 5 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Generate partial Python venv restore key @@ -77,7 +77,7 @@ jobs: uses: actions/checkout@v2.3.5 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v2.3.5 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@v2.3.5 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74e14ee22b..5c58d52616 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.0.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Install requirements From 182cc539b8154c0710fcea7e522267e42eba8899 Mon Sep 17 00:00:00 2001 From: Tim Martin Date: Wed, 2 Mar 2022 21:58:26 +0000 Subject: [PATCH 226/357] Use value directly instead of index in ``enumerate`` contexts (#5856) Refactoring to prevent warnings being issued on these lines from a new proposed checker. --- pylint/checkers/__init__.py | 6 +++--- pylint/checkers/refactoring/refactoring_checker.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index a79a381941..ab23b6b592 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -124,8 +124,8 @@ def table_lines_from_stats( ("error", "NC"), ] - for index, _ in enumerate(new): - new_value = new[index][1] + for index, value in enumerate(new): + new_value = value[1] old_value = old[index][1] diff_str = ( diff_string(old_value, new_value) @@ -134,7 +134,7 @@ def table_lines_from_stats( ) new_str = f"{new_value:.3f}" if isinstance(new_value, float) else str(new_value) old_str = f"{old_value:.3f}" if isinstance(old_value, float) else str(old_value) - lines.extend((new[index][0].replace("_", " "), new_str, old_str, diff_str)) + lines.extend((value[0].replace("_", " "), new_str, old_str, diff_str)) return lines diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 1bf75e77e3..b727f1b632 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -578,12 +578,12 @@ def process_tokens(self, tokens): token_string = token[1] if token_string == "elif": # AST exists by the time process_tokens is called, so - # it's safe to assume tokens[index+1] - # exists. tokens[index+1][2] is the elif's position as + # it's safe to assume tokens[index+1] exists. + # tokens[index+1][2] is the elif's position as # reported by CPython and PyPy, - # tokens[index][2] is the actual position and also is + # token[2] is the actual position and also is # reported by IronPython. - self._elifs.extend([tokens[index][2], tokens[index + 1][2]]) + self._elifs.extend([token[2], tokens[index + 1][2]]) elif _is_trailing_comma(tokens, index): if self.linter.is_message_enabled("trailing-comma-tuple"): self.add_message("trailing-comma-tuple", line=token.start[0]) From a1df7685a4e6a05b519ea011f16a2f0d49d08032 Mon Sep 17 00:00:00 2001 From: dbrookman <53625739+dbrookman@users.noreply.github.com> Date: Fri, 4 Mar 2022 03:20:58 -0500 Subject: [PATCH 227/357] Fix matching note tags with a non-word char last (#5859) Using "\b" at the end of these patterns will only match note tags that end in an alphanumeric character, immediately followed by a non-alphanumeric character, or the end of the string. This is due to "\b" being defined as a boundary between a word character ("\w") and a non-word character ("\W"), or the end of the string. This leads to deviations like "???" being ignored when specified. Swapping "\b" for a positive lookahead that targets a whitespace, a colon, or the end of a string accounts for this. Closes #5840. --- CONTRIBUTORS.txt | 2 ++ ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/misc.py | 4 ++-- tests/checkers/unittest_misc.py | 10 ++++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 66480ea6af..88ab429cf1 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -601,3 +601,5 @@ contributors: * Carli Freudenberg (CarliJoy): contributor - Fixed issue 5281, added Unicode checker - Improve non-ascii-name checker + +* Daniel Brookman: contributor diff --git a/ChangeLog b/ChangeLog index b6ba4becf2..fc5d18485b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,10 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Fix matching ``--notes`` options that end in a non-word character. + + Closes #5840 + * ``using-f-string-in-unsupported-version`` and ``using-final-decorator-in-unsupported-version`` msgids were renamed from ``W1601`` and ``W1602`` to ``W2601`` and ``W2602``. Disabling using these msgids will break. This is done in order to restore consistency with the already existing msgids for ``apply-builtin`` and diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 21f13c0048..4954cada48 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -92,6 +92,10 @@ Extensions Other Changes ============= +* Fix matching ``--notes`` options that end in a non-word character. + + Closes #5840 + * ``using-f-string-in-unsupported-version`` and ``using-final-decorator-in-unsupported-version`` msgids were renamed from ``W1601`` and ``W1602`` to ``W2601`` and ``W2602``. Disables using these msgids will break. This is done in order to restore consistency with the already existing msgids for ``apply-builtin`` and diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index 69149e61a9..baec58fbbf 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -121,9 +121,9 @@ def open(self): notes = "|".join(re.escape(note) for note in self.config.notes) if self.config.notes_rgx: - regex_string = rf"#\s*({notes}|{self.config.notes_rgx})\b" + regex_string = rf"#\s*({notes}|{self.config.notes_rgx})(?=(:|\s|\Z))" else: - regex_string = rf"#\s*({notes})\b" + regex_string = rf"#\s*({notes})(?=(:|\s|\Z))" self._fixme_pattern = re.compile(regex_string, re.I) diff --git a/tests/checkers/unittest_misc.py b/tests/checkers/unittest_misc.py index 23e19a9d0f..bd15a932ed 100644 --- a/tests/checkers/unittest_misc.py +++ b/tests/checkers/unittest_misc.py @@ -68,6 +68,16 @@ def test_without_space_fixme(self) -> None: ): self.checker.process_tokens(_tokenize_str(code)) + @set_config(notes=["???"]) + def test_non_alphanumeric_codetag(self) -> None: + code = """a = 1 + #??? + """ + with self.assertAddsMessages( + MessageTest(msg_id="fixme", line=2, args="???", col_offset=17) + ): + self.checker.process_tokens(_tokenize_str(code)) + @set_config(notes=[]) def test_absent_codetag(self) -> None: code = """a = 1 From 4ca885fd8c35e6781a359cda0e4de2c0910973b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 4 Mar 2022 14:36:56 +0100 Subject: [PATCH 228/357] Allow disabling ``duplicate-code`` with a disable comment (#5446) Co-authored-by: Pierre Sassoulas Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ChangeLog | 5 + doc/whatsnew/2.13.rst | 5 + pylint/checkers/similar.py | 11 +- .../raw_strings_all}/__init__.py | 0 .../raw_strings_all}/first.py | 0 .../raw_strings_all}/second.py | 0 .../duplicate_code/raw_strings_all/third.py | 11 ++ .../raw_strings_disable_file/__init__.py | 0 .../raw_strings_disable_file/first.py | 12 ++ .../raw_strings_disable_file/second.py | 11 ++ .../raw_strings_disable_file/third.py | 11 ++ .../__init__.py | 0 .../raw_strings_disable_file_double/first.py | 12 ++ .../raw_strings_disable_file_double/second.py | 12 ++ .../raw_strings_disable_file_double/third.py | 11 ++ .../__init__.py | 0 .../raw_strings_disable_line_begin/first.py | 11 ++ .../raw_strings_disable_line_begin/second.py | 11 ++ .../__init__.py | 0 .../first.py | 11 ++ .../second.py | 11 ++ .../raw_strings_disable_line_end/__init__.py | 0 .../raw_strings_disable_line_end/first.py | 11 ++ .../raw_strings_disable_line_end/second.py | 11 ++ .../__init__.py | 0 .../raw_strings_disable_line_middle/first.py | 11 ++ .../raw_strings_disable_line_middle/second.py | 11 ++ .../raw_strings_disable_scope/__init__.py | 0 .../raw_strings_disable_scope/first.py | 12 ++ .../raw_strings_disable_scope/second.py | 11 ++ .../raw_strings_disable_scope/third.py | 11 ++ .../__init__.py | 0 .../raw_strings_disable_scope_double/first.py | 12 ++ .../second.py | 12 ++ .../raw_strings_disable_scope_double/third.py | 11 ++ .../__init__.py | 0 .../first.py | 21 +++ .../second.py | 20 +++ tests/test_self.py | 8 - tests/test_similar.py | 141 ++++++++++++++++++ 40 files changed, 439 insertions(+), 9 deletions(-) rename tests/regrtest_data/{duplicate_data_raw_strings => duplicate_code/raw_strings_all}/__init__.py (100%) rename tests/regrtest_data/{duplicate_data_raw_strings => duplicate_code/raw_strings_all}/first.py (100%) rename tests/regrtest_data/{duplicate_data_raw_strings => duplicate_code/raw_strings_all}/second.py (100%) create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_all/third.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file/third.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/third.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope/third.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/second.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/third.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/first.py create mode 100644 tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/second.py create mode 100644 tests/test_similar.py diff --git a/ChangeLog b/ChangeLog index fc5d18485b..0b17299186 100644 --- a/ChangeLog +++ b/ChangeLog @@ -409,6 +409,11 @@ Release date: TBA Closes #4955 +* Allow disabling ``duplicate-code`` with a disable comment when running through + pylint. + + Closes #214 + .. Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 4954cada48..4acac1ace5 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -230,6 +230,11 @@ Other Changes Closes #5323 +* Allow disabling ``duplicate-code`` with a disable comment when running through + pylint. + + Closes #214 + * Fix false positive for ``undefined-variable`` when ``namedtuple`` class attributes are used as return annotations. diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 113b086bc7..5287e31279 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -381,10 +381,19 @@ def append_stream( else: readlines = stream.readlines # type: ignore[assignment] # hint parameter is incorrectly typed as non-optional try: + active_lines: List[str] = [] + if hasattr(self, "linter"): + # Remove those lines that should be ignored because of disables + for index, line in enumerate(readlines()): + if self.linter._is_one_message_enabled("R0801", index + 1): # type: ignore[attr-defined] + active_lines.append(line) + else: + active_lines = readlines() + self.linesets.append( LineSet( streamid, - readlines(), + active_lines, self.ignore_comments, self.ignore_docstrings, self.ignore_imports, diff --git a/tests/regrtest_data/duplicate_data_raw_strings/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_all/__init__.py similarity index 100% rename from tests/regrtest_data/duplicate_data_raw_strings/__init__.py rename to tests/regrtest_data/duplicate_code/raw_strings_all/__init__.py diff --git a/tests/regrtest_data/duplicate_data_raw_strings/first.py b/tests/regrtest_data/duplicate_code/raw_strings_all/first.py similarity index 100% rename from tests/regrtest_data/duplicate_data_raw_strings/first.py rename to tests/regrtest_data/duplicate_code/raw_strings_all/first.py diff --git a/tests/regrtest_data/duplicate_data_raw_strings/second.py b/tests/regrtest_data/duplicate_code/raw_strings_all/second.py similarity index 100% rename from tests/regrtest_data/duplicate_data_raw_strings/second.py rename to tests/regrtest_data/duplicate_code/raw_strings_all/second.py diff --git a/tests/regrtest_data/duplicate_code/raw_strings_all/third.py b/tests/regrtest_data/duplicate_code/raw_strings_all/third.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_all/third.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/first.py new file mode 100644 index 0000000000..fc7f0af615 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/first.py @@ -0,0 +1,12 @@ +# pylint: disable=duplicate-code +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/second.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/second.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file/third.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/third.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_file/third.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/first.py new file mode 100644 index 0000000000..fc7f0af615 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/first.py @@ -0,0 +1,12 @@ +# pylint: disable=duplicate-code +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/second.py new file mode 100644 index 0000000000..fc7f0af615 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/second.py @@ -0,0 +1,12 @@ +# pylint: disable=duplicate-code +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/third.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/third.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_file_double/third.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/first.py new file mode 100644 index 0000000000..3f6182d23a --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/first.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 # pylint: disable=duplicate-code + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/second.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_begin/second.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/first.py new file mode 100644 index 0000000000..030b519e22 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/first.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 # pylint: disable=duplicate-code + yyyy = 2 # pylint: disable=duplicate-code + zzzz = 3 # pylint: disable=duplicate-code + wwww = 4 # pylint: disable=duplicate-code + vvvv = xxxx + yyyy + zzzz + wwww # pylint: disable=duplicate-code + return vvvv # pylint: disable=duplicate-code diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/second.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_disable_all/second.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/first.py new file mode 100644 index 0000000000..97728fff4a --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/first.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv # pylint: disable=duplicate-code diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/second.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_end/second.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/first.py new file mode 100644 index 0000000000..828b94c459 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/first.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 # pylint: disable=duplicate-code + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/second.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_line_middle/second.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/first.py new file mode 100644 index 0000000000..97e987edb3 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/first.py @@ -0,0 +1,12 @@ +r"""A raw docstring. +""" + + +def look_busy(): + # pylint: disable=duplicate-code + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/second.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/second.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/third.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/third.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope/third.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/first.py new file mode 100644 index 0000000000..97e987edb3 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/first.py @@ -0,0 +1,12 @@ +r"""A raw docstring. +""" + + +def look_busy(): + # pylint: disable=duplicate-code + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/second.py new file mode 100644 index 0000000000..97e987edb3 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/second.py @@ -0,0 +1,12 @@ +r"""A raw docstring. +""" + + +def look_busy(): + # pylint: disable=duplicate-code + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/third.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/third.py new file mode 100644 index 0000000000..687c5922ed --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_double/third.py @@ -0,0 +1,11 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/__init__.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/first.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/first.py new file mode 100644 index 0000000000..ae2b088b56 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/first.py @@ -0,0 +1,21 @@ +r"""A raw docstring. +""" + + +def look_busy(): + # pylint: disable=duplicate-code + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv + + +def look_busy_two(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/second.py b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/second.py new file mode 100644 index 0000000000..c3693ef374 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/raw_strings_disable_scope_second_function/second.py @@ -0,0 +1,20 @@ +r"""A raw docstring. +""" + + +def look_busy(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv + + +def look_busy_two(): + xxxx = 1 + yyyy = 2 + zzzz = 3 + wwww = 4 + vvvv = xxxx + yyyy + zzzz + wwww + return vvvv diff --git a/tests/test_self.py b/tests/test_self.py index 61aa35d325..d22d2a5e48 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1121,14 +1121,6 @@ def test_jobs_score(self) -> None: expected = "Your code has been rated at 7.50/10" self._test_output([path, "--jobs=2", "-ry"], expected_output=expected) - def test_duplicate_code_raw_strings(self) -> None: - path = join(HERE, "regrtest_data", "duplicate_data_raw_strings") - expected_output = "Similar lines in 2 files" - self._test_output( - [path, "--disable=all", "--enable=duplicate-code"], - expected_output=expected_output, - ) - def test_regression_parallel_mode_without_filepath(self) -> None: # Test that parallel mode properly passes filepath # https://github.com/PyCQA/pylint/issues/3564 diff --git a/tests/test_similar.py b/tests/test_similar.py new file mode 100644 index 0000000000..53c71cbd1c --- /dev/null +++ b/tests/test_similar.py @@ -0,0 +1,141 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + + +import contextlib +import os +import re +import sys +import warnings +from io import StringIO +from os.path import abspath, dirname, join +from typing import Iterator, List, TextIO + +import pytest + +from pylint.lint import Run + +HERE = abspath(dirname(__file__)) +DATA = join(HERE, "regrtest_data", "duplicate_code") +CLEAN_PATH = re.escape(dirname(dirname(__file__)) + os.path.sep) + + +@contextlib.contextmanager +def _patch_streams(out: TextIO) -> Iterator: + sys.stderr = sys.stdout = out + try: + yield + finally: + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + + +class TestSimilarCodeChecker: + def _runtest(self, args: List[str], code: int) -> None: + """Runs the tests and sees if output code is as expected.""" + out = StringIO() + pylint_code = self._run_pylint(args, out=out) + output = out.getvalue() + msg = f"expected output status {code}, got {pylint_code}" + if output is not None: + msg = f"{msg}. Below pylint output: \n{output}" + assert pylint_code == code, msg + + @staticmethod + def _run_pylint(args: List[str], out: TextIO) -> int: + """Runs pylint with a patched output.""" + args = args + ["--persistent=no"] + with _patch_streams(out): + with pytest.raises(SystemExit) as cm: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Run(args) + return cm.value.code + + @staticmethod + def _clean_paths(output: str) -> str: + """Normalize path to the tests directory.""" + output = re.sub(CLEAN_PATH, "", output, flags=re.MULTILINE) + return output.replace("\\", "/") + + def _test_output(self, args: List[str], expected_output: str) -> None: + """Tests if the output of a pylint run is as expected.""" + out = StringIO() + self._run_pylint(args, out=out) + actual_output = self._clean_paths(out.getvalue()) + expected_output = self._clean_paths(expected_output) + assert expected_output.strip() in actual_output.strip() + + def test_duplicate_code_raw_strings_all(self) -> None: + """Test similar lines in 3 similar files.""" + path = join(DATA, "raw_strings_all") + expected_output = "Similar lines in 2 files" + self._test_output( + [path, "--disable=all", "--enable=duplicate-code"], + expected_output=expected_output, + ) + + def test_duplicate_code_raw_strings_disable_file(self) -> None: + """Tests disabling duplicate-code at the file level in a single file.""" + path = join(DATA, "raw_strings_disable_file") + expected_output = "Similar lines in 2 files" + self._test_output( + [path, "--disable=all", "--enable=duplicate-code"], + expected_output=expected_output, + ) + + def test_duplicate_code_raw_strings_disable_file_double(self) -> None: + """Tests disabling duplicate-code at the file level in two files.""" + path = join(DATA, "raw_strings_disable_file_double") + self._runtest([path, "--disable=all", "--enable=duplicate-code"], code=0) + + def test_duplicate_code_raw_strings_disable_line_two(self) -> None: + """Tests disabling duplicate-code at a line at the begin of a piece of similar code.""" + path = join(DATA, "raw_strings_disable_line_begin") + expected_output = "Similar lines in 2 files" + self._test_output( + [path, "--disable=all", "--enable=duplicate-code"], + expected_output=expected_output, + ) + + def test_duplicate_code_raw_strings_disable_line_disable_all(self) -> None: + """Tests disabling duplicate-code with all similar lines disabled per line.""" + path = join(DATA, "raw_strings_disable_line_disable_all") + self._runtest([path, "--disable=all", "--enable=duplicate-code"], code=0) + + def test_duplicate_code_raw_strings_disable_line_midle(self) -> None: + """Tests disabling duplicate-code at a line in the middle of a piece of similar code.""" + path = join(DATA, "raw_strings_disable_line_middle") + self._runtest([path, "--disable=all", "--enable=duplicate-code"], code=0) + + def test_duplicate_code_raw_strings_disable_line_end(self) -> None: + """Tests disabling duplicate-code at a line at the end of a piece of similar code.""" + path = join(DATA, "raw_strings_disable_line_end") + expected_output = "Similar lines in 2 files" + self._test_output( + [path, "--disable=all", "--enable=duplicate-code"], + expected_output=expected_output, + ) + + def test_duplicate_code_raw_strings_disable_scope(self) -> None: + """Tests disabling duplicate-code at an inner scope level.""" + path = join(DATA, "raw_strings_disable_scope") + expected_output = "Similar lines in 2 files" + self._test_output( + [path, "--disable=all", "--enable=duplicate-code"], + expected_output=expected_output, + ) + + def test_duplicate_code_raw_strings_disable_scope_double(self) -> None: + """Tests disabling duplicate-code at an inner scope level in two files.""" + path = join(DATA, "raw_strings_disable_scope_double") + self._runtest([path, "--disable=all", "--enable=duplicate-code"], code=0) + + def test_duplicate_code_raw_strings_disable_scope_function(self) -> None: + """Tests disabling duplicate-code at an inner scope level with another scope with similarity.""" + path = join(DATA, "raw_strings_disable_scope_second_function") + expected_output = "Similar lines in 2 files" + self._test_output( + [path, "--disable=all", "--enable=duplicate-code"], + expected_output=expected_output, + ) From 263ae7153b3168355f0a149831e56219bb7e862f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 6 Mar 2022 15:50:21 -0500 Subject: [PATCH 229/357] Add stdlib xml.etree.cElementTree to deprecated modules (#5863) --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/stdlib.py | 1 + tests/functional/d/deprecated/deprecated_module_py33.py | 4 ++++ tests/functional/d/deprecated/deprecated_module_py33.txt | 1 + 5 files changed, 14 insertions(+) create mode 100644 tests/functional/d/deprecated/deprecated_module_py33.py create mode 100644 tests/functional/d/deprecated/deprecated_module_py33.txt diff --git a/ChangeLog b/ChangeLog index 0b17299186..76c92a0b78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -395,6 +395,10 @@ Release date: TBA Closes #258 +* Importing the deprecated stdlib module ``xml.etree.cElementTree`` now emits ``deprecated_module``. + + Closes #5862 + * ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions .. code-block:: python diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 4acac1ace5..e7fc63b888 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -382,6 +382,10 @@ Other Changes * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* Importing the deprecated stdlib module ``xml.etree.cElementTree`` now emits ``deprecated_module``. + + Closes #5862 + * Fixed false positive ``unexpected-keyword-arg`` for decorators. Closes #258 diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index fe6113953e..864abf7349 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -73,6 +73,7 @@ DEPRECATED_MODULES = { (0, 0, 0): {"tkinter.tix", "fpectl"}, (3, 2, 0): {"optparse"}, + (3, 3, 0): {"xml.etree.cElementTree"}, (3, 4, 0): {"imp"}, (3, 5, 0): {"formatter"}, (3, 6, 0): {"asynchat", "asyncore"}, diff --git a/tests/functional/d/deprecated/deprecated_module_py33.py b/tests/functional/d/deprecated/deprecated_module_py33.py new file mode 100644 index 0000000000..a9fc2bda9c --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py33.py @@ -0,0 +1,4 @@ +"""Test deprecated modules from Python 3.3.""" +# pylint: disable=unused-import + +import xml.etree.cElementTree # [deprecated-module] diff --git a/tests/functional/d/deprecated/deprecated_module_py33.txt b/tests/functional/d/deprecated/deprecated_module_py33.txt new file mode 100644 index 0000000000..120a9abfdc --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py33.txt @@ -0,0 +1 @@ +deprecated-module:4:0:4:29::Uses of a deprecated module 'xml.etree.cElementTree':UNDEFINED From d91ef1890e5f09939a228ad6e0c05101f0fc2235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 14:56:12 +0100 Subject: [PATCH 230/357] Bump actions/download-artifact from 2.1.0 to 3 (#5865) * Bump actions/download-artifact from 2.1.0 to 3 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2.1.0 to 3. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2.1.0...v3) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update .github/workflows/ci.yaml Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3679aea615..802f3a5739 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -263,7 +263,7 @@ jobs: echo "Failed to restore Python venv from cache" exit 1 - name: Download all coverage artifacts - uses: actions/download-artifact@v2.1.0 + uses: actions/download-artifact@v3.0.0 - name: Combine coverage results run: | . venv/bin/activate From 970c0d94443a045f2d3bd571ab29a01f5c5e14b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 17:10:09 +0100 Subject: [PATCH 231/357] Bump actions/checkout from 2.4.0 to 3.0.0 (#5866) * Bump actions/checkout from 2.4.0 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Pin 3.0.0 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .github/workflows/ci.yaml | 22 +++++++++++----------- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/primer-test.yaml | 8 ++++---- .github/workflows/release.yml | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 802f3a5739..4d0ddd9fe2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,7 +23,7 @@ jobs: pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 with: fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} @@ -81,7 +81,7 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v3.0.0 @@ -124,7 +124,7 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v3.0.0 @@ -159,7 +159,7 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} @@ -202,7 +202,7 @@ jobs: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 @@ -243,7 +243,7 @@ jobs: COVERAGERC_FILE: .coveragerc steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 @@ -287,7 +287,7 @@ jobs: python-version: [3.8, 3.9] steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 @@ -339,7 +339,7 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} @@ -386,7 +386,7 @@ jobs: # Workaround to set correct temp directory on Windows # https://github.com/actions/virtual-environments/issues/712 - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 @@ -421,7 +421,7 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} @@ -464,7 +464,7 @@ jobs: python-version: ["pypy-3.6"] steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 26c0eea1c1..29493d2071 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index ba2bc06b2e..566fc3ecea 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -32,7 +32,7 @@ jobs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} @@ -74,7 +74,7 @@ jobs: python-version: [3.8, 3.9, "3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v2.3.5 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 @@ -109,7 +109,7 @@ jobs: python-version: [3.8, 3.9, "3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v2.3.5 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 @@ -144,7 +144,7 @@ jobs: python-version: [3.8, 3.9, "3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v2.3.5 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v3.0.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c58d52616..1bf53bf711 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code from Github - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v3.0.0 From 499a5306fcc2a46c7431354e087df0edffb6f3d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 17:10:55 +0100 Subject: [PATCH 232/357] Bump actions/upload-artifact from 2.3.1 to 3.0.0 (#5867) * Bump actions/upload-artifact from 2.3.1 to 3 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.3.1 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2.3.1...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Pin 3.0.0 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4d0ddd9fe2..0ab58deb44 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -226,7 +226,7 @@ jobs: . venv/bin/activate pytest -vv --durations=20 --benchmark-disable --cov --cov-report= tests/ - name: Upload coverage artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3.0.0 with: name: coverage-${{ matrix.python-version }} path: .coverage @@ -321,7 +321,7 @@ jobs: run: >- echo "::set-output name=datetime::"$(date "+%Y%m%d_%H%M") - name: Upload benchmark artifact - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v3.0.0 with: name: benchmark-${{ runner.os }}-${{ matrix.python-version }}_${{ From c80bf2a7579545f381e71dd33adf6b3517a7651d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9o=20Bouvard?= Date: Mon, 7 Mar 2022 20:18:10 +0100 Subject: [PATCH 233/357] Fix Markdown link in ReStructuredText README (#5870) This commit updates a link formatted using Markdown syntax to use ReStructuredText syntax, which is what the README file is written in. This allows a prettier link visualization in GitHub, by not displaying the full URL between parentheses. --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index fb24114476..4967a00f9e 100644 --- a/README.rst +++ b/README.rst @@ -168,8 +168,7 @@ Use the badge in your project's README.rst (or any other rst file):: If you use GitHub Actions, and one of your CI workflows begins with "name: pylint", you -can use GitHub's -[workflow status badges](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge#using-the-workflow-file-name) +can use GitHub's `workflow status badges `_ to show an up-to-date indication of whether pushes to your default branch pass pylint. For more detailed information, check the documentation. From 942246f7572777d71f0fec7dc911dc25834cdb41 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 7 Mar 2022 20:22:10 +0100 Subject: [PATCH 234/357] Improve CI workflow (#5868) * Update timeouts [ci] * Fix pypy cache key [ci] * Stage different jobs [ci] --- .github/workflows/ci.yaml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0ab58deb44..06945c50fb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: prepare-base: name: Prepare base dependencies runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 outputs: python-key: ${{ steps.generate-python-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} @@ -77,7 +77,7 @@ jobs: formatting: name: checks / pre-commit runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 needs: prepare-base steps: - name: Check out code from GitHub @@ -151,7 +151,7 @@ jobs: prepare-tests-linux: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -194,7 +194,7 @@ jobs: pytest-linux: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 needs: prepare-tests-linux strategy: fail-fast: false @@ -279,8 +279,8 @@ jobs: benchmark-linux: name: tests / run benchmark / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 - needs: prepare-tests-linux + timeout-minutes: 10 + needs: ["prepare-tests-linux", "pytest-linux"] strategy: fail-fast: false matrix: @@ -331,7 +331,8 @@ jobs: prepare-tests-windows: name: tests / prepare / ${{ matrix.python-version }} / Windows runs-on: windows-latest - timeout-minutes: 5 + timeout-minutes: 10 + needs: pytest-linux strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] @@ -374,7 +375,7 @@ jobs: pytest-windows: name: tests / run / ${{ matrix.python-version }} / Windows runs-on: windows-latest - timeout-minutes: 10 + timeout-minutes: 15 needs: prepare-tests-windows strategy: fail-fast: false @@ -413,7 +414,7 @@ jobs: prepare-tests-pypy: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 strategy: matrix: python-version: ["pypy-3.6"] @@ -441,10 +442,10 @@ jobs: with: path: venv key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + ${{ runner.os }}-${{ matrix.python-version }}-${{ steps.generate-python-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ env.CACHE_VERSION }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}- - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -456,7 +457,7 @@ jobs: pytest-pypy: name: tests / run / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 needs: prepare-tests-pypy strategy: fail-fast: false @@ -476,7 +477,7 @@ jobs: with: path: venv key: - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + ${{ runner.os }}-${{ matrix.python-version }}-${{ needs.prepare-tests-pypy.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' From 6dd80f3d2d8b87e5ecdffd28a7d1c34b07a3c984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9o=20Bouvard?= Date: Mon, 7 Mar 2022 20:34:52 +0100 Subject: [PATCH 235/357] Fix pre-commit configuration hook URL (#5869) Removing the final slash in the `repo` url prevents git from not being able to clone the repository. --- .pre-commit-config.yaml | 2 +- CONTRIBUTORS.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0757c41c10..e3a1ad58f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: black args: [--safe, --quiet] exclude: *fixtures - - repo: https://github.com/Pierre-Sassoulas/black-disable-checker/ + - repo: https://github.com/Pierre-Sassoulas/black-disable-checker rev: 1.0.1 hooks: - id: black-disable-checker diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 88ab429cf1..491a49dc1a 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -603,3 +603,5 @@ contributors: - Improve non-ascii-name checker * Daniel Brookman: contributor + +* Téo Bouvard: contributor From 4649153dae5d55b3ac01787a7a61b25624077526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Mar 2022 15:32:49 +0100 Subject: [PATCH 236/357] Also test on PyPy 3.7 --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 06945c50fb..0ead6e5d76 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -417,7 +417,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - python-version: ["pypy-3.6"] + python-version: ["pypy-3.6", "pypy-3.7"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: @@ -462,7 +462,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy-3.6"] + python-version: ["pypy-3.6", "pypy-3.7"] steps: - name: Check out code from GitHub uses: actions/checkout@v3.0.0 From 444e9e1a6e67dba23d050e638ae027ee44414a08 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 8 Mar 2022 11:01:40 +0100 Subject: [PATCH 237/357] Restore the useful part of the python3 checker (#5843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reinstate checks from the python3 checker that are still useful for py3 Closes #5025 Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 6 +++ doc/whatsnew/2.13.rst | 6 +++ pylint/constants.py | 5 +-- pylint/extensions/__init__.py | 2 +- pylint/extensions/code_style.py | 2 +- pylint/extensions/eq_without_hash.py | 39 +++++++++++++++++++ .../ext/eq_without_hash/eq_without_hash.py | 11 ++++++ .../ext/eq_without_hash/eq_without_hash.rc | 2 + .../ext/eq_without_hash/eq_without_hash.txt | 1 + 9 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 pylint/extensions/eq_without_hash.py create mode 100644 tests/functional/ext/eq_without_hash/eq_without_hash.py create mode 100644 tests/functional/ext/eq_without_hash/eq_without_hash.rc create mode 100644 tests/functional/ext/eq_without_hash/eq_without_hash.txt diff --git a/ChangeLog b/ChangeLog index 76c92a0b78..51bc84fc54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -53,6 +53,12 @@ Release date: TBA Closes #5486 +* Reinstated checks from the python3 checker that are still useful for python 3 + (``eq-without-hash``). This is now in the ``pylint.extensions.eq_without_hash`` optional + extension. + + Closes #5025 + * Added several checkers to deal with unicode security issues (see `Trojan Sources `_ and `PEP 672 `_ for details) that also diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index e7fc63b888..6fe17d9956 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -110,6 +110,12 @@ Other Changes Closes #352 +* Reinstated checks from the python3 checker that are still useful for python 3 + (``eq-without-hash``). This is now in the ``pylint.extensions.eq_without_hash`` optional + extension. + + Closes #5025 + * Fix false-negative for ``assignment-from-none`` checker with list.sort() method. Closes #5722 diff --git a/pylint/constants.py b/pylint/constants.py index c3b94863dc..1a06ea8329 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -80,9 +80,7 @@ class DeletedMessage(NamedTuple): old_names: List[Tuple[str, str]] = [] -DELETED_MSGID_PREFIXES = [ - 16, # the PY3K+ checker, see https://github.com/PyCQA/pylint/pull/4942 -] +DELETED_MSGID_PREFIXES: List[int] = [] DELETED_MESSAGES = [ # Everything until the next comment is from the @@ -139,7 +137,6 @@ class DeletedMessage(NamedTuple): DeletedMessage("W1638", "range-builtin-not-iterating"), DeletedMessage("W1639", "filter-builtin-not-iterating"), DeletedMessage("W1640", "using-cmp-argument"), - DeletedMessage("W1641", "eq-without-hash"), DeletedMessage("W1642", "div-method"), DeletedMessage("W1643", "idiv-method"), DeletedMessage("W1644", "rdiv-method"), diff --git a/pylint/extensions/__init__.py b/pylint/extensions/__init__.py index c348361019..4af3aefb17 100644 --- a/pylint/extensions/__init__.py +++ b/pylint/extensions/__init__.py @@ -10,7 +10,7 @@ def initialize(linter: "PyLinter") -> None: - """Initialize linter with checkers in the extensions directory.""" + """Initialize linter with checkers in the extensions' directory.""" register_plugins(linter, __path__[0]) diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index 7082c991ce..5a3e3ba17d 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -28,7 +28,7 @@ class CodeStyleChecker(BaseChecker): i.e. detect a common issue or improve performance => it should probably be part of the core checker classes 2. Is it something that would improve code consistency, - maybe because it's slightly better with regards to performance + maybe because it's slightly better with regard to performance and therefore preferred => this is the right place 3. Everything else should go into another extension """ diff --git a/pylint/extensions/eq_without_hash.py b/pylint/extensions/eq_without_hash.py new file mode 100644 index 0000000000..b0dd6ce46f --- /dev/null +++ b/pylint/extensions/eq_without_hash.py @@ -0,0 +1,39 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +"""This is the remnant of the python3 checker. It was removed because +the transition from python 2 to python3 is behind us, but some checks +are still useful in python3 after all. +See https://github.com/PyCQA/pylint/issues/5025 +""" + +from astroid import nodes + +from pylint import checkers, interfaces +from pylint.checkers import utils +from pylint.lint import PyLinter + + +class EqWithoutHash(checkers.BaseChecker): + + __implements__ = interfaces.IAstroidChecker + name = "eq-without-hash" + + msgs = { + "W1641": ( + "Implementing __eq__ without also implementing __hash__", + "eq-without-hash", + "Used when a class implements __eq__ but not __hash__. Objects get " + "None as their default __hash__ implementation if they also implement __eq__.", + ), + } + + @utils.check_messages("eq-without-hash") + def visit_classdef(self, node: nodes.ClassDef) -> None: + locals_and_methods = set(node.locals).union(x.name for x in node.mymethods()) + if "__eq__" in locals_and_methods and "__hash__" not in locals_and_methods: + self.add_message("eq-without-hash", node=node, confidence=interfaces.HIGH) + + +def register(linter: PyLinter) -> None: + linter.register_checker(EqWithoutHash(linter)) diff --git a/tests/functional/ext/eq_without_hash/eq_without_hash.py b/tests/functional/ext/eq_without_hash/eq_without_hash.py new file mode 100644 index 0000000000..a28afc97bb --- /dev/null +++ b/tests/functional/ext/eq_without_hash/eq_without_hash.py @@ -0,0 +1,11 @@ +"""Regression test for #5025""" + +# pylint: disable=invalid-name, missing-docstring, too-few-public-methods + + +class AClass: # [eq-without-hash] + def __init__(self) -> None: + self.x = 5 + + def __eq__(self, other: object) -> bool: + return isinstance(other, AClass) and other.x == self.x diff --git a/tests/functional/ext/eq_without_hash/eq_without_hash.rc b/tests/functional/ext/eq_without_hash/eq_without_hash.rc new file mode 100644 index 0000000000..5a4c9fcce3 --- /dev/null +++ b/tests/functional/ext/eq_without_hash/eq_without_hash.rc @@ -0,0 +1,2 @@ +[MASTER] +load-plugins=pylint.extensions.eq_without_hash, diff --git a/tests/functional/ext/eq_without_hash/eq_without_hash.txt b/tests/functional/ext/eq_without_hash/eq_without_hash.txt new file mode 100644 index 0000000000..b7e2dc46da --- /dev/null +++ b/tests/functional/ext/eq_without_hash/eq_without_hash.txt @@ -0,0 +1 @@ +eq-without-hash:6:0:11:62:AClass:Implementing __eq__ without also implementing __hash__:HIGH From 6c0ba130730e82fc21063f138ffd05347bea0b9e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 8 Mar 2022 07:31:25 -0500 Subject: [PATCH 238/357] Removed unused detection of site-packages directory (#5874) --- pylint/checkers/imports.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index bd368a2c59..96e0066eba 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -50,8 +50,6 @@ import collections import copy import os -import sys -from distutils import sysconfig from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union import astroid @@ -441,33 +439,6 @@ def __init__(self, linter: Optional["PyLinter"] = None) -> None: ("RP0402", "Modules dependencies graph", self._report_dependencies_graph), ) - self._site_packages = self._compute_site_packages() - - @staticmethod - def _compute_site_packages(): - def _normalized_path(path): - return os.path.normcase(os.path.abspath(path)) - - paths = set() - real_prefix = getattr(sys, "real_prefix", None) - for prefix in filter(None, (real_prefix, sys.prefix)): - path = sysconfig.get_python_lib(prefix=prefix) - path = _normalized_path(path) - paths.add(path) - - # Handle Debian's derivatives /usr/local. - if os.path.isfile("/etc/debian_version"): - for prefix in filter(None, (real_prefix, sys.prefix)): - libpython = os.path.join( - prefix, - "local", - "lib", - "python" + sysconfig.get_python_version(), - "dist-packages", - ) - paths.add(libpython) - return paths - def open(self): """Called before visiting project (i.e set of modules).""" self.linter.stats.dependencies = {} From b1bb7f86bf08876e909fe135802a262fb2e5d82f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 8 Mar 2022 10:14:50 -0500 Subject: [PATCH 239/357] Add `distutils` to deprecated modules (#5864) --- ChangeLog | 2 ++ doc/whatsnew/2.13.rst | 2 ++ pylint/checkers/stdlib.py | 7 +++++++ tests/functional/d/deprecated/deprecated_module_py310.py | 4 ++++ tests/functional/d/deprecated/deprecated_module_py310.rc | 2 ++ tests/functional/d/deprecated/deprecated_module_py310.txt | 1 + .../r/regression_02/regression_distutil_import_error_73.py | 2 +- 7 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/functional/d/deprecated/deprecated_module_py310.py create mode 100644 tests/functional/d/deprecated/deprecated_module_py310.rc create mode 100644 tests/functional/d/deprecated/deprecated_module_py310.txt diff --git a/ChangeLog b/ChangeLog index 51bc84fc54..b4c72ba03d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -405,6 +405,8 @@ Release date: TBA Closes #5862 +* Importing the deprecated stdlib module ``distutils`` now emits ``deprecated_module`` on Python 3.10+. + * ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions .. code-block:: python diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 6fe17d9956..a0e5273db9 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -392,6 +392,8 @@ Other Changes Closes #5862 +* Importing the deprecated stdlib module ``distutils`` now emits ``deprecated_module`` on Python 3.10+. + * Fixed false positive ``unexpected-keyword-arg`` for decorators. Closes #258 diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 864abf7349..8270eb88cc 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -79,6 +79,7 @@ (3, 6, 0): {"asynchat", "asyncore"}, (3, 7, 0): {"macpath"}, (3, 9, 0): {"lib2to3", "parser", "symbol", "binhex"}, + (3, 10, 0): {"distutils"}, } DEPRECATED_ARGUMENTS = { @@ -126,6 +127,7 @@ "abc.abstractstaticmethod", "abc.abstractproperty", }, + (3, 4, 0): {"importlib.util.module_for_loader"}, } @@ -205,6 +207,10 @@ }, (3, 4, 0): { "importlib.find_loader", + "importlib.abc.Loader.load_module", + "importlib.abc.Loader.module_repr", + "importlib.abc.PathEntryFinder.find_loader", + "importlib.abc.PathEntryFinder.find_module", "plistlib.readPlist", "plistlib.writePlist", "plistlib.readPlistFromBytes", @@ -253,6 +259,7 @@ }, (3, 10, 0): { "_sqlite3.enable_shared_cache", + "importlib.abc.Finder.find_module", "pathlib.Path.link_to", "zipimport.zipimporter.load_module", "zipimport.zipimporter.find_module", diff --git a/tests/functional/d/deprecated/deprecated_module_py310.py b/tests/functional/d/deprecated/deprecated_module_py310.py new file mode 100644 index 0000000000..8321474762 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py310.py @@ -0,0 +1,4 @@ +"""Test deprecated modules from Python 3.10.""" +# pylint: disable=unused-import + +import distutils # [deprecated-module] diff --git a/tests/functional/d/deprecated/deprecated_module_py310.rc b/tests/functional/d/deprecated/deprecated_module_py310.rc new file mode 100644 index 0000000000..b29fd450ac --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py310.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver = 3.10 diff --git a/tests/functional/d/deprecated/deprecated_module_py310.txt b/tests/functional/d/deprecated/deprecated_module_py310.txt new file mode 100644 index 0000000000..a46667aa52 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py310.txt @@ -0,0 +1 @@ +deprecated-module:4:0:4:16::Uses of a deprecated module 'distutils':UNDEFINED diff --git a/tests/functional/r/regression_02/regression_distutil_import_error_73.py b/tests/functional/r/regression_02/regression_distutil_import_error_73.py index 751a4980f7..d40c0973fe 100644 --- a/tests/functional/r/regression_02/regression_distutil_import_error_73.py +++ b/tests/functional/r/regression_02/regression_distutil_import_error_73.py @@ -7,7 +7,7 @@ https://github.com/PyCQA/astroid/pull/1321 """ -# pylint: disable=unused-import +# pylint: disable=unused-import, deprecated-module import distutils.version from distutils.util import strtobool From 1ebdc8c59f31962db0244a79eaab9b8d90a87baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9o=20Bouvard?= Date: Wed, 9 Mar 2022 17:43:40 +0100 Subject: [PATCH 240/357] Fix pyreverse type hinting for class methods (#5881) * Fix pyreverse type hinting for class methods This commit fixes the alignment of arguments and their type annotations in pyreverse printer output. It does so by checking for the type of the current function rather than the name of the first argument. This allows class methods having a non-standard first argument (different from "self" or "cls") to be correctly serialized in class diagrams. * Add test for method with None args According to astroid docs, this happens for builtin functions implemented in C. In this case, we return an empty argument list. --- ChangeLog | 2 ++ pylint/pyreverse/printer.py | 15 +++++++-------- tests/data/clientmodule_test.py | 8 ++++++++ tests/pyreverse/data/classes_No_Name.dot | 2 +- tests/pyreverse/data/classes_No_Name.html | 2 ++ tests/pyreverse/data/classes_No_Name.mmd | 2 ++ tests/pyreverse/data/classes_No_Name.puml | 2 ++ tests/pyreverse/data/classes_No_Name.vcg | 2 +- tests/pyreverse/data/classes_colorized.dot | 2 +- tests/pyreverse/data/classes_colorized.puml | 2 ++ tests/pyreverse/test_printer.py | 10 ++++++++++ 11 files changed, 38 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index b4c72ba03d..d97f87c9fb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,8 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Fix pyreverse diagrams type hinting for classmethods and staticmethods. + * Fix matching ``--notes`` options that end in a non-word character. Closes #5840 diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index 559e456be3..ca7f9b0a2a 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -99,14 +99,13 @@ def emit_edge( @staticmethod def _get_method_arguments(method: nodes.FunctionDef) -> List[str]: - if method.args.args: - arguments: List[nodes.AssignName] = [ - arg for arg in method.args.args if arg.name != "self" - ] - else: - arguments = [] - - annotations = dict(zip(arguments, method.args.annotations[1:])) + if method.args.args is None: + return [] + + first_arg = 0 if method.type in {"function", "staticmethod"} else 1 + arguments: List[nodes.AssignName] = method.args.args[first_arg:] + + annotations = dict(zip(arguments, method.args.annotations[first_arg:])) for arg in arguments: annotation_label = "" ann = annotations.get(arg) diff --git a/tests/data/clientmodule_test.py b/tests/data/clientmodule_test.py index 82deaaf6f8..257aadec1d 100644 --- a/tests/data/clientmodule_test.py +++ b/tests/data/clientmodule_test.py @@ -28,3 +28,11 @@ def __init__(self, value, _id, relation2: DoNothing2): self._id = _id self.relation = DoNothing() self.relation2 = relation2 + + @classmethod + def from_value(cls, value: int): + return cls(value, 0, DoNothing2()) + + @staticmethod + def transform_value(value: int) -> int: + return value * 2 diff --git a/tests/pyreverse/data/classes_No_Name.dot b/tests/pyreverse/data/classes_No_Name.dot index b2c5551088..e5a304ce37 100644 --- a/tests/pyreverse/data/classes_No_Name.dot +++ b/tests/pyreverse/data/classes_No_Name.dot @@ -8,7 +8,7 @@ charset="utf-8" "data.suppliermodule_test.DoSomething" [color="black", fontcolor="black", label="{DoSomething|my_int : Optional[int]\lmy_int_2 : Optional[int]\lmy_string : str\l|do_it(new_int: int): int\l}", shape="record", style="solid"]; "data.suppliermodule_test.Interface" [color="black", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; "data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label="{PropertyPatterns|prop1\lprop2\l|}", shape="record", style="solid"]; -"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|}", shape="record", style="solid"]; +"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|from_value(value: int)\ltransform_value(value: int): int\l}", shape="record", style="solid"]; "data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"]; "data.clientmodule_test.Ancestor" -> "data.suppliermodule_test.Interface" [arrowhead="empty", arrowtail="node", style="dashed"]; "data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Ancestor" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; diff --git a/tests/pyreverse/data/classes_No_Name.html b/tests/pyreverse/data/classes_No_Name.html index 3f81c340e5..420a869d4f 100644 --- a/tests/pyreverse/data/classes_No_Name.html +++ b/tests/pyreverse/data/classes_No_Name.html @@ -35,6 +35,8 @@ relation relation2 top : str + from_value(value: int) + transform_value(value: int) -> int } Specialization --|> Ancestor Ancestor ..|> Interface diff --git a/tests/pyreverse/data/classes_No_Name.mmd b/tests/pyreverse/data/classes_No_Name.mmd index d2ac9839db..50da7f39cf 100644 --- a/tests/pyreverse/data/classes_No_Name.mmd +++ b/tests/pyreverse/data/classes_No_Name.mmd @@ -30,6 +30,8 @@ classDiagram relation relation2 top : str + from_value(value: int) + transform_value(value: int) -> int } Specialization --|> Ancestor Ancestor ..|> Interface diff --git a/tests/pyreverse/data/classes_No_Name.puml b/tests/pyreverse/data/classes_No_Name.puml index 1d9e5f37da..a0f8350d0d 100644 --- a/tests/pyreverse/data/classes_No_Name.puml +++ b/tests/pyreverse/data/classes_No_Name.puml @@ -31,6 +31,8 @@ class "Specialization" as data.clientmodule_test.Specialization { relation relation2 top : str + from_value(value: int) + transform_value(value: int) -> int } data.clientmodule_test.Specialization --|> data.clientmodule_test.Ancestor data.clientmodule_test.Ancestor ..|> data.suppliermodule_test.Interface diff --git a/tests/pyreverse/data/classes_No_Name.vcg b/tests/pyreverse/data/classes_No_Name.vcg index bc542b64b1..6497f26b4e 100644 --- a/tests/pyreverse/data/classes_No_Name.vcg +++ b/tests/pyreverse/data/classes_No_Name.vcg @@ -25,7 +25,7 @@ graph:{ node: {title:"data.property_pattern.PropertyPatterns" label:"\fbPropertyPatterns\fn\n\f__________________\n\f08prop1\n\f08prop2\n\f__________________" shape:box } - node: {title:"data.clientmodule_test.Specialization" label:"\fbSpecialization\fn\n\f________________\n\f08TYPE : str\n\f08relation\n\f08relation2\n\f08top : str\n\f________________" + node: {title:"data.clientmodule_test.Specialization" label:"\fbSpecialization\fn\n\f_________________\n\f08TYPE : str\n\f08relation\n\f08relation2\n\f08top : str\n\f_________________\n\f10from_value()\n\f10transform_value()" shape:box } edge: {sourcename:"data.clientmodule_test.Specialization" targetname:"data.clientmodule_test.Ancestor" arrowstyle:solid diff --git a/tests/pyreverse/data/classes_colorized.dot b/tests/pyreverse/data/classes_colorized.dot index 0ed3285666..3b68c79336 100644 --- a/tests/pyreverse/data/classes_colorized.dot +++ b/tests/pyreverse/data/classes_colorized.dot @@ -8,7 +8,7 @@ charset="utf-8" "data.suppliermodule_test.DoSomething" [color="aliceblue", fontcolor="black", label="{DoSomething|my_int : Optional[int]\lmy_int_2 : Optional[int]\lmy_string : str\l|do_it(new_int: int): int\l}", shape="record", style="filled"]; "data.suppliermodule_test.Interface" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; "data.property_pattern.PropertyPatterns" [color="aliceblue", fontcolor="black", label="{PropertyPatterns|prop1\lprop2\l|}", shape="record", style="filled"]; -"data.clientmodule_test.Specialization" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|}", shape="record", style="filled"]; +"data.clientmodule_test.Specialization" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|from_value(value: int)\ltransform_value(value: int): int\l}", shape="record", style="filled"]; "data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"]; "data.clientmodule_test.Ancestor" -> "data.suppliermodule_test.Interface" [arrowhead="empty", arrowtail="node", style="dashed"]; "data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Ancestor" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; diff --git a/tests/pyreverse/data/classes_colorized.puml b/tests/pyreverse/data/classes_colorized.puml index 611a669dbf..8a37f090f4 100644 --- a/tests/pyreverse/data/classes_colorized.puml +++ b/tests/pyreverse/data/classes_colorized.puml @@ -31,6 +31,8 @@ class "Specialization" as data.clientmodule_test.Specialization #aliceblue { relation relation2 top : str + from_value(value: int) + transform_value(value: int) -> int } data.clientmodule_test.Specialization --|> data.clientmodule_test.Ancestor data.clientmodule_test.Ancestor ..|> data.suppliermodule_test.Interface diff --git a/tests/pyreverse/test_printer.py b/tests/pyreverse/test_printer.py index 6d240c8362..5406c6e83f 100644 --- a/tests/pyreverse/test_printer.py +++ b/tests/pyreverse/test_printer.py @@ -8,6 +8,7 @@ from typing import Type import pytest +from astroid import nodes from pylint.pyreverse.dot_printer import DotPrinter from pylint.pyreverse.plantuml_printer import PlantUmlPrinter @@ -46,6 +47,15 @@ def test_unsupported_layout(layout: Layout, printer_class: Type[Printer]): printer_class(title="unittest", layout=layout) +def test_method_arguments_none(): + func = nodes.FunctionDef() + args = nodes.Arguments() + args.args = None + func.postinit(args, body=None) + parsed_args = Printer._get_method_arguments(func) + assert parsed_args == [] + + class TestPlantUmlPrinter: printer = PlantUmlPrinter(title="unittest", layout=Layout.TOP_TO_BOTTOM) From 51d414dc472e0d977c7a60b37801e22be8611f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 10 Mar 2022 13:43:20 +0100 Subject: [PATCH 241/357] Use the ``tomli`` package instead of ``toml`` to parse ``.toml`` (#5887) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .pre-commit-config.yaml | 3 +-- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/config/find_default_config_files.py | 13 ++++++++----- pylint/config/option_manager_mixin.py | 13 ++++++++----- requirements_test.txt | 1 - setup.cfg | 5 +---- .../toml/issue_3181/toml_decode_error.1.out | 2 +- 8 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3a1ad58f6..c440a0ca37 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -86,8 +86,7 @@ repos: types: [python] args: [] require_serial: true - additional_dependencies: - ["platformdirs==2.2.0", "types-pkg_resources==0.1.3", "types-toml==0.1.3"] + additional_dependencies: ["platformdirs==2.2.0", "types-pkg_resources==0.1.3"] exclude: tests/functional/|tests/input|tests(/.*)*/data|tests/regrtest_data/|tests/data/|tests(/.*)+/conftest.py|doc/|bin/ - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.5.1 diff --git a/ChangeLog b/ChangeLog index d97f87c9fb..2e824df6ed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -83,6 +83,10 @@ Release date: TBA Closes #5281 +* Use the ``tomli`` package instead of ``toml`` to parse ``.toml`` files. + + Closes #5885 + * Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``. Closes #5312 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a0e5273db9..0830fd561a 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -129,6 +129,10 @@ Other Changes Closes #5614 +* Use the ``tomli`` package instead of ``toml`` to parse ``.toml`` files. + + Closes #5885 + * Fixed false positive ``consider-using-dict-comprehension`` when creating a dict using a list of tuples where key AND value vary depending on the same condition. diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index 10c31345de..73a7f0f8a5 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -3,17 +3,20 @@ import configparser import os +import sys from typing import Iterator, Optional -import toml -from toml import TomlDecodeError +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib def _toml_has_config(path): - with open(path, encoding="utf-8") as toml_handle: + with open(path, mode="rb") as toml_handle: try: - content = toml.load(toml_handle) - except TomlDecodeError as error: + content = tomllib.load(toml_handle) + except tomllib.TOMLDecodeError as error: print(f"Failed to load '{path}': {error}") return False diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 9260f75930..387c628235 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -14,13 +14,16 @@ from types import ModuleType from typing import Dict, List, Optional, TextIO, Tuple, Union -import toml - from pylint import utils from pylint.config.man_help_formatter import _ManHelpFormatter from pylint.config.option import Option from pylint.config.option_parser import OptionParser +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + def _expand_default(self, option): """Patch OptionParser.expand_default with custom behaviour. @@ -276,7 +279,7 @@ def read_config_file(self, config_file=None, verbose=None): if config_file.endswith(".toml"): try: self._parse_toml(config_file, parser) - except toml.TomlDecodeError as e: + except tomllib.TOMLDecodeError as e: self.add_message("config-parse-error", line=0, args=str(e)) else: # Use this encoding in order to strip the BOM marker, if any. @@ -300,8 +303,8 @@ def _parse_toml( self, config_file: Union[Path, str], parser: configparser.ConfigParser ) -> None: """Parse and handle errors of a toml configuration file.""" - with open(config_file, encoding="utf-8") as fp: - content = toml.load(fp) + with open(config_file, mode="rb") as fp: + content = tomllib.load(fp) try: sections_values = content["tool"]["pylint"] except KeyError: diff --git a/requirements_test.txt b/requirements_test.txt index 052ac74d55..bcc6311fc2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,4 +10,3 @@ pytest-profiling~=1.7 pytest-xdist~=2.5 # Type packages for mypy types-pkg_resources==0.1.3 -types-toml==0.10.4 diff --git a/setup.cfg b/setup.cfg index 26318dd7cb..8c148180fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,7 @@ install_requires = astroid>=2.9.2,<=2.10.0-dev0 isort>=4.2.5,<6 mccabe>=0.6,<0.7 - toml>=0.9.2 + tomli>=1.1.0;python_version<"3.11" colorama;sys_platform=="win32" typing-extensions>=3.10.0;python_version<"3.10" python_requires = >=3.6.2 @@ -125,9 +125,6 @@ ignore_missing_imports = True [mypy-_string] ignore_missing_imports = True -[mypy-toml.decoder] -ignore_missing_imports = True - [mypy-git.*] ignore_missing_imports = True diff --git a/tests/config/functional/toml/issue_3181/toml_decode_error.1.out b/tests/config/functional/toml/issue_3181/toml_decode_error.1.out index 545acbd2dd..4511afc5c1 100644 --- a/tests/config/functional/toml/issue_3181/toml_decode_error.1.out +++ b/tests/config/functional/toml/issue_3181/toml_decode_error.1.out @@ -1,2 +1,2 @@ ************* Module {abspath} -{relpath}:1:0: F0011: error while parsing the configuration: Found invalid character in key name: '*'. Try quoting the key name. (line 3 column 2 char 48) (config-parse-error) +{relpath}:1:0: F0011: error while parsing the configuration: Invalid statement (at line 3, column 1) (config-parse-error) From c01758eebcc2e15c0fc9b95095230f07a0ac8a02 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 7 Mar 2022 18:42:22 -0500 Subject: [PATCH 242/357] Fix typo in ColorizedTextReporter deprecation warning --- pylint/reporters/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 6743ab5b15..2e1abf6bfa 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -303,7 +303,7 @@ def __init__( list(color_mapping.values())[0], MessageStyle ): warnings.warn( - "In pylint 3.0, the ColoreziedTextReporter will only accept ColorMappingDict as color_mapping parameter", + "In pylint 3.0, the ColorizedTextReporter will only accept ColorMappingDict as color_mapping parameter", DeprecationWarning, ) temp_color_mapping: ColorMappingDict = {} From 94f8099ce2365f2a94b93789cdf634bd4f031c71 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 7 Mar 2022 18:45:45 -0500 Subject: [PATCH 243/357] Correct type annotation of ImportsChecker.dependencies_stat --- pylint/checkers/imports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 96e0066eba..bcad7692ed 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -829,7 +829,7 @@ def _add_imported_module( self._module_pkg[context_name] = context_name.rsplit(".", 1)[0] # handle dependencies - dependencies_stat: Dict[str, Union[Set]] = self.linter.stats.dependencies + dependencies_stat: Dict[str, Set[str]] = self.linter.stats.dependencies importedmodnames = dependencies_stat.setdefault(importedmodname, set()) if context_name not in importedmodnames: importedmodnames.add(context_name) From 1faf365b6a45e9a3776287d13d3a7e634b42af29 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 17:13:47 +0100 Subject: [PATCH 244/357] Add broken NoReturn check (#5304) --- ChangeLog | 7 +++ doc/whatsnew/2.13.rst | 6 +++ pylint/extensions/typing.py | 51 ++++++++++++++++++- requirements_test_min.txt | 1 + .../ext/typing/typing_broken_noreturn.py | 32 ++++++++++++ .../ext/typing/typing_broken_noreturn.rc | 3 ++ .../ext/typing/typing_broken_noreturn.txt | 4 ++ .../typing_broken_noreturn_future_import.py | 37 ++++++++++++++ .../typing_broken_noreturn_future_import.rc | 6 +++ .../typing_broken_noreturn_future_import.txt | 1 + .../typing/typing_broken_noreturn_py372.py | 34 +++++++++++++ .../typing/typing_broken_noreturn_py372.rc | 3 ++ 12 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 tests/functional/ext/typing/typing_broken_noreturn.py create mode 100644 tests/functional/ext/typing/typing_broken_noreturn.rc create mode 100644 tests/functional/ext/typing/typing_broken_noreturn.txt create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_future_import.py create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_future_import.rc create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_future_import.txt create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_py372.py create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_py372.rc diff --git a/ChangeLog b/ChangeLog index 2e824df6ed..3d80754279 100644 --- a/ChangeLog +++ b/ChangeLog @@ -400,6 +400,12 @@ Release date: TBA Closes #5371 +* ``TypingChecker`` + + * Added new check ``broken-noreturn`` to detect broken uses of ``typing.NoReturn`` + if ``py-version`` is set to Python ``3.7.1`` or below. + https://bugs.python.org/issue34921 + * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. @@ -555,6 +561,7 @@ Release date: 2021-11-24 * Properly identify parameters with no documentation and add new message called ``missing-any-param-doc`` Closes #3799 + * Add checkers ``overridden-final-method`` & ``subclassed-final-class`` Closes #3197 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 0830fd561a..47a6ba8446 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -75,6 +75,12 @@ Extensions * Pyreverse - add output in mermaid-js format and html which is an mermaid js diagram with html boilerplate +* ``TypingChecker`` + + * Added new check ``broken-noreturn`` to detect broken uses of ``typing.NoReturn`` + if ``py-version`` is set to Python ``3.7.1`` or below. + https://bugs.python.org/issue34921 + * ``DocstringParameterChecker`` * Fixed incorrect classification of Numpy-style docstring as Google-style docstring for diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index cc68bc35ef..949156fde5 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -7,9 +7,10 @@ from pylint.checkers.utils import ( check_messages, is_node_in_type_annotation_context, + is_postponed_evaluation_enabled, safe_infer, ) -from pylint.interfaces import IAstroidChecker +from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils.utils import get_global_option if TYPE_CHECKING: @@ -68,6 +69,12 @@ class TypingAlias(NamedTuple): ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES) UNION_NAMES = ("Optional", "Union") +TYPING_NORETURN = frozenset( + ( + "typing.NoReturn", + "typing_extensions.NoReturn", + ) +) class DeprecatedTypingAliasMsg(NamedTuple): @@ -103,6 +110,14 @@ class TypingChecker(BaseChecker): "Emitted when 'typing.Union' or 'typing.Optional' is used " "instead of the alternative Union syntax 'int | None'.", ), + "E6004": ( + "'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1", + "broken-noreturn", + "``typing.NoReturn`` inside compound types is broken in " + "Python 3.7.0 and 3.7.1. If not dependent on runtime introspection, " + "use string annotation instead. E.g. " + "``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921", + ), } options = ( ( @@ -153,6 +168,8 @@ def open(self) -> None: self._py37_plus and self.config.runtime_typing is False ) + self._should_check_noreturn = py_version < (3, 7, 2) + def _msg_postponed_eval_hint(self, node) -> str: """Message hint if postponed evaluation isn't enabled.""" if self._py310_plus or "annotations" in node.root().future_imports: @@ -163,23 +180,29 @@ def _msg_postponed_eval_hint(self, node) -> str: "deprecated-typing-alias", "consider-using-alias", "consider-alternative-union-syntax", + "broken-noreturn", ) def visit_name(self, node: nodes.Name) -> None: if self._should_check_typing_alias and node.name in ALIAS_NAMES: self._check_for_typing_alias(node) if self._should_check_alternative_union_syntax and node.name in UNION_NAMES: self._check_for_alternative_union_syntax(node, node.name) + if self._should_check_noreturn and node.name == "NoReturn": + self._check_broken_noreturn(node) @check_messages( "deprecated-typing-alias", "consider-using-alias", "consider-alternative-union-syntax", + "broken-noreturn", ) def visit_attribute(self, node: nodes.Attribute) -> None: if self._should_check_typing_alias and node.attrname in ALIAS_NAMES: self._check_for_typing_alias(node) if self._should_check_alternative_union_syntax and node.attrname in UNION_NAMES: self._check_for_alternative_union_syntax(node, node.attrname) + if self._should_check_noreturn and node.attrname == "NoReturn": + self._check_broken_noreturn(node) def _check_for_alternative_union_syntax( self, @@ -279,6 +302,32 @@ def leave_module(self, node: nodes.Module) -> None: self._alias_name_collisions.clear() self._consider_using_alias_msgs.clear() + def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> None: + """Check for 'NoReturn' inside compound types.""" + if not isinstance(node.parent, nodes.BaseContainer): + # NoReturn not part of a Union or Callable type + return + + if is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context( + node + ): + return + + for inferred in node.infer(): + # To deal with typing_extensions, don't use safe_infer + if ( + isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef)) + and inferred.qname() in TYPING_NORETURN + # In Python 3.6, NoReturn is alias of '_NoReturn' + # In Python 3.7 - 3.8, NoReturn is alias of '_SpecialForm' + or isinstance(inferred, astroid.bases.BaseInstance) + and isinstance(inferred._proxied, nodes.ClassDef) + and inferred._proxied.qname() + in {"typing._NoReturn", "typing._SpecialForm"} + ): + self.add_message("broken-noreturn", node=node, confidence=INFERENCE) + break + def register(linter: "PyLinter") -> None: linter.register_checker(TypingChecker(linter)) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 79f99044b4..8c5b0f9c38 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,5 +1,6 @@ -e .[testutil] # astroid dependency is also defined in setup.cfg astroid==2.9.3 # Pinned to a specific version for tests +typing-extensions~=4.0 pytest~=7.0 pytest-benchmark~=3.4 diff --git a/tests/functional/ext/typing/typing_broken_noreturn.py b/tests/functional/ext/typing/typing_broken_noreturn.py new file mode 100644 index 0000000000..f675fce6aa --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn.py @@ -0,0 +1,32 @@ +""" +'typing.NoReturn' is broken inside compond types for Python 3.7.0 +https://bugs.python.org/issue34921 + +If no runtime introspection is required, use string annotations instead. +""" +# pylint: disable=missing-docstring +import typing +from typing import Callable, NoReturn, Union + +import typing_extensions + + +def func1() -> NoReturn: + raise Exception + +def func2() -> Union[None, NoReturn]: # [broken-noreturn] + pass + +def func3() -> Union[None, "NoReturn"]: + pass + +def func4() -> Union[None, typing.NoReturn]: # [broken-noreturn] + pass + +def func5() -> Union[None, typing_extensions.NoReturn]: # [broken-noreturn] + pass + + +Alias1 = NoReturn +Alias2 = Callable[..., NoReturn] # [broken-noreturn] +Alias3 = Callable[..., "NoReturn"] diff --git a/tests/functional/ext/typing/typing_broken_noreturn.rc b/tests/functional/ext/typing/typing_broken_noreturn.rc new file mode 100644 index 0000000000..56e0bed570 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn.rc @@ -0,0 +1,3 @@ +[master] +py-version=3.7 +load-plugins=pylint.extensions.typing diff --git a/tests/functional/ext/typing/typing_broken_noreturn.txt b/tests/functional/ext/typing/typing_broken_noreturn.txt new file mode 100644 index 0000000000..ce4503341f --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn.txt @@ -0,0 +1,4 @@ +broken-noreturn:17:27:17:35:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:23:27:23:42:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:26:27:26:53:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:31:23:31:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.py b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py new file mode 100644 index 0000000000..83ec547499 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py @@ -0,0 +1,37 @@ +""" +'typing.NoReturn' is broken inside compond types for Python 3.7.0 +https://bugs.python.org/issue34921 + +If no runtime introspection is required, use string annotations instead. + +With 'from __future__ import annotations', only emit errors for nodes +not in a type annotation context. +""" +# pylint: disable=missing-docstring +from __future__ import annotations + +import typing +from typing import Callable, NoReturn, Union + +import typing_extensions + + +def func1() -> NoReturn: + raise Exception + +def func2() -> Union[None, NoReturn]: + pass + +def func3() -> Union[None, "NoReturn"]: + pass + +def func4() -> Union[None, typing.NoReturn]: + pass + +def func5() -> Union[None, typing_extensions.NoReturn]: + pass + + +Alias1 = NoReturn +Alias2 = Callable[..., NoReturn] # [broken-noreturn] +Alias3 = Callable[..., "NoReturn"] diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc b/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc new file mode 100644 index 0000000000..92ef9524a3 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc @@ -0,0 +1,6 @@ +[master] +py-version=3.7 +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7 diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt b/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt new file mode 100644 index 0000000000..891a3a4d8b --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt @@ -0,0 +1 @@ +broken-noreturn:36:23:36:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE diff --git a/tests/functional/ext/typing/typing_broken_noreturn_py372.py b/tests/functional/ext/typing/typing_broken_noreturn_py372.py new file mode 100644 index 0000000000..c4b18a0337 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_py372.py @@ -0,0 +1,34 @@ +""" +'typing.NoReturn' is broken inside compond types for Python 3.7.0 +https://bugs.python.org/issue34921 + +If no runtime introspection is required, use string annotations instead. + +Don't emit errors if py-version set to >= 3.7.2. +""" +# pylint: disable=missing-docstring +import typing +from typing import Callable, NoReturn, Union + +import typing_extensions + + +def func1() -> NoReturn: + raise Exception + +def func2() -> Union[None, NoReturn]: + pass + +def func3() -> Union[None, "NoReturn"]: + pass + +def func4() -> Union[None, typing.NoReturn]: + pass + +def func5() -> Union[None, typing_extensions.NoReturn]: + pass + + +Alias1 = NoReturn +Alias2 = Callable[..., NoReturn] +Alias3 = Callable[..., "NoReturn"] diff --git a/tests/functional/ext/typing/typing_broken_noreturn_py372.rc b/tests/functional/ext/typing/typing_broken_noreturn_py372.rc new file mode 100644 index 0000000000..24550b2c9d --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_py372.rc @@ -0,0 +1,3 @@ +[master] +py-version=3.7.2 +load-plugins=pylint.extensions.typing From 6d30e12f4413f1252fde58950e4b9a430585e5d0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 23:16:58 +0100 Subject: [PATCH 245/357] Add broken Callable check (#5891) --- ChangeLog | 4 + doc/whatsnew/2.13.rst | 4 + pylint/extensions/typing.py | 104 ++++++++++++++++-- .../ext/typing/typing_broken_callable.py | 28 +++++ .../ext/typing/typing_broken_callable.rc | 6 + .../ext/typing/typing_broken_callable.txt | 4 + ...typing_broken_callable_deprecated_alias.py | 26 +++++ ...typing_broken_callable_deprecated_alias.rc | 6 + .../typing_broken_callable_future_import.py | 30 +++++ .../typing_broken_callable_future_import.rc | 6 + .../typing_broken_callable_future_import.txt | 2 + 11 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 tests/functional/ext/typing/typing_broken_callable.py create mode 100644 tests/functional/ext/typing/typing_broken_callable.rc create mode 100644 tests/functional/ext/typing/typing_broken_callable.txt create mode 100644 tests/functional/ext/typing/typing_broken_callable_deprecated_alias.py create mode 100644 tests/functional/ext/typing/typing_broken_callable_deprecated_alias.rc create mode 100644 tests/functional/ext/typing/typing_broken_callable_future_import.py create mode 100644 tests/functional/ext/typing/typing_broken_callable_future_import.rc create mode 100644 tests/functional/ext/typing/typing_broken_callable_future_import.txt diff --git a/ChangeLog b/ChangeLog index 3d80754279..ff34a28ce3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -406,6 +406,10 @@ Release date: TBA if ``py-version`` is set to Python ``3.7.1`` or below. https://bugs.python.org/issue34921 + * Added new check ``broken-collections-callable`` to detect broken uses of ``collections.abc.Callable`` + if ``py-version`` is set to Python ``3.9.1`` or below. + https://bugs.python.org/issue42965 + * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 47a6ba8446..a71a18b4b9 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -81,6 +81,10 @@ Extensions if ``py-version`` is set to Python ``3.7.1`` or below. https://bugs.python.org/issue34921 + * Added new check ``broken-collections-callable`` to detect broken uses of ``collections.abc.Callable`` + if ``py-version`` is set to Python ``3.9.1`` or below. + https://bugs.python.org/issue42965 + * ``DocstringParameterChecker`` * Fixed incorrect classification of Numpy-style docstring as Google-style docstring for diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 949156fde5..9da7b109f6 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -81,7 +81,7 @@ class DeprecatedTypingAliasMsg(NamedTuple): node: Union[nodes.Name, nodes.Attribute] qname: str alias: str - parent_subscript: bool + parent_subscript: bool = False class TypingChecker(BaseChecker): @@ -118,6 +118,14 @@ class TypingChecker(BaseChecker): "use string annotation instead. E.g. " "``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921", ), + "E6005": ( + "'collections.abc.Callable' inside Optional and Union is broken in " + "3.9.0 / 3.9.1 (use 'typing.Callable' instead)", + "broken-collections-callable", + "``collections.abc.Callable`` inside Optional and Union is broken in " + "Python 3.9.0 and 3.9.1. Use ``typing.Callable`` for these cases instead. " + "https://bugs.python.org/issue42965", + ), } options = ( ( @@ -152,7 +160,9 @@ class TypingChecker(BaseChecker): def __init__(self, linter: "PyLinter") -> None: """Initialize checker instance.""" super().__init__(linter=linter) + self._found_broken_callable_location: bool = False self._alias_name_collisions: Set[str] = set() + self._deprecated_typing_alias_msgs: List[DeprecatedTypingAliasMsg] = [] self._consider_using_alias_msgs: List[DeprecatedTypingAliasMsg] = [] def open(self) -> None: @@ -169,8 +179,9 @@ def open(self) -> None: ) self._should_check_noreturn = py_version < (3, 7, 2) + self._should_check_callable = py_version < (3, 9, 2) - def _msg_postponed_eval_hint(self, node) -> str: + def _msg_postponed_eval_hint(self, node: nodes.NodeNG) -> str: """Message hint if postponed evaluation isn't enabled.""" if self._py310_plus or "annotations" in node.root().future_imports: return "" @@ -181,6 +192,7 @@ def _msg_postponed_eval_hint(self, node) -> str: "consider-using-alias", "consider-alternative-union-syntax", "broken-noreturn", + "broken-collections-callable", ) def visit_name(self, node: nodes.Name) -> None: if self._should_check_typing_alias and node.name in ALIAS_NAMES: @@ -189,12 +201,15 @@ def visit_name(self, node: nodes.Name) -> None: self._check_for_alternative_union_syntax(node, node.name) if self._should_check_noreturn and node.name == "NoReturn": self._check_broken_noreturn(node) + if self._should_check_callable and node.name == "Callable": + self._check_broken_callable(node) @check_messages( "deprecated-typing-alias", "consider-using-alias", "consider-alternative-union-syntax", "broken-noreturn", + "broken-collections-callable", ) def visit_attribute(self, node: nodes.Attribute) -> None: if self._should_check_typing_alias and node.attrname in ALIAS_NAMES: @@ -203,6 +218,8 @@ def visit_attribute(self, node: nodes.Attribute) -> None: self._check_for_alternative_union_syntax(node, node.attrname) if self._should_check_noreturn and node.attrname == "NoReturn": self._check_broken_noreturn(node) + if self._should_check_callable and node.attrname == "Callable": + self._check_broken_callable(node) def _check_for_alternative_union_syntax( self, @@ -255,10 +272,16 @@ def _check_for_typing_alias( return if self._py39_plus: - self.add_message( - "deprecated-typing-alias", - node=node, - args=(inferred.qname(), alias.name), + if inferred.qname() == "typing.Callable" and self._broken_callable_location( + node + ): + self._found_broken_callable_location = True + self._deprecated_typing_alias_msgs.append( + DeprecatedTypingAliasMsg( + node, + inferred.qname(), + alias.name, + ) ) return @@ -284,7 +307,20 @@ def leave_module(self, node: nodes.Module) -> None: 'consider-using-alias' check. Make sure results are safe to recommend / collision free. """ - if self._py37_plus and not self._py39_plus: + if self._py39_plus: + for msg in self._deprecated_typing_alias_msgs: + if ( + self._found_broken_callable_location + and msg.qname == "typing.Callable" + ): + continue + self.add_message( + "deprecated-typing-alias", + node=msg.node, + args=(msg.qname, msg.alias), + ) + + elif self._py37_plus: msg_future_import = self._msg_postponed_eval_hint(node) for msg in self._consider_using_alias_msgs: if msg.qname in self._alias_name_collisions: @@ -298,7 +334,10 @@ def leave_module(self, node: nodes.Module) -> None: msg_future_import if msg.parent_subscript else "", ), ) + # Clear all module cache variables + self._found_broken_callable_location = False + self._deprecated_typing_alias_msgs.clear() self._alias_name_collisions.clear() self._consider_using_alias_msgs.clear() @@ -328,6 +367,57 @@ def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> No self.add_message("broken-noreturn", node=node, confidence=INFERENCE) break + def _check_broken_callable(self, node: Union[nodes.Name, nodes.Attribute]) -> None: + """Check for 'collections.abc.Callable' inside Optional and Union.""" + inferred = safe_infer(node) + if not ( + isinstance(inferred, nodes.ClassDef) + and inferred.qname() == "_collections_abc.Callable" + and self._broken_callable_location(node) + ): + return + + self.add_message("broken-collections-callable", node=node, confidence=INFERENCE) + + def _broken_callable_location( # pylint: disable=no-self-use + self, node: Union[nodes.Name, nodes.Attribute] + ) -> bool: + """Check if node would be a broken location for collections.abc.Callable.""" + if is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context( + node + ): + return False + + # Check first Callable arg is a list of arguments -> Callable[[int], None] + if not ( + isinstance(node.parent, nodes.Subscript) + and isinstance(node.parent.slice, nodes.Tuple) + and len(node.parent.slice.elts) == 2 + and isinstance(node.parent.slice.elts[0], nodes.List) + ): + return False + + # Check nested inside Optional or Union + parent_subscript = node.parent.parent + if isinstance(parent_subscript, nodes.BaseContainer): + parent_subscript = parent_subscript.parent + if not ( + isinstance(parent_subscript, nodes.Subscript) + and isinstance(parent_subscript.value, (nodes.Name, nodes.Attribute)) + ): + return False + + inferred_parent = safe_infer(parent_subscript.value) + if not ( + isinstance(inferred_parent, nodes.FunctionDef) + and inferred_parent.qname() in {"typing.Optional", "typing.Union"} + or isinstance(inferred_parent, astroid.bases.Instance) + and inferred_parent.qname() == "typing._SpecialForm" + ): + return False + + return True + def register(linter: "PyLinter") -> None: linter.register_checker(TypingChecker(linter)) diff --git a/tests/functional/ext/typing/typing_broken_callable.py b/tests/functional/ext/typing/typing_broken_callable.py new file mode 100644 index 0000000000..5cdac7dd75 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable.py @@ -0,0 +1,28 @@ +""" +'collections.abc.Callable' is broken inside Optional and Union types for Python 3.9.0 +https://bugs.python.org/issue42965 + +Use 'typing.Callable' instead. +""" +# pylint: disable=missing-docstring,unsubscriptable-object +import collections.abc +from collections.abc import Callable +from typing import Optional, Union + +Alias1 = Optional[Callable[[int], None]] # [broken-collections-callable] +Alias2 = Union[Callable[[int], None], None] # [broken-collections-callable] + +Alias3 = Optional[Callable[..., None]] +Alias4 = Union[Callable[..., None], None] +Alias5 = list[Callable[..., None]] +Alias6 = Callable[[int], None] + + +def func1() -> Optional[Callable[[int], None]]: # [broken-collections-callable] + ... + +def func2() -> Optional["Callable[[int], None]"]: + ... + +def func3() -> Union[collections.abc.Callable[[int], None], None]: # [broken-collections-callable] + ... diff --git a/tests/functional/ext/typing/typing_broken_callable.rc b/tests/functional/ext/typing/typing_broken_callable.rc new file mode 100644 index 0000000000..301a67b7c8 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable.rc @@ -0,0 +1,6 @@ +[master] +py-version=3.9 +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7 diff --git a/tests/functional/ext/typing/typing_broken_callable.txt b/tests/functional/ext/typing/typing_broken_callable.txt new file mode 100644 index 0000000000..360cd896bc --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable.txt @@ -0,0 +1,4 @@ +broken-collections-callable:12:18:12:26::'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE +broken-collections-callable:13:15:13:23::'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE +broken-collections-callable:21:24:21:32:func1:'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE +broken-collections-callable:27:21:27:45:func3:'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE diff --git a/tests/functional/ext/typing/typing_broken_callable_deprecated_alias.py b/tests/functional/ext/typing/typing_broken_callable_deprecated_alias.py new file mode 100644 index 0000000000..f01592a59a --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable_deprecated_alias.py @@ -0,0 +1,26 @@ +""" +'collections.abc.Callable' is broken inside Optional and Union types for Python 3.9.0 +https://bugs.python.org/issue42965 + +Use 'typing.Callable' instead. + +Don't emit 'deprecated-typing-alias' for 'Callable' if at least one replacement +would create broken instances. +""" +# pylint: disable=missing-docstring,unsubscriptable-object +from typing import Callable, Optional, Union + +Alias1 = Optional[Callable[[int], None]] +Alias2 = Union[Callable[[int], None], None] + +Alias3 = Optional[Callable[..., None]] +Alias4 = Union[Callable[..., None], None] +Alias5 = list[Callable[[int], None]] +Alias6 = Callable[[int], None] + + +def func1() -> Optional[Callable[[int], None]]: + ... + +def func2() -> Optional["Callable[[int], None]"]: + ... diff --git a/tests/functional/ext/typing/typing_broken_callable_deprecated_alias.rc b/tests/functional/ext/typing/typing_broken_callable_deprecated_alias.rc new file mode 100644 index 0000000000..301a67b7c8 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable_deprecated_alias.rc @@ -0,0 +1,6 @@ +[master] +py-version=3.9 +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7 diff --git a/tests/functional/ext/typing/typing_broken_callable_future_import.py b/tests/functional/ext/typing/typing_broken_callable_future_import.py new file mode 100644 index 0000000000..947e060b98 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable_future_import.py @@ -0,0 +1,30 @@ +""" +'collections.abc.Callable' is broken inside Optional and Union types for Python 3.9.0 +https://bugs.python.org/issue42965 + +Use 'typing.Callable' instead. +""" +# pylint: disable=missing-docstring,unsubscriptable-object +from __future__ import annotations + +import collections.abc +from collections.abc import Callable +from typing import Optional, Union + +Alias1 = Optional[Callable[[int], None]] # [broken-collections-callable] +Alias2 = Union[Callable[[int], None], None] # [broken-collections-callable] + +Alias3 = Optional[Callable[..., None]] +Alias4 = Union[Callable[..., None], None] +Alias5 = list[Callable[[int], None]] +Alias6 = Callable[[int], None] + + +def func1() -> Optional[Callable[[int], None]]: + ... + +def func2() -> Optional["Callable[[int], None]"]: + ... + +def func3() -> Union[collections.abc.Callable[[int], None], None]: + ... diff --git a/tests/functional/ext/typing/typing_broken_callable_future_import.rc b/tests/functional/ext/typing/typing_broken_callable_future_import.rc new file mode 100644 index 0000000000..301a67b7c8 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable_future_import.rc @@ -0,0 +1,6 @@ +[master] +py-version=3.9 +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7 diff --git a/tests/functional/ext/typing/typing_broken_callable_future_import.txt b/tests/functional/ext/typing/typing_broken_callable_future_import.txt new file mode 100644 index 0000000000..a3f3553f6a --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_callable_future_import.txt @@ -0,0 +1,2 @@ +broken-collections-callable:14:18:14:26::'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE +broken-collections-callable:15:15:15:23::'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE From 8bc9eab5ce3386ce843043ae8377c1eee3469d9b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 11 Mar 2022 08:27:59 +0100 Subject: [PATCH 246/357] Add confidence value to typing extension messages (#5892) --- pylint/extensions/typing.py | 3 + .../typing/typing_consider_using_alias.txt | 40 ++++++------- ...ng_consider_using_alias_without_future.txt | 40 ++++++------- .../typing/typing_consider_using_union.txt | 20 +++---- .../typing_consider_using_union_py310.txt | 36 ++++++------ ...ng_consider_using_union_without_future.txt | 20 +++---- .../ext/typing/typing_deprecated_alias.txt | 56 +++++++++---------- 7 files changed, 109 insertions(+), 106 deletions(-) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 9da7b109f6..b2e6d769d0 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -247,6 +247,7 @@ def _check_for_alternative_union_syntax( "consider-alternative-union-syntax", node=node, args=(name, self._msg_postponed_eval_hint(node)), + confidence=INFERENCE, ) def _check_for_typing_alias( @@ -318,6 +319,7 @@ def leave_module(self, node: nodes.Module) -> None: "deprecated-typing-alias", node=msg.node, args=(msg.qname, msg.alias), + confidence=INFERENCE, ) elif self._py37_plus: @@ -333,6 +335,7 @@ def leave_module(self, node: nodes.Module) -> None: msg.alias, msg_future_import if msg.parent_subscript else "", ), + confidence=INFERENCE, ) # Clear all module cache variables diff --git a/tests/functional/ext/typing/typing_consider_using_alias.txt b/tests/functional/ext/typing/typing_consider_using_alias.txt index f4ddbd213f..0845e2c9cd 100644 --- a/tests/functional/ext/typing/typing_consider_using_alias.txt +++ b/tests/functional/ext/typing/typing_consider_using_alias.txt @@ -1,20 +1,20 @@ -consider-using-alias:16:6:16:17::'typing.Dict' will be deprecated with PY39, consider using 'dict' instead:UNDEFINED -consider-using-alias:17:6:17:10::'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:19:6:19:24::'typing.OrderedDict' will be deprecated with PY39, consider using 'collections.OrderedDict' instead:UNDEFINED -consider-using-alias:20:6:20:22::'typing.Awaitable' will be deprecated with PY39, consider using 'collections.abc.Awaitable' instead:UNDEFINED -consider-using-alias:21:6:21:21::'typing.Iterable' will be deprecated with PY39, consider using 'collections.abc.Iterable' instead:UNDEFINED -consider-using-alias:22:6:22:21::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead:UNDEFINED -consider-using-alias:23:6:23:27::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead:UNDEFINED -consider-using-alias:24:6:24:20::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead:UNDEFINED -consider-using-alias:25:7:25:22::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead:UNDEFINED -consider-using-alias:34:9:34:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:36:7:36:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead:UNDEFINED -consider-using-alias:37:7:37:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:UNDEFINED -consider-using-alias:38:7:38:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead:UNDEFINED -consider-using-alias:44:74:44:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead:UNDEFINED -consider-using-alias:44:16:44:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:44:37:44:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:44:93:44:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:UNDEFINED -consider-using-alias:60:12:60:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:65:12:65:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:69:12:69:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED +consider-using-alias:16:6:16:17::'typing.Dict' will be deprecated with PY39, consider using 'dict' instead:INFERENCE +consider-using-alias:17:6:17:10::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:19:6:19:24::'typing.OrderedDict' will be deprecated with PY39, consider using 'collections.OrderedDict' instead:INFERENCE +consider-using-alias:20:6:20:22::'typing.Awaitable' will be deprecated with PY39, consider using 'collections.abc.Awaitable' instead:INFERENCE +consider-using-alias:21:6:21:21::'typing.Iterable' will be deprecated with PY39, consider using 'collections.abc.Iterable' instead:INFERENCE +consider-using-alias:22:6:22:21::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead:INFERENCE +consider-using-alias:23:6:23:27::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead:INFERENCE +consider-using-alias:24:6:24:20::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead:INFERENCE +consider-using-alias:25:7:25:22::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead:INFERENCE +consider-using-alias:34:9:34:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:36:7:36:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead:INFERENCE +consider-using-alias:37:7:37:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:INFERENCE +consider-using-alias:38:7:38:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead:INFERENCE +consider-using-alias:44:74:44:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead:INFERENCE +consider-using-alias:44:16:44:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:44:37:44:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:44:93:44:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:INFERENCE +consider-using-alias:60:12:60:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:65:12:65:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:69:12:69:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE diff --git a/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt b/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt index e4552cae35..d7ed5fc845 100644 --- a/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt +++ b/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt @@ -1,20 +1,20 @@ -consider-using-alias:13:6:13:17::'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:14:6:14:10::'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:16:6:16:24::'typing.OrderedDict' will be deprecated with PY39, consider using 'collections.OrderedDict' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:17:6:17:22::'typing.Awaitable' will be deprecated with PY39, consider using 'collections.abc.Awaitable' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:18:6:18:21::'typing.Iterable' will be deprecated with PY39, consider using 'collections.abc.Iterable' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:19:6:19:21::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead:UNDEFINED -consider-using-alias:20:6:20:27::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:21:6:21:20::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:22:7:22:22::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:31:9:31:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:UNDEFINED -consider-using-alias:33:7:33:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:34:7:34:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:35:7:35:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:41:74:41:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:41:16:41:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:41:37:41:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:41:93:41:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:57:12:57:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:62:12:62:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:UNDEFINED -consider-using-alias:66:12:66:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:UNDEFINED +consider-using-alias:13:6:13:17::'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:14:6:14:10::'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:16:6:16:24::'typing.OrderedDict' will be deprecated with PY39, consider using 'collections.OrderedDict' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:17:6:17:22::'typing.Awaitable' will be deprecated with PY39, consider using 'collections.abc.Awaitable' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:18:6:18:21::'typing.Iterable' will be deprecated with PY39, consider using 'collections.abc.Iterable' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:19:6:19:21::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead:INFERENCE +consider-using-alias:20:6:20:27::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:21:6:21:20::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:22:7:22:22::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:31:9:31:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:33:7:33:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:34:7:34:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:35:7:35:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:41:74:41:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:41:16:41:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:41:37:41:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:41:93:41:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:57:12:57:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:62:12:62:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:66:12:66:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE diff --git a/tests/functional/ext/typing/typing_consider_using_union.txt b/tests/functional/ext/typing/typing_consider_using_union.txt index 58b8e74ce5..4a1e7521d2 100644 --- a/tests/functional/ext/typing/typing_consider_using_union.txt +++ b/tests/functional/ext/typing/typing_consider_using_union.txt @@ -1,10 +1,10 @@ -consider-alternative-union-syntax:13:6:13:11::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:14:11:14:16::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:15:16:15:28::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:16:6:16:14::Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:24:10:24:18:func1:Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:25:24:25:29:func1:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:26:5:26:10:func1:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:38:12:38:17:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:43:27:43:32:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:47:12:47:20:CustomDataClass:Consider using alternative Union syntax instead of 'Optional':UNDEFINED +consider-alternative-union-syntax:13:6:13:11::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:14:11:14:16::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:15:16:15:28::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:16:6:16:14::Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:24:10:24:18:func1:Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:25:24:25:29:func1:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:26:5:26:10:func1:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:38:12:38:17:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:43:27:43:32:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:47:12:47:20:CustomDataClass:Consider using alternative Union syntax instead of 'Optional':INFERENCE diff --git a/tests/functional/ext/typing/typing_consider_using_union_py310.txt b/tests/functional/ext/typing/typing_consider_using_union_py310.txt index dec5556e7d..fde1d7e2b2 100644 --- a/tests/functional/ext/typing/typing_consider_using_union_py310.txt +++ b/tests/functional/ext/typing/typing_consider_using_union_py310.txt @@ -1,18 +1,18 @@ -consider-alternative-union-syntax:11:6:11:11::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:12:11:12:16::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:13:16:13:28::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:14:6:14:14::Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:16:9:16:14::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:17:14:17:19::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:18:19:18:31::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:19:9:19:17::Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:22:10:22:18:func1:Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:23:24:23:29:func1:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:24:5:24:10:func1:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:27:19:27:24:Custom1:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:31:28:31:33::Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:33:14:33:22::Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:36:12:36:17:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:38:56:38:64::Consider using alternative Union syntax instead of 'Optional':UNDEFINED -consider-alternative-union-syntax:41:27:41:32:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union':UNDEFINED -consider-alternative-union-syntax:45:12:45:20:CustomDataClass:Consider using alternative Union syntax instead of 'Optional':UNDEFINED +consider-alternative-union-syntax:11:6:11:11::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:12:11:12:16::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:13:16:13:28::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:14:6:14:14::Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:16:9:16:14::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:17:14:17:19::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:18:19:18:31::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:19:9:19:17::Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:22:10:22:18:func1:Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:23:24:23:29:func1:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:24:5:24:10:func1:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:27:19:27:24:Custom1:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:31:28:31:33::Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:33:14:33:22::Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:36:12:36:17:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:38:56:38:64::Consider using alternative Union syntax instead of 'Optional':INFERENCE +consider-alternative-union-syntax:41:27:41:32:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union':INFERENCE +consider-alternative-union-syntax:45:12:45:20:CustomDataClass:Consider using alternative Union syntax instead of 'Optional':INFERENCE diff --git a/tests/functional/ext/typing/typing_consider_using_union_without_future.txt b/tests/functional/ext/typing/typing_consider_using_union_without_future.txt index 4eccda9518..bc8ca4c075 100644 --- a/tests/functional/ext/typing/typing_consider_using_union_without_future.txt +++ b/tests/functional/ext/typing/typing_consider_using_union_without_future.txt @@ -1,10 +1,10 @@ -consider-alternative-union-syntax:11:6:11:11::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:12:11:12:16::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:13:16:13:28::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:14:6:14:14::Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:22:10:22:18:func1:Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:23:24:23:29:func1:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:24:5:24:10:func1:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:36:12:36:17:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:41:27:41:32:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:UNDEFINED -consider-alternative-union-syntax:45:12:45:20:CustomDataClass:Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well:UNDEFINED +consider-alternative-union-syntax:11:6:11:11::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:12:11:12:16::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:13:16:13:28::Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:14:6:14:14::Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:22:10:22:18:func1:Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:23:24:23:29:func1:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:24:5:24:10:func1:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:36:12:36:17:CustomNamedTuple:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:41:27:41:32:CustomTypedDict2:Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well:INFERENCE +consider-alternative-union-syntax:45:12:45:20:CustomDataClass:Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well:INFERENCE diff --git a/tests/functional/ext/typing/typing_deprecated_alias.txt b/tests/functional/ext/typing/typing_deprecated_alias.txt index 585f71c235..62cf9902ad 100644 --- a/tests/functional/ext/typing/typing_deprecated_alias.txt +++ b/tests/functional/ext/typing/typing_deprecated_alias.txt @@ -1,28 +1,28 @@ -deprecated-typing-alias:13:6:13:17::'typing.Dict' is deprecated, use 'dict' instead:UNDEFINED -deprecated-typing-alias:14:6:14:10::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:16:6:16:24::'typing.OrderedDict' is deprecated, use 'collections.OrderedDict' instead:UNDEFINED -deprecated-typing-alias:17:6:17:22::'typing.Awaitable' is deprecated, use 'collections.abc.Awaitable' instead:UNDEFINED -deprecated-typing-alias:18:6:18:21::'typing.Iterable' is deprecated, use 'collections.abc.Iterable' instead:UNDEFINED -deprecated-typing-alias:19:6:19:21::'typing.Hashable' is deprecated, use 'collections.abc.Hashable' instead:UNDEFINED -deprecated-typing-alias:20:6:20:27::'typing.ContextManager' is deprecated, use 'contextlib.AbstractContextManager' instead:UNDEFINED -deprecated-typing-alias:21:6:21:20::'typing.Pattern' is deprecated, use 're.Pattern' instead:UNDEFINED -deprecated-typing-alias:22:7:22:22::'typing.Match' is deprecated, use 're.Match' instead:UNDEFINED -deprecated-typing-alias:28:9:28:12::'typing.Set' is deprecated, use 'set' instead:UNDEFINED -deprecated-typing-alias:29:9:29:13::'typing.Dict' is deprecated, use 'dict' instead:UNDEFINED -deprecated-typing-alias:29:19:29:23::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:30:20:30:31::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:31:9:31:13::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:33:7:33:11::'typing.Type' is deprecated, use 'type' instead:UNDEFINED -deprecated-typing-alias:34:7:34:12::'typing.Tuple' is deprecated, use 'tuple' instead:UNDEFINED -deprecated-typing-alias:35:7:35:15::'typing.Callable' is deprecated, use 'collections.abc.Callable' instead:UNDEFINED -deprecated-typing-alias:41:74:41:78:func1:'typing.Dict' is deprecated, use 'dict' instead:UNDEFINED -deprecated-typing-alias:41:16:41:20:func1:'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:41:37:41:41:func1:'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:41:93:41:105:func1:'typing.Tuple' is deprecated, use 'tuple' instead:UNDEFINED -deprecated-typing-alias:48:20:48:31:CustomIntList:'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:52:28:52:32::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:54:14:54:18::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:57:12:57:16:CustomNamedTuple:'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:59:56:59:60::'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:62:12:62:16:CustomTypedDict2:'typing.List' is deprecated, use 'list' instead:UNDEFINED -deprecated-typing-alias:66:12:66:16:CustomDataClass:'typing.List' is deprecated, use 'list' instead:UNDEFINED +deprecated-typing-alias:13:6:13:17::'typing.Dict' is deprecated, use 'dict' instead:INFERENCE +deprecated-typing-alias:14:6:14:10::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:16:6:16:24::'typing.OrderedDict' is deprecated, use 'collections.OrderedDict' instead:INFERENCE +deprecated-typing-alias:17:6:17:22::'typing.Awaitable' is deprecated, use 'collections.abc.Awaitable' instead:INFERENCE +deprecated-typing-alias:18:6:18:21::'typing.Iterable' is deprecated, use 'collections.abc.Iterable' instead:INFERENCE +deprecated-typing-alias:19:6:19:21::'typing.Hashable' is deprecated, use 'collections.abc.Hashable' instead:INFERENCE +deprecated-typing-alias:20:6:20:27::'typing.ContextManager' is deprecated, use 'contextlib.AbstractContextManager' instead:INFERENCE +deprecated-typing-alias:21:6:21:20::'typing.Pattern' is deprecated, use 're.Pattern' instead:INFERENCE +deprecated-typing-alias:22:7:22:22::'typing.Match' is deprecated, use 're.Match' instead:INFERENCE +deprecated-typing-alias:28:9:28:12::'typing.Set' is deprecated, use 'set' instead:INFERENCE +deprecated-typing-alias:29:9:29:13::'typing.Dict' is deprecated, use 'dict' instead:INFERENCE +deprecated-typing-alias:29:19:29:23::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:30:20:30:31::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:31:9:31:13::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:33:7:33:11::'typing.Type' is deprecated, use 'type' instead:INFERENCE +deprecated-typing-alias:34:7:34:12::'typing.Tuple' is deprecated, use 'tuple' instead:INFERENCE +deprecated-typing-alias:35:7:35:15::'typing.Callable' is deprecated, use 'collections.abc.Callable' instead:INFERENCE +deprecated-typing-alias:41:74:41:78:func1:'typing.Dict' is deprecated, use 'dict' instead:INFERENCE +deprecated-typing-alias:41:16:41:20:func1:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:41:37:41:41:func1:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:41:93:41:105:func1:'typing.Tuple' is deprecated, use 'tuple' instead:INFERENCE +deprecated-typing-alias:48:20:48:31:CustomIntList:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:52:28:52:32::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:54:14:54:18::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:57:12:57:16:CustomNamedTuple:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:59:56:59:60::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:62:12:62:16:CustomTypedDict2:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:66:12:66:16:CustomDataClass:'typing.List' is deprecated, use 'list' instead:INFERENCE From 588ed30f267a77d16233256e5f832d32533b795d Mon Sep 17 00:00:00 2001 From: Konrad Weihmann <46938494+priv-kweihmann@users.noreply.github.com> Date: Fri, 11 Mar 2022 12:54:22 +0100 Subject: [PATCH 247/357] Allow mccabe 0.7.x (#5896) python version < 3.7 will still remain on mccabe 0.6.x, while newer version will pick mccabe 0.7.x release Closes #5878 Signed-off-by: Konrad Weihmann --- CONTRIBUTORS.txt | 2 ++ ChangeLog | 4 ++++ setup.cfg | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 491a49dc1a..eeaaa13b1e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -605,3 +605,5 @@ contributors: * Daniel Brookman: contributor * Téo Bouvard: contributor + +* Konrad Weihmann: contributor diff --git a/ChangeLog b/ChangeLog index ff34a28ce3..111a06ab44 100644 --- a/ChangeLog +++ b/ChangeLog @@ -492,6 +492,10 @@ Release date: 2021-11-25 Closes #5316 +* Allow usage of mccabe 0.7.x release + + Closes #5878 + What's New in Pylint 2.12.1? ============================ diff --git a/setup.cfg b/setup.cfg index 8c148180fb..1c235c535f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -50,7 +50,7 @@ install_requires = # see https://github.com/PyCQA/astroid/issues/1341 astroid>=2.9.2,<=2.10.0-dev0 isort>=4.2.5,<6 - mccabe>=0.6,<0.7 + mccabe>=0.6,<0.8 tomli>=1.1.0;python_version<"3.11" colorama;sys_platform=="win32" typing-extensions>=3.10.0;python_version<"3.10" From a6e02fb6f0dd2c84dc1bf58df0f2511fe9ab90c2 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 11 Mar 2022 07:15:35 -0500 Subject: [PATCH 248/357] Prevent `useless-suppression` on disables for stdlib deprecation checker (#5876) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/checkers/stdlib.py | 20 ++++++++++-------- pylint/constants.py | 17 +++++++++++++++ pylint/utils/file_state.py | 21 ++++++++++++------- .../deprecated_method_suppression.py | 8 +++++++ .../d/deprecated/deprecated_methods_py39.py | 4 ++++ .../d/deprecated/deprecated_methods_py39.rc | 2 ++ .../d/deprecated/deprecated_methods_py39.txt | 1 + .../d/deprecated/deprecated_module_py3.txt | 2 +- .../d/deprecated/deprecated_module_py39.py | 4 ++++ .../d/deprecated/deprecated_module_py39.rc | 2 ++ .../d/deprecated/deprecated_module_py39.txt | 1 + ...eprecated_module_py39_earlier_pyversion.py | 6 ++++++ ...eprecated_module_py39_earlier_pyversion.rc | 5 +++++ ...precated_module_py39_earlier_pyversion.txt | 1 + 16 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 tests/functional/d/deprecated/deprecated_method_suppression.py create mode 100644 tests/functional/d/deprecated/deprecated_methods_py39.py create mode 100644 tests/functional/d/deprecated/deprecated_methods_py39.rc create mode 100644 tests/functional/d/deprecated/deprecated_methods_py39.txt create mode 100644 tests/functional/d/deprecated/deprecated_module_py39.py create mode 100644 tests/functional/d/deprecated/deprecated_module_py39.rc create mode 100644 tests/functional/d/deprecated/deprecated_module_py39.txt create mode 100644 tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.py create mode 100644 tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc create mode 100644 tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.txt diff --git a/ChangeLog b/ChangeLog index 111a06ab44..6e3a338e12 100644 --- a/ChangeLog +++ b/ChangeLog @@ -421,6 +421,10 @@ Release date: TBA Closes #5862 +* Disables for ``deprecated-module`` and similar warnings for stdlib features deprecated + in newer versions of Python no longer raise ``useless-suppression`` when linting with + older Python interpreters where those features are not yet deprecated. + * Importing the deprecated stdlib module ``distutils`` now emits ``deprecated_module`` on Python 3.10+. * ``missing-raises-doc`` will now check the class hierarchy of the raised exceptions diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a71a18b4b9..1aa94d4aff 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -402,6 +402,10 @@ Other Changes * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* Disables for ``deprecated-module`` and similar warnings for stdlib features deprecated + in newer versions of Python no longer raise ``useless-suppression`` when linting with + older Python interpreters where those features are not yet deprecated. + * Importing the deprecated stdlib module ``xml.etree.cElementTree`` now emits ``deprecated_module``. Closes #5862 diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 8270eb88cc..3884fc32e4 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -39,7 +39,7 @@ import sys from collections.abc import Iterable -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import astroid from astroid import nodes @@ -473,24 +473,26 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): def __init__(self, linter: Optional["PyLinter"] = None) -> None: BaseChecker.__init__(self, linter) - self._deprecated_methods: Set[Any] = set() - self._deprecated_methods.update(DEPRECATED_METHODS[0]) + self._deprecated_methods: Set[str] = set() + self._deprecated_arguments: Dict[ + str, Tuple[Tuple[Optional[int], str], ...] + ] = {} + self._deprecated_classes: Dict[str, Set[str]] = {} + self._deprecated_modules: Set[str] = set() + self._deprecated_decorators: Set[str] = set() + for since_vers, func_list in DEPRECATED_METHODS[sys.version_info[0]].items(): if since_vers <= sys.version_info: self._deprecated_methods.update(func_list) - self._deprecated_attributes = {} for since_vers, func_list in DEPRECATED_ARGUMENTS.items(): if since_vers <= sys.version_info: - self._deprecated_attributes.update(func_list) - self._deprecated_classes = {} + self._deprecated_arguments.update(func_list) for since_vers, class_list in DEPRECATED_CLASSES.items(): if since_vers <= sys.version_info: self._deprecated_classes.update(class_list) - self._deprecated_modules = set() for since_vers, mod_list in DEPRECATED_MODULES.items(): if since_vers <= sys.version_info: self._deprecated_modules.update(mod_list) - self._deprecated_decorators = set() for since_vers, decorator_list in DEPRECATED_DECORATORS.items(): if since_vers <= sys.version_info: self._deprecated_decorators.update(decorator_list) @@ -788,7 +790,7 @@ def deprecated_methods(self): return self._deprecated_methods def deprecated_arguments(self, method: str): - return self._deprecated_attributes.get(method, ()) + return self._deprecated_arguments.get(method, ()) def deprecated_classes(self, module: str): return self._deprecated_classes.get(module, ()) diff --git a/pylint/constants.py b/pylint/constants.py index 1a06ea8329..ae87d4293d 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -173,3 +173,20 @@ class DeletedMessage(NamedTuple): # https://github.com/PyCQA/pylint/pull/3571 DeletedMessage("C0330", "bad-continuation"), ] + + +# ignore some messages when emitting useless-suppression: +# - cyclic-import: can show false positives due to incomplete context +# - deprecated-{module, argument, class, method, decorator}: +# can cause false positives for multi-interpreter projects +# when linting with an interpreter on a lower python version +INCOMPATIBLE_WITH_USELESS_SUPPRESSION = frozenset( + [ + "R0401", # cyclic-import + "W0402", # deprecated-module + "W1505", # deprecated-method + "W1511", # deprecated-argument + "W1512", # deprecated-class + "W1513", # deprecated-decorator + ] +) diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index 654cb725b9..30e91c3ea1 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -16,7 +16,11 @@ from astroid import nodes -from pylint.constants import MSG_STATE_SCOPE_MODULE, WarningScope +from pylint.constants import ( + INCOMPATIBLE_WITH_USELESS_SUPPRESSION, + MSG_STATE_SCOPE_MODULE, + WarningScope, +) if sys.version_info >= (3, 8): from typing import Literal @@ -159,13 +163,14 @@ def iter_spurious_suppression_messages( ]: for warning, lines in self._raw_module_msgs_state.items(): for line, enable in lines.items(): - if not enable and (warning, line) not in self._ignored_msgs: - # ignore cyclic-import check which can show false positives - # here due to incomplete context - if warning != "R0401": - yield "useless-suppression", line, ( - msgs_store.get_msg_display_string(warning), - ) + if ( + not enable + and (warning, line) not in self._ignored_msgs + and warning not in INCOMPATIBLE_WITH_USELESS_SUPPRESSION + ): + yield "useless-suppression", line, ( + msgs_store.get_msg_display_string(warning), + ) # don't use iteritems here, _ignored_msgs may be modified by add_message for (warning, from_), ignored_lines in list(self._ignored_msgs.items()): for line in ignored_lines: diff --git a/tests/functional/d/deprecated/deprecated_method_suppression.py b/tests/functional/d/deprecated/deprecated_method_suppression.py new file mode 100644 index 0000000000..4f3bb63fe0 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_method_suppression.py @@ -0,0 +1,8 @@ +"""Test that versions below Py3.10 will not emit useless-suppression for +disabling deprecated-method (on a method deprecated in Py3.10. + +This test can be run on all Python versions, but it will lack value when +Pylint drops support for 3.9.""" +# pylint: disable=import-error, unused-import + +import threading.current_thread # pylint: disable=deprecated-method diff --git a/tests/functional/d/deprecated/deprecated_methods_py39.py b/tests/functional/d/deprecated/deprecated_methods_py39.py new file mode 100644 index 0000000000..9959b1c2a2 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_methods_py39.py @@ -0,0 +1,4 @@ +"""Test deprecated methods from Python 3.9.""" + +import binascii +binascii.b2a_hqx() # [deprecated-method] diff --git a/tests/functional/d/deprecated/deprecated_methods_py39.rc b/tests/functional/d/deprecated/deprecated_methods_py39.rc new file mode 100644 index 0000000000..16b75eea75 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_methods_py39.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/d/deprecated/deprecated_methods_py39.txt b/tests/functional/d/deprecated/deprecated_methods_py39.txt new file mode 100644 index 0000000000..292ca5ef16 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_methods_py39.txt @@ -0,0 +1 @@ +deprecated-method:4:0:4:18::Using deprecated method b2a_hqx():UNDEFINED diff --git a/tests/functional/d/deprecated/deprecated_module_py3.txt b/tests/functional/d/deprecated/deprecated_module_py3.txt index 79c0a418c2..bf41f781e9 100644 --- a/tests/functional/d/deprecated/deprecated_module_py3.txt +++ b/tests/functional/d/deprecated/deprecated_module_py3.txt @@ -1 +1 @@ -deprecated-module:4:::Uses of a deprecated module 'optparse' +deprecated-module:4:0:4:15::Uses of a deprecated module 'optparse':UNDEFINED diff --git a/tests/functional/d/deprecated/deprecated_module_py39.py b/tests/functional/d/deprecated/deprecated_module_py39.py new file mode 100644 index 0000000000..a897dd4e33 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py39.py @@ -0,0 +1,4 @@ +"""Test deprecated modules from Python 3.9.""" +# pylint: disable=unused-import + +import binhex # [deprecated-module] diff --git a/tests/functional/d/deprecated/deprecated_module_py39.rc b/tests/functional/d/deprecated/deprecated_module_py39.rc new file mode 100644 index 0000000000..16b75eea75 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py39.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/d/deprecated/deprecated_module_py39.txt b/tests/functional/d/deprecated/deprecated_module_py39.txt new file mode 100644 index 0000000000..c85cbcbffd --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py39.txt @@ -0,0 +1 @@ +deprecated-module:4:0:4:13::Uses of a deprecated module 'binhex':UNDEFINED diff --git a/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.py b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.py new file mode 100644 index 0000000000..c1c2f17883 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.py @@ -0,0 +1,6 @@ +"""Test deprecated modules from Python 3.9, +but use an earlier --py-version and ensure a warning is still emitted. +""" +# pylint: disable=unused-import + +import binhex # [deprecated-module] diff --git a/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc new file mode 100644 index 0000000000..8118783a19 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc @@ -0,0 +1,5 @@ +[master] +py-version=3.8 + +[testoptions] +min_pyver=3.9 diff --git a/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.txt b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.txt new file mode 100644 index 0000000000..558ed224a4 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.txt @@ -0,0 +1 @@ +deprecated-module:6:0:6:13::Uses of a deprecated module 'binhex':UNDEFINED From d01e5e167834097ff7e5bf26a069aa1339669183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 11 Mar 2022 15:37:59 +0100 Subject: [PATCH 249/357] Add ``typevar-name-missing-variance`` checker (#5825) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: Yudaka --- CONTRIBUTORS.txt | 2 + ChangeLog | 4 + doc/whatsnew/2.13.rst | 4 + pylint/checkers/base.py | 121 +++++++++++++++++- .../generic_alias_side_effects.py | 2 +- .../regression_2443_duplicate_bases.py | 2 +- .../t/typevar_name_incorrect_variance.py | 69 ++++++++++ .../t/typevar_name_incorrect_variance.txt | 21 +++ 8 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 tests/functional/t/typevar_name_incorrect_variance.py create mode 100644 tests/functional/t/typevar_name_incorrect_variance.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index eeaaa13b1e..ec76f57d7b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -598,6 +598,8 @@ contributors: * Kian-Meng, Ang: contributor +* Nuzula H. Yudaka (Nuzhuka): contributor + * Carli Freudenberg (CarliJoy): contributor - Fixed issue 5281, added Unicode checker - Improve non-ascii-name checker diff --git a/ChangeLog b/ChangeLog index 6e3a338e12..33dacc56bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -450,6 +450,10 @@ Release date: TBA Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) +* Added new checker ``typevar-name-missing-variance``. Emitted when a covariant + or contravariant ``TypeVar`` does not end with ``_co`` or ``_contra`` respectively or + when a ``TypeVar`` is not either but has a suffix. + What's New in Pylint 2.12.2? ============================ diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 1aa94d4aff..96dfa518a9 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -37,6 +37,10 @@ New checkers Closes #5460 +* Added new checker ``typevar-name-missing-variance``. Emitted when a covariant + or contravariant ``TypeVar`` does not end with ``_co`` or ``_contra`` respectively or + when a ``TypeVar`` is not either but has a suffix. + * Add ``modified-iterating-list``, ``modified-iterating-dict``, and ``modified-iterating-set``, emitted when items are added to or removed from respectively a list, dictionary or set being iterated through. diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index e2a3ece496..71dabd8efc 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -228,6 +228,7 @@ class AnyStyle(NamingStyle): # List of methods which can be redefined REDEFINABLE_METHODS = frozenset(("__module__",)) TYPING_FORWARD_REF_QNAME = "typing.ForwardRef" +TYPING_TYPE_VAR_QNAME = "typing.TypeVar" def _redefines_import(node): @@ -1738,6 +1739,16 @@ class NameChecker(_BasicChecker): ] }, ), + "C0105": ( + 'Type variable "%s" is %s, use "%s" instead', + "typevar-name-incorrect-variance", + "Emitted when a TypeVar name doesn't reflect its type variance. " + "According to PEP8, it is recommended to add suffixes '_co' and " + "'_contra' to the variables used to declare covariant or " + "contravariant behaviour respectively. Invariant (default) variables " + "do not require a suffix. The message is also emitted when invariant " + "variables do have a suffix.", + ), "W0111": ( "Name %s will become a keyword in Python %s", "assign-to-new-keyword", @@ -1940,33 +1951,69 @@ def visit_global(self, node: nodes.Global) -> None: for name in node.names: self._check_name("const", name, node) - @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword") + @utils.check_messages( + "disallowed-name", + "invalid-name", + "assign-to-new-keyword", + "typevar-name-incorrect-variance", + ) def visit_assignname(self, node: nodes.AssignName) -> None: """Check module level assigned names.""" self._check_assign_to_new_keyword_violation(node.name, node) frame = node.frame(future=True) assign_type = node.assign_type() + + # Check names defined in comprehensions if isinstance(assign_type, nodes.Comprehension): self._check_name("inlinevar", node.name, node) + + # Check names defined in module scope elif isinstance(frame, nodes.Module): + # Check names defined in Assign nodes if isinstance(assign_type, nodes.Assign): - if isinstance(utils.safe_infer(assign_type.value), nodes.ClassDef): + inferred_assign_type = utils.safe_infer(assign_type.value) + + # Check TypeVar's assigned alone or in tuple assignment + if isinstance(node.parent, nodes.Assign) and self._assigns_typevar( + assign_type.value + ): + self._check_name("typevar", assign_type.targets[0].name, node) + elif ( + isinstance(node.parent, nodes.Tuple) + and isinstance(assign_type.value, nodes.Tuple) + and self._assigns_typevar( + assign_type.value.elts[node.parent.elts.index(node)] + ) + ): + self._check_name( + "typevar", + assign_type.targets[0].elts[node.parent.elts.index(node)].name, + node, + ) + + # Check classes (TypeVar's are classes so they need to be excluded first) + elif isinstance(inferred_assign_type, nodes.ClassDef): self._check_name("class", node.name, node) - # Don't emit if the name redefines an import - # in an ImportError except handler. + + # Don't emit if the name redefines an import in an ImportError except handler. elif not _redefines_import(node) and isinstance( - utils.safe_infer(assign_type.value), nodes.Const + inferred_assign_type, nodes.Const ): self._check_name("const", node.name, node) + # Check names defined in AnnAssign nodes elif isinstance( assign_type, nodes.AnnAssign ) and utils.is_assign_name_annotated_with(node, "Final"): self._check_name("const", node.name, node) + + # Check names defined in function scopes elif isinstance(frame, nodes.FunctionDef): # global introduced variable aren't in the function locals if node.name in frame and node.name not in frame.argnames(): if not _redefines_import(node): self._check_name("variable", node.name, node) + + # Check names defined in class scopes elif isinstance(frame, nodes.ClassDef): if not list(frame.local_attr_ancestors(node.name)): for ancestor in frame.ancestors(): @@ -2031,6 +2078,14 @@ def _name_disallowed_by_regex(self, name: str) -> bool: def _check_name(self, node_type, name, node, confidence=interfaces.HIGH): """Check for a name using the type's regexp.""" + # pylint: disable=fixme + # TODO: move this down in the function and check TypeVar + # for name patterns as well. + # Check TypeVar names for variance suffixes + if node_type == "typevar": + self._check_typevar_variance(name, node) + return + def _should_exempt_from_invalid_name(node): if node_type == "variable": inferred = utils.safe_infer(node) @@ -2075,6 +2130,62 @@ def _name_became_keyword_in_version(name, rules): return ".".join(str(v) for v in version) return None + @staticmethod + def _assigns_typevar(node: Optional[nodes.NodeNG]) -> bool: + """Check if a node is assigning a TypeVar.""" + if isinstance(node, astroid.Call): + inferred = utils.safe_infer(node.func) + if ( + isinstance(inferred, astroid.ClassDef) + and inferred.qname() == TYPING_TYPE_VAR_QNAME + ): + return True + return False + + def _check_typevar_variance(self, name: str, node: nodes.AssignName) -> None: + """Check if a TypeVar has a variance and if it's included in the name. + + Returns the args for the message to be displayed. + """ + if isinstance(node.parent, nodes.Assign): + keywords = node.assign_type().value.keywords + elif isinstance(node.parent, nodes.Tuple): + keywords = ( + node.assign_type().value.elts[node.parent.elts.index(node)].keywords + ) + + for kw in keywords: + if kw.arg == "covariant" and kw.value.value: + if not name.endswith("_co"): + suggest_name = f"{re.sub('_contra$', '', name)}_co" + self.add_message( + "typevar-name-incorrect-variance", + node=node, + args=(name, "covariant", suggest_name), + confidence=interfaces.INFERENCE, + ) + return + + if kw.arg == "contravariant" and kw.value.value: + if not name.endswith("_contra"): + suggest_name = f"{re.sub('_co$', '', name)}_contra" + self.add_message( + "typevar-name-incorrect-variance", + node=node, + args=(name, "contravariant", suggest_name), + confidence=interfaces.INFERENCE, + ) + return + + if name.endswith("_co") or name.endswith("_contra"): + suggest_name = re.sub("_contra$|_co$", "", name) + self.add_message( + "typevar-name-incorrect-variance", + node=node, + args=(name, "invariant", suggest_name), + confidence=interfaces.INFERENCE, + ) + class DocStringChecker(_BasicChecker): msgs = { diff --git a/tests/functional/g/generic_alias/generic_alias_side_effects.py b/tests/functional/g/generic_alias/generic_alias_side_effects.py index ea7d7a9ce9..344db95531 100644 --- a/tests/functional/g/generic_alias/generic_alias_side_effects.py +++ b/tests/functional/g/generic_alias/generic_alias_side_effects.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring,invalid-name,line-too-long,too-few-public-methods,use-list-literal,use-dict-literal +# pylint: disable=missing-docstring,invalid-name,line-too-long,too-few-public-methods,use-list-literal,use-dict-literal, typevar-name-incorrect-variance import typing import collections from typing import Generic, TypeVar diff --git a/tests/functional/r/regression/regression_2443_duplicate_bases.py b/tests/functional/r/regression/regression_2443_duplicate_bases.py index f32490c44a..b5ea1c9f98 100644 --- a/tests/functional/r/regression/regression_2443_duplicate_bases.py +++ b/tests/functional/r/regression/regression_2443_duplicate_bases.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring, too-many-ancestors,too-few-public-methods +# pylint: disable=missing-docstring, too-many-ancestors,too-few-public-methods, typevar-name-incorrect-variance from typing import Generic, TypeVar IN = TypeVar('IN', contravariant=True) diff --git a/tests/functional/t/typevar_name_incorrect_variance.py b/tests/functional/t/typevar_name_incorrect_variance.py new file mode 100644 index 0000000000..bb8a898801 --- /dev/null +++ b/tests/functional/t/typevar_name_incorrect_variance.py @@ -0,0 +1,69 @@ +"""Test case for typevar-name-incorrect-variance.""" + +from typing import TypeVar + +# Type variables without variance +T = TypeVar("T") +T_co = TypeVar("T_co") # [typevar-name-incorrect-variance] +T_contra = TypeVar("T_contra") # [typevar-name-incorrect-variance] +ScoresT_contra = TypeVar("ScoresT_contra") # [typevar-name-incorrect-variance] + +# Type variables not starting with T +N = TypeVar("N") +N_co = TypeVar("N_co", covariant=True) +N_contra = TypeVar("N_contra", contravariant=True) + +# Tests for combinations with contravariance +CT_co = TypeVar("CT_co", contravariant=True) # [typevar-name-incorrect-variance] +CT_contra = TypeVar("CT_contra") # [typevar-name-incorrect-variance] +CT_contra = TypeVar("CT_contra", contravariant=True) + +# Tests for combinations with covariance +VT = TypeVar("VT", covariant=True) # [typevar-name-incorrect-variance] +VT_contra = TypeVar("VT_contra", covariant=True) # [typevar-name-incorrect-variance] +VT_co = TypeVar("VT_co", covariant=True) + +# Tests for combinations with bound +VT = TypeVar("VT", bound=int) +VT_co = TypeVar("VT_co", bound=int) # [typevar-name-incorrect-variance] +VT_contra = TypeVar("VT_contra", bound=int) # [typevar-name-incorrect-variance] + +VT = TypeVar("VT", bound=int, covariant=True) # [typevar-name-incorrect-variance] +VT_co = TypeVar("VT_co", bound=int, covariant=True) +VT_contra = TypeVar( # [typevar-name-incorrect-variance] + "VT_contra", bound=int, covariant=True +) + +VT = TypeVar("VT", bound=int, covariant=False) +VT_co = TypeVar( # [typevar-name-incorrect-variance] + "VT_co", bound=int, covariant=False +) +VT_contra = TypeVar( # [typevar-name-incorrect-variance] + "VT_contra", bound=int, covariant=False +) + +VT = TypeVar("VT", bound=int, contravariant=True) # [typevar-name-incorrect-variance] +VT_co = TypeVar( # [typevar-name-incorrect-variance] + "VT_co", bound=int, contravariant=True +) +VT_contra = TypeVar("VT_contra", bound=int, contravariant=True) + +VT = TypeVar("VT", bound=int, contravariant=False) +VT_co = TypeVar( # [typevar-name-incorrect-variance] + "VT_co", bound=int, contravariant=False +) +VT_contra = TypeVar( # [typevar-name-incorrect-variance] + "VT_contra", bound=int, contravariant=False +) + +# Tests for combinations with tuple assignment +( + VT, # [typevar-name-incorrect-variance] + VT_contra, # [typevar-name-incorrect-variance] +) = TypeVar("VT", covariant=True), TypeVar("VT_contra", covariant=True) +VT_co, VT_contra = TypeVar( # [typevar-name-incorrect-variance] + "VT_co", covariant=True +), TypeVar("VT_contra", covariant=True) +VAR, VT_contra = "a string", TypeVar( # [typevar-name-incorrect-variance] + "VT_contra", covariant=True +) diff --git a/tests/functional/t/typevar_name_incorrect_variance.txt b/tests/functional/t/typevar_name_incorrect_variance.txt new file mode 100644 index 0000000000..d4dcfd2f9a --- /dev/null +++ b/tests/functional/t/typevar_name_incorrect_variance.txt @@ -0,0 +1,21 @@ +typevar-name-incorrect-variance:7:0:7:4::"Type variable ""T_co"" is invariant, use ""T"" instead":INFERENCE +typevar-name-incorrect-variance:8:0:8:8::"Type variable ""T_contra"" is invariant, use ""T"" instead":INFERENCE +typevar-name-incorrect-variance:9:0:9:14::"Type variable ""ScoresT_contra"" is invariant, use ""ScoresT"" instead":INFERENCE +typevar-name-incorrect-variance:17:0:17:5::"Type variable ""CT_co"" is contravariant, use ""CT_contra"" instead":INFERENCE +typevar-name-incorrect-variance:18:0:18:9::"Type variable ""CT_contra"" is invariant, use ""CT"" instead":INFERENCE +typevar-name-incorrect-variance:22:0:22:2::"Type variable ""VT"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:23:0:23:9::"Type variable ""VT_contra"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:28:0:28:5::"Type variable ""VT_co"" is invariant, use ""VT"" instead":INFERENCE +typevar-name-incorrect-variance:29:0:29:9::"Type variable ""VT_contra"" is invariant, use ""VT"" instead":INFERENCE +typevar-name-incorrect-variance:31:0:31:2::"Type variable ""VT"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:33:0:33:9::"Type variable ""VT_contra"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:38:0:38:5::"Type variable ""VT_co"" is invariant, use ""VT"" instead":INFERENCE +typevar-name-incorrect-variance:41:0:41:9::"Type variable ""VT_contra"" is invariant, use ""VT"" instead":INFERENCE +typevar-name-incorrect-variance:45:0:45:2::"Type variable ""VT"" is contravariant, use ""VT_contra"" instead":INFERENCE +typevar-name-incorrect-variance:46:0:46:5::"Type variable ""VT_co"" is contravariant, use ""VT_contra"" instead":INFERENCE +typevar-name-incorrect-variance:52:0:52:5::"Type variable ""VT_co"" is invariant, use ""VT"" instead":INFERENCE +typevar-name-incorrect-variance:55:0:55:9::"Type variable ""VT_contra"" is invariant, use ""VT"" instead":INFERENCE +typevar-name-incorrect-variance:61:4:61:6::"Type variable ""VT"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:62:4:62:13::"Type variable ""VT_contra"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:64:7:64:16::"Type variable ""VT_contra"" is covariant, use ""VT_co"" instead":INFERENCE +typevar-name-incorrect-variance:67:5:67:14::"Type variable ""VT_contra"" is covariant, use ""VT_co"" instead":INFERENCE From 6fd52958b1d6bb293e5cd3e857365d541d9a9fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 10:39:49 +0100 Subject: [PATCH 250/357] Upgrade mypy to 0.940 (#5900) --- .pre-commit-config.yaml | 2 +- pylint/testutils/functional/test_file.py | 2 +- requirements_test_pre_commit.txt | 2 +- setup.cfg | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c440a0ca37..f4ace61f43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: types: [text] # necessary to include ChangeLog file files: ^(ChangeLog|doc/(.*/)*.*\.rst) - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 + rev: v0.940 hooks: - id: mypy name: mypy diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index bf2469e498..1ebc30d0e0 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -84,7 +84,7 @@ def _parse_options(self) -> None: assert ( name in POSSIBLE_TEST_OPTIONS ), f"[testoptions]' can only contains one of {POSSIBLE_TEST_OPTIONS}" - self.options[name] = conv(value) # type: ignore[misc] + self.options[name] = conv(value) # type: ignore[literal-required] @property def option_file(self) -> str: diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 15ab3bd945..31bca7e84c 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,4 @@ black==22.1.0 flake8==4.0.1 flake8-typing-imports==1.12.0 isort==5.10.1 -mypy==0.931 +mypy==0.940 diff --git a/setup.cfg b/setup.cfg index 1c235c535f..71462cb10c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -94,6 +94,7 @@ no_implicit_optional = True scripts_are_modules = True warn_unused_ignores = True show_error_codes = True +enable_error_code = ignore-without-code [mypy-astroid.*] ignore_missing_imports = True From 11807f0ae07af4d11a2ac8f9edbaa79558741cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 13:23:52 +0100 Subject: [PATCH 251/357] Update ``astroid`` requirement to 2.11.0 --- ChangeLog | 3 +++ doc/whatsnew/2.13.rst | 3 +++ requirements_test_min.txt | 2 +- setup.cfg | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 33dacc56bf..4f564286a3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,9 @@ Release date: TBA Closes #5729 +* The line numbering for messages related to function arguments is now more accurate. This can + require some message disables to be relocated to updated positions. + * Add ``--recursive`` option to allow recursive discovery of all modules and packages in subtree. Running pylint with ``--recursive=y`` option will check all discovered ``.py`` files and packages found inside subtree of directory provided as parameter to pylint. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 96dfa518a9..837a7ff330 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -110,6 +110,9 @@ Other Changes Closes #5840 +* The line numbering for messages related to function arguments is now more accurate. This can + require some message disables to be relocated to updated positions. + * ``using-f-string-in-unsupported-version`` and ``using-final-decorator-in-unsupported-version`` msgids were renamed from ``W1601`` and ``W1602`` to ``W2601`` and ``W2602``. Disables using these msgids will break. This is done in order to restore consistency with the already existing msgids for ``apply-builtin`` and diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 8c5b0f9c38..10385cd5b6 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e .[testutil] # astroid dependency is also defined in setup.cfg -astroid==2.9.3 # Pinned to a specific version for tests +astroid==2.11.0 # Pinned to a specific version for tests typing-extensions~=4.0 pytest~=7.0 pytest-benchmark~=3.4 diff --git a/setup.cfg b/setup.cfg index 71462cb10c..31de3b711a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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.9.2,<=2.10.0-dev0 + astroid>=2.11.0,<=2.12.0-dev0 isort>=4.2.5,<6 mccabe>=0.6,<0.8 tomli>=1.1.0;python_version<"3.11" From 6273752b0e77b4cb9fe848225dfd72734707ae34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 13:37:05 +0100 Subject: [PATCH 252/357] Fixes for tests due to new ``tolineno`` behaviour --- tests/checkers/unittest_stdlib.py | 6 ++---- tests/checkers/unittest_unicode/unittest_bad_chars.py | 1 + .../ext/broad_try_clause/broad_try_clause_extension.txt | 2 +- tests/functional/i/invalid/invalid_name.py | 9 ++------- tests/functional/i/invalid/invalid_name.txt | 8 ++++---- tests/functional/r/regression/regression_2913.py | 2 +- tests/functional/r/regression/regression_2913.txt | 1 - 7 files changed, 11 insertions(+), 18 deletions(-) delete mode 100644 tests/functional/r/regression/regression_2913.txt diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py index 88f5c79229..890fa7a8a9 100644 --- a/tests/checkers/unittest_stdlib.py +++ b/tests/checkers/unittest_stdlib.py @@ -49,10 +49,8 @@ def test_deprecated_no_qname_on_unexpected_nodes(self) -> None: """ def infer_func( - node: Name, context: Optional[Any] = None - ) -> Iterator[ - Union[Iterator, Iterator[AssignAttr]] - ]: # pylint: disable=unused-argument + node: Name, context: Optional[Any] = None # pylint: disable=unused-argument + ) -> Iterator[Union[Iterator, Iterator[AssignAttr]]]: new_node = nodes.AssignAttr(attrname="alpha", parent=node) yield new_node diff --git a/tests/checkers/unittest_unicode/unittest_bad_chars.py b/tests/checkers/unittest_unicode/unittest_bad_chars.py index 1f1e1e8d96..8c98e094bd 100644 --- a/tests/checkers/unittest_unicode/unittest_bad_chars.py +++ b/tests/checkers/unittest_unicode/unittest_bad_chars.py @@ -130,6 +130,7 @@ def test_find_bad_chars( # string module = astroid.MANAGER.ast_from_string(file) except AstroidBuildingError: + # pylint: disable-next=redefined-variable-type module = cast(nodes.Module, FakeNode(file.read_bytes())) expected = [ diff --git a/tests/functional/ext/broad_try_clause/broad_try_clause_extension.txt b/tests/functional/ext/broad_try_clause/broad_try_clause_extension.txt index 221d3a4c4d..d8d2c3e77c 100644 --- a/tests/functional/ext/broad_try_clause/broad_try_clause_extension.txt +++ b/tests/functional/ext/broad_try_clause/broad_try_clause_extension.txt @@ -1,4 +1,4 @@ too-many-try-statements:5:0:10:8::try clause contains 3 statements, expected at most 1:UNDEFINED too-many-try-statements:12:0:17:8::try clause contains 3 statements, expected at most 1:UNDEFINED -too-many-try-statements:19:0:27:8::try clause contains 4 statements, expected at most 1:UNDEFINED +too-many-try-statements:19:0:25:8::try clause contains 4 statements, expected at most 1:UNDEFINED too-many-try-statements:29:0:44:8::try clause contains 7 statements, expected at most 1:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_name.py b/tests/functional/i/invalid/invalid_name.py index 527064c44c..201d5bf9f3 100644 --- a/tests/functional/i/invalid/invalid_name.py +++ b/tests/functional/i/invalid/invalid_name.py @@ -58,13 +58,8 @@ def wrapper(*args, **kwargs): return real_decorator -@dummy_decorator(1, [ - 0 # Fixing #119 should make this go away -# C0103 always points here - line 61 # [invalid-name] - - -]) -def a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad(): # Should be line 65 +@dummy_decorator(1, [0]) +def a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad(): # [invalid-name] """Docstring""" print('LOL') diff --git a/tests/functional/i/invalid/invalid_name.txt b/tests/functional/i/invalid/invalid_name.txt index 8f464f5d65..bec705f981 100644 --- a/tests/functional/i/invalid/invalid_name.txt +++ b/tests/functional/i/invalid/invalid_name.txt @@ -2,7 +2,7 @@ invalid-name:12:0:12:3::"Constant name ""aaa"" doesn't conform to UPPER_CASE nam invalid-name:16:4:16:8::"Constant name ""time"" doesn't conform to UPPER_CASE naming style":HIGH invalid-name:32:0:33:12:a:"Function name ""a"" doesn't conform to snake_case naming style":HIGH invalid-name:46:4:46:13::"Constant name ""Foocapfor"" doesn't conform to UPPER_CASE naming style":HIGH -invalid-name:63:0:69:16:a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad:"Function name ""a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad"" doesn't conform to snake_case naming style":HIGH -invalid-name:75:23:75:29:FooBar.__init__:"Argument name ""fooBar"" doesn't conform to snake_case naming style":HIGH -invalid-name:81:8:81:14:FooBar.func1:"Argument name ""fooBar"" doesn't conform to snake_case naming style":HIGH -invalid-name:101:8:101:15:FooBar.test_disable_mixed:"Argument name ""fooBar2"" doesn't conform to snake_case naming style":HIGH +invalid-name:62:0:64:16:a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad:"Function name ""a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad"" doesn't conform to snake_case naming style":HIGH +invalid-name:70:23:70:29:FooBar.__init__:"Argument name ""fooBar"" doesn't conform to snake_case naming style":HIGH +invalid-name:76:8:76:14:FooBar.func1:"Argument name ""fooBar"" doesn't conform to snake_case naming style":HIGH +invalid-name:96:8:96:15:FooBar.test_disable_mixed:"Argument name ""fooBar2"" doesn't conform to snake_case naming style":HIGH diff --git a/tests/functional/r/regression/regression_2913.py b/tests/functional/r/regression/regression_2913.py index f36adb4599..9bcc963faa 100644 --- a/tests/functional/r/regression/regression_2913.py +++ b/tests/functional/r/regression/regression_2913.py @@ -14,6 +14,6 @@ class BaseCorrect: # pylint: disable=too-few-public-methods """My base class.""" -class BaseInner: # [too-few-public-methods] +class BaseInner: # pylint: disable=too-few-public-methods """My base class.""" diff --git a/tests/functional/r/regression/regression_2913.txt b/tests/functional/r/regression/regression_2913.txt deleted file mode 100644 index f30ae1ae92..0000000000 --- a/tests/functional/r/regression/regression_2913.txt +++ /dev/null @@ -1 +0,0 @@ -too-few-public-methods:17:0:19:24:BaseInner:Too few public methods (0/2):UNDEFINED From 72193ab970705e532ecaffe91c1810bbc1745de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 15:33:50 +0100 Subject: [PATCH 253/357] Add regression test for issue #5408 (#5795) --- ChangeLog | 6 +++ doc/whatsnew/2.13.rst | 6 +++ .../r/regression_02/regression_5408.py | 37 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tests/functional/r/regression_02/regression_5408.py diff --git a/ChangeLog b/ChangeLog index 4f564286a3..9422c8d21b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -131,6 +131,12 @@ Release date: TBA Closes #5670 +* Fixed crash with recursion error for inference of class attributes that referenced + the class itself. + + Closes #5408 + Ref PyCQA/astroid#1392 + * The issue template for crashes is now created for crashes which were previously not covered by this mechanism. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 837a7ff330..22defda5df 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -219,6 +219,12 @@ Other Changes Closes #5177, #5212 +* Fixed crash with recursion error for inference of class attributes that referenced + the class itself. + + Closes #5408 + Ref PyCQA/astroid#1392 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/tests/functional/r/regression_02/regression_5408.py b/tests/functional/r/regression_02/regression_5408.py new file mode 100644 index 0000000000..b9bcdb9e83 --- /dev/null +++ b/tests/functional/r/regression_02/regression_5408.py @@ -0,0 +1,37 @@ +"""Regression test for issue 5408. + +Recursion error for self-referencing class attribute. +See: https://github.com/PyCQA/pylint/issues/5408 +""" + +# pylint: disable=missing-docstring, too-few-public-methods, invalid-name, inherit-non-class +# pylint: disable=no-self-argument + + +class MyInnerClass: + ... + + +class MySubClass: + inner_class = MyInnerClass + + +class MyClass: + sub_class = MySubClass() + + +def get_unpatched_class(cls): + return cls + + +def get_unpatched(item): + lookup = get_unpatched_class if isinstance(item, type) else lambda item: None + return lookup(item) + + +_Child = get_unpatched(MyClass.sub_class.inner_class) + + +class Child(_Child): + def patch(cls): + MyClass.sub_class.inner_class = cls From ae3853648ac012467c0c99e9388ffa008fc014af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 17:52:29 +0100 Subject: [PATCH 254/357] Add tests for #4826 (#5696) --- ChangeLog | 6 ++++++ doc/whatsnew/2.13.rst | 6 ++++++ .../functional/n/no/no_member_binary_operations.py | 13 +++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 tests/functional/n/no/no_member_binary_operations.py diff --git a/ChangeLog b/ChangeLog index 9422c8d21b..b73d77685e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,12 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* No longer emit ``no-member`` in for loops that reference ``self`` if the binary operation that + started the for loop uses a ``self`` that is encapsulated in tuples or lists. + + Ref PyCQA/astroid#1360 + Closes #4826 + * Fix pyreverse diagrams type hinting for classmethods and staticmethods. * Fix matching ``--notes`` options that end in a non-word character. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 22defda5df..a3d10a9291 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -106,6 +106,12 @@ Extensions Other Changes ============= +* No longer emit ``no-member`` in for loops that reference ``self`` if the binary operation that + started the for loop uses a ``self`` that is encapsulated in tuples or lists. + + Ref PyCQA/astroid#1360 + Closes #4826 + * Fix matching ``--notes`` options that end in a non-word character. Closes #5840 diff --git a/tests/functional/n/no/no_member_binary_operations.py b/tests/functional/n/no/no_member_binary_operations.py new file mode 100644 index 0000000000..e998932ebe --- /dev/null +++ b/tests/functional/n/no/no_member_binary_operations.py @@ -0,0 +1,13 @@ +"""Tests for no-member in relation to binary operations.""" +# pylint: disable=too-few-public-methods, missing-class-docstring, missing-function-docstring + +# Test for: https://github.com/PyCQA/pylint/issues/4826 +class MyClass: + def __init__(self): + self.a_list = [] + self.data = [] + + def operator(self): + for i in [self] + self.a_list: + for _ in i.data: + pass From 7dcb3ae9020729e9fb3b53b171567620138d485b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 19:19:09 +0100 Subject: [PATCH 255/357] Add regression test for #5679 (#5725) --- ChangeLog | 5 ++ doc/whatsnew/2.13.rst | 5 ++ .../max_inferable_limit_for_classes/main.py | 38 +++++++++ .../nodes/roles.py | 82 +++++++++++++++++++ .../other_funcs.py | 31 +++++++ tests/test_self.py | 21 +++++ 6 files changed, 182 insertions(+) create mode 100644 tests/regrtest_data/max_inferable_limit_for_classes/main.py create mode 100644 tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py create mode 100644 tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py diff --git a/ChangeLog b/ChangeLog index b73d77685e..ba2e3a20d4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -333,6 +333,11 @@ Release date: TBA Closes #5557 +* Fixed a crash on ``__init__`` nodes when the attribute was previously uninferable due to a cache + limit size. This limit can be hit when the inheritance pattern of a class (and therefore of the ``__init__`` attribute) is very large. + + Closes #5679 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a3d10a9291..1b5fa13b93 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -210,6 +210,11 @@ Other Changes Closes #5713 +* Fixed a crash on ``__init__`` nodes when the attribute was previously uninferable due to a cache + limit size. This limit can be hit when the inheritance pattern of a class (and therefore of the ``__init__`` attribute) is very large. + + Closes #5679 + * Fixed extremely long processing of long lines with comma's. Closes #5483 diff --git a/tests/regrtest_data/max_inferable_limit_for_classes/main.py b/tests/regrtest_data/max_inferable_limit_for_classes/main.py new file mode 100644 index 0000000000..2588d916fe --- /dev/null +++ b/tests/regrtest_data/max_inferable_limit_for_classes/main.py @@ -0,0 +1,38 @@ +"""This example is based on sqlalchemy. + +See https://github.com/PyCQA/pylint/issues/5679 +""" +from other_funcs import FromClause + +from .nodes import roles + + +class HasMemoized(object): + ... + + +class Generative(HasMemoized): + ... + + +class ColumnElement( + roles.ColumnArgumentOrKeyRole, + roles.BinaryElementRole, + roles.OrderByRole, + roles.ColumnsClauseRole, + roles.LimitOffsetRole, + roles.DMLColumnRole, + roles.DDLConstraintColumnRole, + roles.StatementRole, + Generative, +): + ... + + +class FunctionElement(ColumnElement, FromClause): + ... + + +class months_between(FunctionElement): + def __init__(self): + super().__init__() diff --git a/tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py b/tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py new file mode 100644 index 0000000000..2f58f1b508 --- /dev/null +++ b/tests/regrtest_data/max_inferable_limit_for_classes/nodes/roles.py @@ -0,0 +1,82 @@ +class SQLRole(object): + ... + + +class UsesInspection(object): + ... + + +class AllowsLambdaRole(object): + ... + + +class ColumnArgumentRole(SQLRole): + ... + + +class ColumnArgumentOrKeyRole(ColumnArgumentRole): + ... + + +class ColumnListRole(SQLRole): + ... + + +class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole): + ... + + +class LimitOffsetRole(SQLRole): + ... + + +class ByOfRole(ColumnListRole): + ... + + +class OrderByRole(AllowsLambdaRole, ByOfRole): + ... + + +class StructuralRole(SQLRole): + ... + + +class ExpressionElementRole(SQLRole): + ... + + +class BinaryElementRole(ExpressionElementRole): + ... + + +class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole): + ... + + +class FromClauseRole(ColumnsClauseRole, JoinTargetRole): + ... + + +class StrictFromClauseRole(FromClauseRole): + ... + + +class AnonymizedFromClauseRole(StrictFromClauseRole): + ... + + +class ReturnsRowsRole(SQLRole): + ... + + +class StatementRole(SQLRole): + ... + + +class DMLColumnRole(SQLRole): + ... + + +class DDLConstraintColumnRole(SQLRole): + ... diff --git a/tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py b/tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py new file mode 100644 index 0000000000..f737fbf5a8 --- /dev/null +++ b/tests/regrtest_data/max_inferable_limit_for_classes/other_funcs.py @@ -0,0 +1,31 @@ +from operator import attrgetter + +from .nodes import roles + + +class HasCacheKey(object): + ... + + +class HasMemoized(object): + ... + + +class MemoizedHasCacheKey(HasCacheKey, HasMemoized): + ... + + +class ClauseElement(MemoizedHasCacheKey): + ... + + +class ReturnsRows(roles.ReturnsRowsRole, ClauseElement): + ... + + +class Selectable(ReturnsRows): + ... + + +class FromClause(roles.AnonymizedFromClauseRole, Selectable): + c = property(attrgetter("columns")) diff --git a/tests/test_self.py b/tests/test_self.py index d22d2a5e48..0018b47a9b 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1287,6 +1287,27 @@ def test_regex_paths_csv_validator() -> None: Run(["--ignore-paths", "test", join(HERE, "regrtest_data", "empty.py")]) assert ex.value.code == 0 + @staticmethod + def test_max_inferred_for_complicated_class_hierarchy() -> None: + """Regression test for a crash reported in https://github.com/PyCQA/pylint/issues/5679. + + The class hierarchy of 'sqlalchemy' is so intricate that it becomes uninferable with + the standard max_inferred of 100. We used to crash when this happened. + """ + with pytest.raises(SystemExit) as ex: + Run( + [ + join( + HERE, + "regrtest_data", + "max_inferable_limit_for_classes", + "main.py", + ), + ] + ) + # Error code should not include bit-value 1 for crash + assert not ex.value.code % 2 + def test_regression_recursive(self): self._test_output( [join(HERE, "regrtest_data", "directory", "subdirectory"), "--recursive=n"], From 49a2e193ac9b796867685b9caae67428c862ac24 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 12 Mar 2022 13:22:58 -0500 Subject: [PATCH 256/357] Fix false positive for `invalid-class-object` when inference fails (#5901) --- ChangeLog | 3 +++ doc/whatsnew/2.13.rst | 3 +++ pylint/checkers/classes/class_checker.py | 8 ++++++-- tests/functional/i/invalid/invalid_class_object.py | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index ba2e3a20d4..825d1e15bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -230,6 +230,9 @@ Release date: TBA Closes #3793 +* Fixed a false positive for ``invalid-class-object`` when the object + being assigned to the ``__class__`` attribute is uninferable. + * Fixed false positive for ``used-before-assignment`` with self-referential type annotation in conditional statements within class methods. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 1b5fa13b93..ed804f0624 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -307,6 +307,9 @@ Other Changes Closes #3793 +* Fixed a false positive for ``invalid-class-object`` when the object + being assigned to the ``__class__`` attribute is uninferable. + * Added a ``testutil`` extra require to the packaging, as ``gitpython`` should not be a dependency all the time but is still required to use the primer helper code in ``pylint.testutil``. You can install it with ``pip install pylint[testutil]``. diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index dcde9231b0..8dbb58fd4a 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1482,8 +1482,12 @@ def _check_invalid_class_object(self, node: nodes.AssignAttr) -> None: if not node.attrname == "__class__": return inferred = safe_infer(node.parent.value) - if isinstance(inferred, nodes.ClassDef) or inferred is astroid.Uninferable: - # If is uninferrable, we allow it to prevent false positives + if ( + isinstance(inferred, nodes.ClassDef) + or inferred is astroid.Uninferable + or inferred is None + ): + # If is uninferable, we allow it to prevent false positives return self.add_message("invalid-class-object", node=node) diff --git a/tests/functional/i/invalid/invalid_class_object.py b/tests/functional/i/invalid/invalid_class_object.py index 367756eaa8..7c08ebae81 100644 --- a/tests/functional/i/invalid/invalid_class_object.py +++ b/tests/functional/i/invalid/invalid_class_object.py @@ -16,3 +16,17 @@ class B: A.__class__ = defaultdict A.__class__ = defaultdict(str) # [invalid-class-object] A.__class__ = 1 # [invalid-class-object] + + +# Here, ambiguity is found when inferring self.__class__ +class C: + @classmethod + def _new_instance(cls): + obj = C() + obj.__class__ = cls + return obj + + def __deepcopy__(self, memo): + obj = C() + obj.__class__ = self.__class__ + return obj From 6ead5fd8fbba5c63ecfa39ab269eaadc577b4cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 19:24:31 +0100 Subject: [PATCH 257/357] Use ``node.position`` in ``add_message`` if available (#5897) --- ChangeLog | 6 ++ doc/whatsnew/2.13.rst | 6 ++ pylint/lint/pylinter.py | 26 ++++--- pylint/testutils/unittest_linter.py | 26 ++++--- .../functional/a/abstract/abstract_method.txt | 32 ++++----- tests/functional/a/arguments_differ.txt | 24 +++---- tests/functional/a/arguments_renamed.txt | 22 +++--- tests/functional/a/async_functions.txt | 14 ++-- .../b/bad_staticmethod_argument.txt | 4 +- tests/functional/b/blacklisted_name.txt | 2 +- tests/functional/c/cached_property.txt | 2 +- .../functional/d/dangerous_default_value.txt | 44 ++++++------ tests/functional/d/dataclass_typecheck.txt | 2 +- tests/functional/d/disable_msg_next_line.txt | 2 +- tests/functional/d/docstrings.txt | 14 ++-- tests/functional/d/dotted_ancestor.txt | 2 +- tests/functional/d/duplicate_bases.txt | 2 +- tests/functional/ext/docparams/docparams.txt | 24 +++---- .../ext/docparams/missing_param_doc.txt | 36 +++++----- .../parameter/missing_param_doc_required.txt | 6 +- .../missing_param_doc_required_Google.txt | 52 +++++++------- .../missing_param_doc_required_Numpy.txt | 44 ++++++------ .../missing_param_doc_required_Sphinx.txt | 70 +++++++++---------- ...ram_doc_required_no_doc_rgx_check_init.txt | 2 +- ...param_doc_required_no_doc_rgx_test_all.txt | 2 +- .../docparams/raise/missing_raises_doc.txt | 2 +- .../raise/missing_raises_doc_Google.txt | 12 ++-- .../raise/missing_raises_doc_Numpy.txt | 12 ++-- .../raise/missing_raises_doc_Sphinx.txt | 12 ++-- .../raise/missing_raises_doc_required.txt | 2 +- ...ng_raises_doc_required_exc_inheritance.txt | 2 +- .../return/missing_return_doc_Google.txt | 10 +-- .../return/missing_return_doc_Numpy.txt | 10 +-- .../return/missing_return_doc_Sphinx.txt | 4 +- .../return/missing_return_doc_required.txt | 4 +- .../missing_return_doc_required_Google.txt | 16 ++--- .../missing_return_doc_required_Numpy.txt | 16 ++--- .../missing_return_doc_required_Sphinx.txt | 16 ++--- .../ext/docparams/useless_type_doc.txt | 8 +-- .../yield/missing_yield_doc_Google.txt | 2 +- .../yield/missing_yield_doc_Numpy.txt | 2 +- .../yield/missing_yield_doc_required.txt | 4 +- .../missing_yield_doc_required_Google.txt | 10 +-- .../missing_yield_doc_required_Numpy.txt | 6 +- .../missing_yield_doc_required_Sphinx.txt | 10 +-- .../docstyle/docstyle_first_line_empty.txt | 6 +- .../ext/docstyle/docstyle_quotes_py38.txt | 8 +-- .../ext/eq_without_hash/eq_without_hash.txt | 2 +- tests/functional/ext/mccabe/mccabe.txt | 28 ++++---- tests/functional/f/first_arg.txt | 10 +-- tests/functional/f/function_redefined.txt | 12 ++-- .../generic_alias_collections.txt | 22 +++--- .../generic_alias_collections_py37.txt | 10 +-- ...ric_alias_collections_py37_with_typing.txt | 10 +-- .../generic_alias_mixed_py37.txt | 10 +-- .../generic_alias_mixed_py39.txt | 10 +-- ...eneric_alias_postponed_evaluation_py37.txt | 10 +-- .../g/generic_alias/generic_alias_related.txt | 2 +- .../generic_alias_related_py39.txt | 2 +- .../generic_alias_side_effects.txt | 16 ++--- .../g/generic_alias/generic_alias_typing.txt | 24 +++---- .../i/inconsistent/inconsistent_mro.txt | 2 +- .../i/inconsistent/inconsistent_returns.txt | 34 ++++----- .../inconsistent_returns_noreturn.txt | 2 +- tests/functional/i/inherit_non_class.txt | 20 +++--- tests/functional/i/init_is_generator.txt | 2 +- tests/functional/i/init_not_called.txt | 4 +- .../i/invalid/invalid_bool_returned.txt | 6 +- .../i/invalid/invalid_bytes_returned.txt | 6 +- .../i/invalid/invalid_format_returned.txt | 6 +- .../invalid_getnewargs_ex_returned.txt | 12 ++-- .../invalid_getnewargs_returned.txt | 6 +- .../i/invalid/invalid_hash_returned.txt | 8 +-- .../i/invalid/invalid_index_returned.txt | 8 +-- .../invalid_length_hint_returned.txt | 6 +- .../invalid_length_returned.txt | 8 +-- .../i/invalid/invalid_metaclass.txt | 12 ++-- tests/functional/i/invalid/invalid_name.txt | 4 +- .../invalid_name_module_level.txt | 2 +- .../invalid_name_multinaming_style.txt | 2 +- .../invalid_name/invalid_name_property.txt | 4 +- .../i/invalid/invalid_overridden_method.txt | 12 ++-- .../i/invalid/invalid_repr_returned.txt | 6 +- .../i/invalid/invalid_str_returned.txt | 6 +- .../k/keyword_arg_before_vararg.txt | 8 +-- tests/functional/m/method_hidden.txt | 6 +- .../m/missing/missing_class_docstring.txt | 2 +- .../m/missing/missing_docstring.txt | 4 +- .../m/missing/missing_docstring_new_style.txt | 8 +-- .../m/missing/missing_function_docstring.txt | 4 +- .../missing_function_docstring_min_length.txt | 4 +- .../missing_function_docstring_rgx.txt | 2 +- .../m/missing/missing_self_argument.txt | 4 +- .../n/name/name_good_bad_names_regex.txt | 2 +- .../n/name/name_preset_snake_case.txt | 8 +-- tests/functional/n/name/name_styles.txt | 14 ++-- tests/functional/n/namePresetCamelCase.txt | 4 +- tests/functional/n/no/no_self_argument.txt | 4 +- .../functional/n/no/no_self_argument_py37.txt | 2 +- tests/functional/n/no/no_self_use.txt | 4 +- tests/functional/n/non/non_ascii_name.txt | 2 +- .../n/non/non_init_parent_called.txt | 2 +- .../n/non/non_iterator_returned.txt | 8 +-- .../non_ascii_name_function.txt | 2 +- .../non_ascii_name_staticmethod.txt | 2 +- .../non_ascii_name_class.txt | 2 +- .../non_ascii_name_class_method.txt | 2 +- tests/functional/n/nonlocal_and_global.txt | 2 +- .../o/overridden_final_method_py38.txt | 4 +- .../functional/p/property_with_parameters.txt | 2 +- tests/functional/p/protocol_classes.txt | 4 +- .../r/regression/regression_4680.txt | 2 +- .../r/regression/regression_4723.txt | 2 +- tests/functional/r/return_in_init.txt | 2 +- tests/functional/s/signature_differs.txt | 2 +- .../functional/s/singledispatch_functions.txt | 2 +- tests/functional/s/slots_checks.txt | 12 ++-- .../s/subclassed_final_class_py38.txt | 2 +- .../s/super/super_init_not_called.txt | 2 +- .../super_init_not_called_extensions.txt | 2 +- ...super_init_not_called_extensions_py310.txt | 2 +- .../t/too/too_few_public_methods.txt | 2 +- .../t/too/too_few_public_methods_excluded.txt | 2 +- tests/functional/t/too/too_many_ancestors.txt | 4 +- .../too_many_ancestors_ignored_parents.txt | 2 +- tests/functional/t/too/too_many_arguments.txt | 2 +- tests/functional/t/too/too_many_branches.txt | 2 +- .../t/too/too_many_instance_attributes.txt | 2 +- tests/functional/t/too/too_many_locals.txt | 6 +- .../t/too/too_many_public_methods.txt | 2 +- .../t/too/too_many_return_statements.txt | 2 +- .../functional/t/too/too_many_statements.txt | 6 +- tests/functional/t/typing_use.txt | 2 +- .../u/undefined/undefined_variable_py30.txt | 6 +- .../u/unexpected_special_method_signature.txt | 32 ++++----- .../u/unused/unused_private_member.txt | 20 +++--- .../u/unused/unused_variable_py38.txt | 8 +-- .../u/use/use_symbolic_message_instead.txt | 4 +- .../u/useless/useless_object_inheritance.txt | 8 +-- tests/functional/u/useless/useless_return.txt | 4 +- .../u/useless/useless_super_delegation.txt | 38 +++++----- .../useless/useless_super_delegation_py3.txt | 4 +- .../useless/useless_super_delegation_py35.txt | 2 +- .../useless/useless_super_delegation_py38.txt | 2 +- 144 files changed, 661 insertions(+), 629 deletions(-) diff --git a/ChangeLog b/ChangeLog index 825d1e15bf..709c895854 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,12 @@ Release date: TBA Closes #5840 +* Updated the position of messages for class and function defintions to no longer cover + the complete definition. Only the ``def`` or ``class`` + the name of the class/function + are covered. + + Closes #5466 + * ``using-f-string-in-unsupported-version`` and ``using-final-decorator-in-unsupported-version`` msgids were renamed from ``W1601`` and ``W1602`` to ``W2601`` and ``W2602``. Disabling using these msgids will break. This is done in order to restore consistency with the already existing msgids for ``apply-builtin`` and diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index ed804f0624..2314725da5 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -133,6 +133,12 @@ Other Changes Closes #352 +* Updated the position of messages for class and function defintions to no longer cover + the complete definition. Only the ``def`` or ``class`` + the name of the class/function + are covered. + + Closes #5466 + * Reinstated checks from the python3 checker that are still useful for python 3 (``eq-without-hash``). This is now in the ``pylint.extensions.eq_without_hash`` optional extension. diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 2f5389d613..9f94da292d 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1526,14 +1526,24 @@ def _add_one_message( # Look up "location" data of node if not yet supplied if node: - if not line: - line = node.fromlineno - if not col_offset: - col_offset = node.col_offset - if not end_lineno: - end_lineno = node.end_lineno - if not end_col_offset: - end_col_offset = node.end_col_offset + if node.position: + if not line: + line = node.position.lineno + if not col_offset: + col_offset = node.position.col_offset + if not end_lineno: + end_lineno = node.position.end_lineno + if not end_col_offset: + end_col_offset = node.position.end_col_offset + else: + if not line: + line = node.fromlineno + if not col_offset: + col_offset = node.col_offset + if not end_lineno: + end_lineno = node.end_lineno + if not end_col_offset: + end_col_offset = node.end_col_offset # should this message be displayed if not self.is_message_enabled(message_definition.msgid, line, confidence): diff --git a/pylint/testutils/unittest_linter.py b/pylint/testutils/unittest_linter.py index c9945f52fb..37d430fd2b 100644 --- a/pylint/testutils/unittest_linter.py +++ b/pylint/testutils/unittest_linter.py @@ -46,14 +46,24 @@ def add_message( # Look up "location" data of node if not yet supplied if node: - if not line: - line = node.fromlineno - if not col_offset: - col_offset = node.col_offset - if not end_lineno: - end_lineno = node.end_lineno - if not end_col_offset: - end_col_offset = node.end_col_offset + if node.position: + if not line: + line = node.position.lineno + if not col_offset: + col_offset = node.position.col_offset + if not end_lineno: + end_lineno = node.position.end_lineno + if not end_col_offset: + end_col_offset = node.position.end_col_offset + else: + if not line: + line = node.fromlineno + if not col_offset: + col_offset = node.col_offset + if not end_lineno: + end_lineno = node.end_lineno + if not end_col_offset: + end_col_offset = node.end_col_offset self._messages.append( MessageTest( diff --git a/tests/functional/a/abstract/abstract_method.txt b/tests/functional/a/abstract/abstract_method.txt index 1eb47639c6..2b4ea9a2eb 100644 --- a/tests/functional/a/abstract/abstract_method.txt +++ b/tests/functional/a/abstract/abstract_method.txt @@ -1,16 +1,16 @@ -abstract-method:47:0:51:38:Concrete:Method 'bbbb' is abstract in class 'Abstract' but is not overridden:UNDEFINED -abstract-method:70:0:72:12:Container:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:70:0:72:12:Container:Method '__iter__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:70:0:72:12:Container:Method '__len__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:76:0:78:17:Sizable:Method '__contains__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:76:0:78:17:Sizable:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:76:0:78:17:Sizable:Method '__iter__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:82:0:83:17:Hashable:Method '__contains__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:82:0:83:17:Hashable:Method '__iter__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:82:0:83:17:Hashable:Method '__len__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:87:0:91:19:Iterator:Method '__contains__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:87:0:91:19:Iterator:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:87:0:91:19:Iterator:Method '__len__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:106:0:107:8:BadComplexMro:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED -abstract-method:106:0:107:8:BadComplexMro:Method '__len__' is abstract in class 'AbstractSizable' but is not overridden:UNDEFINED -abstract-method:106:0:107:8:BadComplexMro:Method 'length' is abstract in class 'AbstractSizable' but is not overridden:UNDEFINED +abstract-method:47:0:47:14:Concrete:Method 'bbbb' is abstract in class 'Abstract' but is not overridden:UNDEFINED +abstract-method:70:0:70:15:Container:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:70:0:70:15:Container:Method '__iter__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:70:0:70:15:Container:Method '__len__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:76:0:76:13:Sizable:Method '__contains__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:76:0:76:13:Sizable:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:76:0:76:13:Sizable:Method '__iter__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:82:0:82:14:Hashable:Method '__contains__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:82:0:82:14:Hashable:Method '__iter__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:82:0:82:14:Hashable:Method '__len__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:87:0:87:14:Iterator:Method '__contains__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:87:0:87:14:Iterator:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:87:0:87:14:Iterator:Method '__len__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:106:0:106:19:BadComplexMro:Method '__hash__' is abstract in class 'Structure' but is not overridden:UNDEFINED +abstract-method:106:0:106:19:BadComplexMro:Method '__len__' is abstract in class 'AbstractSizable' but is not overridden:UNDEFINED +abstract-method:106:0:106:19:BadComplexMro:Method 'length' is abstract in class 'AbstractSizable' but is not overridden:UNDEFINED diff --git a/tests/functional/a/arguments_differ.txt b/tests/functional/a/arguments_differ.txt index 5cce378e27..07ef79a0f9 100644 --- a/tests/functional/a/arguments_differ.txt +++ b/tests/functional/a/arguments_differ.txt @@ -1,12 +1,12 @@ -arguments-differ:12:4:13:12:Child.test:Number of parameters was 1 in 'Parent.test' and is now 2 in overridden 'Child.test' method:UNDEFINED -arguments-differ:23:4:24:12:ChildDefaults.test:Number of parameters was 3 in 'ParentDefaults.test' and is now 2 in overridden 'ChildDefaults.test' method:UNDEFINED -arguments-differ:41:4:42:12:ClassmethodChild.func:Number of parameters was 2 in 'Classmethod.func' and is now 0 in overridden 'ClassmethodChild.func' method:UNDEFINED -arguments-differ:68:4:69:64:VarargsChild.has_kwargs:Variadics removed in overridden 'VarargsChild.has_kwargs' method:UNDEFINED -arguments-renamed:71:4:72:89:VarargsChild.no_kwargs:Parameter 'args' has been renamed to 'arg' in overridden 'VarargsChild.no_kwargs' method:UNDEFINED -arguments-differ:144:4:145:26:StaticmethodChild2.func:Number of parameters was 1 in 'Staticmethod.func' and is now 2 in overridden 'StaticmethodChild2.func' method:UNDEFINED -arguments-differ:180:4:181:12:SecondChangesArgs.test:Number of parameters was 2 in 'FirstHasArgs.test' and is now 4 in overridden 'SecondChangesArgs.test' method:UNDEFINED -arguments-differ:306:4:307:60:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method:UNDEFINED -arguments-differ:309:4:310:82:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method:UNDEFINED -arguments-differ:312:4:313:32:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method:UNDEFINED -arguments-differ:315:4:316:32:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method:UNDEFINED -arguments-differ:318:4:319:41:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method:UNDEFINED +arguments-differ:12:4:12:12:Child.test:Number of parameters was 1 in 'Parent.test' and is now 2 in overridden 'Child.test' method:UNDEFINED +arguments-differ:23:4:23:12:ChildDefaults.test:Number of parameters was 3 in 'ParentDefaults.test' and is now 2 in overridden 'ChildDefaults.test' method:UNDEFINED +arguments-differ:41:4:41:12:ClassmethodChild.func:Number of parameters was 2 in 'Classmethod.func' and is now 0 in overridden 'ClassmethodChild.func' method:UNDEFINED +arguments-differ:68:4:68:18:VarargsChild.has_kwargs:Variadics removed in overridden 'VarargsChild.has_kwargs' method:UNDEFINED +arguments-renamed:71:4:71:17:VarargsChild.no_kwargs:Parameter 'args' has been renamed to 'arg' in overridden 'VarargsChild.no_kwargs' method:UNDEFINED +arguments-differ:144:4:144:12:StaticmethodChild2.func:Number of parameters was 1 in 'Staticmethod.func' and is now 2 in overridden 'StaticmethodChild2.func' method:UNDEFINED +arguments-differ:180:4:180:12:SecondChangesArgs.test:Number of parameters was 2 in 'FirstHasArgs.test' and is now 4 in overridden 'SecondChangesArgs.test' method:UNDEFINED +arguments-differ:306:4:306:16:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method:UNDEFINED +arguments-differ:309:4:309:16:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method:UNDEFINED +arguments-differ:312:4:312:16:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method:UNDEFINED +arguments-differ:315:4:315:16:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method:UNDEFINED +arguments-differ:318:4:318:16:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method:UNDEFINED diff --git a/tests/functional/a/arguments_renamed.txt b/tests/functional/a/arguments_renamed.txt index 366aa47300..7f6466ba8d 100644 --- a/tests/functional/a/arguments_renamed.txt +++ b/tests/functional/a/arguments_renamed.txt @@ -1,11 +1,11 @@ -arguments-renamed:17:4:18:55:Orange.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'Orange.brew' method:UNDEFINED -arguments-renamed:20:4:21:69:Orange.eat_with_condiment:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'Orange.eat_with_condiment' method:UNDEFINED -arguments-differ:27:4:28:68:Banana.eat_with_condiment:Number of parameters was 3 in 'Fruit.eat_with_condiment' and is now 4 in overridden 'Banana.eat_with_condiment' method:UNDEFINED -arguments-renamed:40:4:41:23:Child.test:Parameter 'arg' has been renamed to 'arg1' in overridden 'Child.test' method:UNDEFINED -arguments-renamed:43:4:44:61:Child.kwargs_test:Parameter 'var1' has been renamed to 'value1' in overridden 'Child.kwargs_test' method:UNDEFINED -arguments-renamed:48:4:49:22:Child2.test:Parameter 'arg' has been renamed to 'var' in overridden 'Child2.test' method:UNDEFINED -arguments-differ:51:4:52:58:Child2.kwargs_test:Number of parameters was 4 in 'Parent.kwargs_test' and is now 3 in overridden 'Child2.kwargs_test' method:UNDEFINED -arguments-renamed:51:4:52:58:Child2.kwargs_test:Parameter 'var2' has been renamed to 'kw2' in overridden 'Child2.kwargs_test' method:UNDEFINED -arguments-renamed:67:4:68:56:ChildDefaults.test1:Parameter 'barg' has been renamed to 'param2' in overridden 'ChildDefaults.test1' method:UNDEFINED -arguments-renamed:95:8:96:59:FruitOverrideConditional.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'FruitOverrideConditional.brew' method:UNDEFINED -arguments-differ:99:12:100:76:FruitOverrideConditional.eat_with_condiment:Number of parameters was 3 in 'FruitConditional.eat_with_condiment' and is now 4 in overridden 'FruitOverrideConditional.eat_with_condiment' method:UNDEFINED +arguments-renamed:17:4:17:12:Orange.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'Orange.brew' method:UNDEFINED +arguments-renamed:20:4:20:26:Orange.eat_with_condiment:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'Orange.eat_with_condiment' method:UNDEFINED +arguments-differ:27:4:27:26:Banana.eat_with_condiment:Number of parameters was 3 in 'Fruit.eat_with_condiment' and is now 4 in overridden 'Banana.eat_with_condiment' method:UNDEFINED +arguments-renamed:40:4:40:12:Child.test:Parameter 'arg' has been renamed to 'arg1' in overridden 'Child.test' method:UNDEFINED +arguments-renamed:43:4:43:19:Child.kwargs_test:Parameter 'var1' has been renamed to 'value1' in overridden 'Child.kwargs_test' method:UNDEFINED +arguments-renamed:48:4:48:12:Child2.test:Parameter 'arg' has been renamed to 'var' in overridden 'Child2.test' method:UNDEFINED +arguments-differ:51:4:51:19:Child2.kwargs_test:Number of parameters was 4 in 'Parent.kwargs_test' and is now 3 in overridden 'Child2.kwargs_test' method:UNDEFINED +arguments-renamed:51:4:51:19:Child2.kwargs_test:Parameter 'var2' has been renamed to 'kw2' in overridden 'Child2.kwargs_test' method:UNDEFINED +arguments-renamed:67:4:67:13:ChildDefaults.test1:Parameter 'barg' has been renamed to 'param2' in overridden 'ChildDefaults.test1' method:UNDEFINED +arguments-renamed:95:8:95:16:FruitOverrideConditional.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'FruitOverrideConditional.brew' method:UNDEFINED +arguments-differ:99:12:99:34:FruitOverrideConditional.eat_with_condiment:Number of parameters was 3 in 'FruitConditional.eat_with_condiment' and is now 4 in overridden 'FruitOverrideConditional.eat_with_condiment' method:UNDEFINED diff --git a/tests/functional/a/async_functions.txt b/tests/functional/a/async_functions.txt index 535e387097..7ab56e2d3c 100644 --- a/tests/functional/a/async_functions.txt +++ b/tests/functional/a/async_functions.txt @@ -1,11 +1,11 @@ -redefined-builtin:5:0:6:8:next:Redefining built-in 'next':UNDEFINED +redefined-builtin:5:0:5:14:next:Redefining built-in 'next':UNDEFINED unused-argument:8:30:8:34:some_function:Unused argument 'arg2':HIGH bad-super-call:22:8:22:31:Class.some_method:Bad first argument 'OtherClass' given to super():UNDEFINED -too-many-arguments:26:0:53:12:complex_function:Too many arguments (10/5):UNDEFINED -too-many-branches:26:0:53:12:complex_function:Too many branches (13/12):UNDEFINED -too-many-return-statements:26:0:53:12:complex_function:Too many return statements (10/6):UNDEFINED -dangerous-default-value:57:0:58:15:func:Dangerous default value [] as argument:UNDEFINED +too-many-arguments:26:0:26:26:complex_function:Too many arguments (10/5):UNDEFINED +too-many-branches:26:0:26:26:complex_function:Too many branches (13/12):UNDEFINED +too-many-return-statements:26:0:26:26:complex_function:Too many return statements (10/6):UNDEFINED +dangerous-default-value:57:0:57:14:func:Dangerous default value [] as argument:UNDEFINED duplicate-argument-name:57:15:57:16:func:Duplicate argument name a in function definition:UNDEFINED duplicate-argument-name:57:18:57:19:func:Duplicate argument name a in function definition:UNDEFINED -disallowed-name:62:0:63:6:foo:"Disallowed name ""foo""":UNDEFINED -empty-docstring:62:0:63:6:foo:Empty function docstring:HIGH +disallowed-name:62:0:62:13:foo:"Disallowed name ""foo""":UNDEFINED +empty-docstring:62:0:62:13:foo:Empty function docstring:HIGH diff --git a/tests/functional/b/bad_staticmethod_argument.txt b/tests/functional/b/bad_staticmethod_argument.txt index 115555c31b..1456726875 100644 --- a/tests/functional/b/bad_staticmethod_argument.txt +++ b/tests/functional/b/bad_staticmethod_argument.txt @@ -1,2 +1,2 @@ -bad-staticmethod-argument:5:4:6:12:Abcd.method1:Static method with 'self' as first argument:UNDEFINED -bad-staticmethod-argument:10:4:11:12:Abcd.method2:Static method with 'cls' as first argument:UNDEFINED +bad-staticmethod-argument:5:4:5:15:Abcd.method1:Static method with 'self' as first argument:UNDEFINED +bad-staticmethod-argument:10:4:10:15:Abcd.method2:Static method with 'cls' as first argument:UNDEFINED diff --git a/tests/functional/b/blacklisted_name.txt b/tests/functional/b/blacklisted_name.txt index 63fe309713..b6b96c5901 100644 --- a/tests/functional/b/blacklisted_name.txt +++ b/tests/functional/b/blacklisted_name.txt @@ -1 +1 @@ -disallowed-name:3:0:4:8:baz:"Disallowed name ""baz""":UNDEFINED +disallowed-name:3:0:3:7:baz:"Disallowed name ""baz""":UNDEFINED diff --git a/tests/functional/c/cached_property.txt b/tests/functional/c/cached_property.txt index a4b52237c8..3756dd9198 100644 --- a/tests/functional/c/cached_property.txt +++ b/tests/functional/c/cached_property.txt @@ -1 +1 @@ -invalid-overridden-method:22:4:23:17:Child.func:Method 'func' was expected to be 'method', found it instead as 'property':UNDEFINED +invalid-overridden-method:22:4:22:12:Child.func:Method 'func' was expected to be 'method', found it instead as 'property':UNDEFINED diff --git a/tests/functional/d/dangerous_default_value.txt b/tests/functional/d/dangerous_default_value.txt index 5d809f2359..98d55c2b62 100644 --- a/tests/functional/d/dangerous_default_value.txt +++ b/tests/functional/d/dangerous_default_value.txt @@ -1,22 +1,22 @@ -dangerous-default-value:6:0:8:16:function1:Dangerous default value [] as argument:UNDEFINED -dangerous-default-value:10:0:12:16:function2:Dangerous default value HEHE (builtins.dict) as argument:UNDEFINED -dangerous-default-value:18:0:20:16:function4:Dangerous default value set() (builtins.set) as argument:UNDEFINED -dangerous-default-value:28:0:30:16:function6:Dangerous default value GLOBAL_SET (builtins.set) as argument:UNDEFINED -dangerous-default-value:32:0:34:16:function7:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED -dangerous-default-value:36:0:38:16:function8:Dangerous default value list() (builtins.list) as argument:UNDEFINED -dangerous-default-value:40:0:42:16:function9:Dangerous default value [] as argument:UNDEFINED -dangerous-default-value:44:0:46:16:function10:Dangerous default value {} as argument:UNDEFINED -dangerous-default-value:48:0:50:16:function11:Dangerous default value list() (builtins.list) as argument:UNDEFINED -dangerous-default-value:52:0:54:16:function12:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED -dangerous-default-value:61:0:63:16:function13:Dangerous default value OINK (builtins.dict) as argument:UNDEFINED -dangerous-default-value:65:0:69:16:function14:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED -dangerous-default-value:73:0:75:16:function15:Dangerous default value INVALID_DICT (builtins.dict) as argument:UNDEFINED -dangerous-default-value:77:0:79:16:function16:Dangerous default value set() as argument:UNDEFINED -dangerous-default-value:81:0:83:16:function17:Dangerous default value deque() (collections.deque) as argument:UNDEFINED -dangerous-default-value:85:0:87:16:function18:Dangerous default value ChainMap() (collections.ChainMap) as argument:UNDEFINED -dangerous-default-value:89:0:91:16:function19:Dangerous default value Counter() (collections.Counter) as argument:UNDEFINED -dangerous-default-value:93:0:95:16:function20:Dangerous default value OrderedDict() (collections.OrderedDict) as argument:UNDEFINED -dangerous-default-value:97:0:99:16:function21:Dangerous default value defaultdict() (collections.defaultdict) as argument:UNDEFINED -dangerous-default-value:101:0:103:16:function22:Dangerous default value UserDict() (collections.UserDict) as argument:UNDEFINED -dangerous-default-value:105:0:107:16:function23:Dangerous default value UserList() (collections.UserList) as argument:UNDEFINED -dangerous-default-value:109:0:111:16:function24:Dangerous default value [] as argument:UNDEFINED +dangerous-default-value:6:0:6:13:function1:Dangerous default value [] as argument:UNDEFINED +dangerous-default-value:10:0:10:13:function2:Dangerous default value HEHE (builtins.dict) as argument:UNDEFINED +dangerous-default-value:18:0:18:13:function4:Dangerous default value set() (builtins.set) as argument:UNDEFINED +dangerous-default-value:28:0:28:13:function6:Dangerous default value GLOBAL_SET (builtins.set) as argument:UNDEFINED +dangerous-default-value:32:0:32:13:function7:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED +dangerous-default-value:36:0:36:13:function8:Dangerous default value list() (builtins.list) as argument:UNDEFINED +dangerous-default-value:40:0:40:13:function9:Dangerous default value [] as argument:UNDEFINED +dangerous-default-value:44:0:44:14:function10:Dangerous default value {} as argument:UNDEFINED +dangerous-default-value:48:0:48:14:function11:Dangerous default value list() (builtins.list) as argument:UNDEFINED +dangerous-default-value:52:0:52:14:function12:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED +dangerous-default-value:61:0:61:14:function13:Dangerous default value OINK (builtins.dict) as argument:UNDEFINED +dangerous-default-value:65:0:65:14:function14:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED +dangerous-default-value:73:0:73:14:function15:Dangerous default value INVALID_DICT (builtins.dict) as argument:UNDEFINED +dangerous-default-value:77:0:77:14:function16:Dangerous default value set() as argument:UNDEFINED +dangerous-default-value:81:0:81:14:function17:Dangerous default value deque() (collections.deque) as argument:UNDEFINED +dangerous-default-value:85:0:85:14:function18:Dangerous default value ChainMap() (collections.ChainMap) as argument:UNDEFINED +dangerous-default-value:89:0:89:14:function19:Dangerous default value Counter() (collections.Counter) as argument:UNDEFINED +dangerous-default-value:93:0:93:14:function20:Dangerous default value OrderedDict() (collections.OrderedDict) as argument:UNDEFINED +dangerous-default-value:97:0:97:14:function21:Dangerous default value defaultdict() (collections.defaultdict) as argument:UNDEFINED +dangerous-default-value:101:0:101:14:function22:Dangerous default value UserDict() (collections.UserDict) as argument:UNDEFINED +dangerous-default-value:105:0:105:14:function23:Dangerous default value UserList() (collections.UserList) as argument:UNDEFINED +dangerous-default-value:109:0:109:14:function24:Dangerous default value [] as argument:UNDEFINED diff --git a/tests/functional/d/dataclass_typecheck.txt b/tests/functional/d/dataclass_typecheck.txt index e898c85fad..97f5860bb6 100644 --- a/tests/functional/d/dataclass_typecheck.txt +++ b/tests/functional/d/dataclass_typecheck.txt @@ -7,6 +7,6 @@ unsubscriptable-object:56:6:56:15::Value 'obj.attr1' is unsubscriptable:UNDEFINE unsupported-assignment-operation:61:0:61:9::'obj.attr1' does not support item assignment:UNDEFINED unsupported-delete-operation:66:4:66:13::'obj.attr1' does not support item deletion:UNDEFINED not-context-manager:91:0:92:8::Context manager 'str' doesn't implement __enter__ and __exit__.:UNDEFINED -invalid-metaclass:99:0:100:8:Test2:Invalid metaclass 'Instance of builtins.int' used:UNDEFINED +invalid-metaclass:99:0:99:11:Test2:Invalid metaclass 'Instance of builtins.int' used:UNDEFINED unhashable-dict-key:105:0:105:2::Dict key is unhashable:UNDEFINED isinstance-second-argument-not-valid-type:115:6:115:30::Second argument of isinstance is not a type:UNDEFINED diff --git a/tests/functional/d/disable_msg_next_line.txt b/tests/functional/d/disable_msg_next_line.txt index 6601f74377..36ba9527d2 100644 --- a/tests/functional/d/disable_msg_next_line.txt +++ b/tests/functional/d/disable_msg_next_line.txt @@ -1,5 +1,5 @@ invalid-name:15:4:15:5:function_C:"Variable name ""x"" doesn't conform to snake_case naming style":HIGH unused-variable:15:4:15:5:function_C:Unused variable 'x':UNDEFINED f-string-without-interpolation:16:11:16:44:function_C:Using an f-string that does not have any interpolated variables:UNDEFINED -invalid-name:19:0:20:15:function_D:"Function name ""function_D"" doesn't conform to snake_case naming style":HIGH +invalid-name:19:0:19:14:function_D:"Function name ""function_D"" doesn't conform to snake_case naming style":HIGH unused-argument:19:21:19:25:function_D:Unused argument 'arg2':HIGH diff --git a/tests/functional/d/docstrings.txt b/tests/functional/d/docstrings.txt index 31e25bf368..d1e21f7af5 100644 --- a/tests/functional/d/docstrings.txt +++ b/tests/functional/d/docstrings.txt @@ -1,8 +1,8 @@ missing-module-docstring:1:0:None:None::Missing module docstring:HIGH -empty-docstring:6:0:7:10:function0:Empty function docstring:HIGH -missing-function-docstring:10:0:12:16:function1:Missing function or method docstring:HIGH -missing-class-docstring:23:0:53:12:AAAA:Missing class docstring:HIGH -missing-function-docstring:40:4:41:12:AAAA.method1:Missing function or method docstring:INFERENCE -empty-docstring:48:4:50:12:AAAA.method3:Empty method docstring:INFERENCE -empty-docstring:62:4:64:12:DDDD.method2:Empty method docstring:INFERENCE -missing-function-docstring:70:4:71:12:DDDD.method4:Missing function or method docstring:INFERENCE +empty-docstring:6:0:6:13:function0:Empty function docstring:HIGH +missing-function-docstring:10:0:10:13:function1:Missing function or method docstring:HIGH +missing-class-docstring:23:0:23:10:AAAA:Missing class docstring:HIGH +missing-function-docstring:40:4:40:15:AAAA.method1:Missing function or method docstring:INFERENCE +empty-docstring:48:4:48:15:AAAA.method3:Empty method docstring:INFERENCE +empty-docstring:62:4:62:15:DDDD.method2:Empty method docstring:INFERENCE +missing-function-docstring:70:4:70:15:DDDD.method4:Missing function or method docstring:INFERENCE diff --git a/tests/functional/d/dotted_ancestor.txt b/tests/functional/d/dotted_ancestor.txt index 9d45ae6bee..f7c6e1f251 100644 --- a/tests/functional/d/dotted_ancestor.txt +++ b/tests/functional/d/dotted_ancestor.txt @@ -1 +1 @@ -too-few-public-methods:7:0:10:50:Aaaa:Too few public methods (0/2):UNDEFINED +too-few-public-methods:7:0:7:10:Aaaa:Too few public methods (0/2):UNDEFINED diff --git a/tests/functional/d/duplicate_bases.txt b/tests/functional/d/duplicate_bases.txt index d656763207..f52bbfe44f 100644 --- a/tests/functional/d/duplicate_bases.txt +++ b/tests/functional/d/duplicate_bases.txt @@ -1 +1 @@ -duplicate-bases:5:0:6:8:Duplicates:Duplicate bases for class 'Duplicates':UNDEFINED +duplicate-bases:5:0:5:16:Duplicates:Duplicate bases for class 'Duplicates':UNDEFINED diff --git a/tests/functional/ext/docparams/docparams.txt b/tests/functional/ext/docparams/docparams.txt index 1fbad61851..c75a954414 100644 --- a/tests/functional/ext/docparams/docparams.txt +++ b/tests/functional/ext/docparams/docparams.txt @@ -1,12 +1,12 @@ -missing-return-doc:4:0:6:17:_private_func1:Missing return documentation:UNDEFINED -missing-return-type-doc:4:0:6:17:_private_func1:Missing return type documentation:UNDEFINED -missing-yield-doc:9:0:11:16:_private_func2:Missing yield documentation:UNDEFINED -missing-yield-type-doc:9:0:11:16:_private_func2:Missing yield type documentation:UNDEFINED -missing-raises-doc:14:0:16:30:_private_func3:"""Exception"" not documented as being raised":UNDEFINED -missing-any-param-doc:19:0:21:17:public_func1:"Missing any documentation in ""public_func1""":UNDEFINED -missing-return-doc:24:0:26:17:_async_private_func1:Missing return documentation:UNDEFINED -missing-return-type-doc:24:0:26:17:_async_private_func1:Missing return type documentation:UNDEFINED -missing-yield-doc:29:0:31:16:_async_private_func2:Missing yield documentation:UNDEFINED -missing-yield-type-doc:29:0:31:16:_async_private_func2:Missing yield type documentation:UNDEFINED -missing-raises-doc:34:0:36:30:_async_private_func3:"""Exception"" not documented as being raised":UNDEFINED -missing-any-param-doc:39:0:41:17:async_public_func1:"Missing any documentation in ""async_public_func1""":UNDEFINED +missing-return-doc:4:0:4:18:_private_func1:Missing return documentation:UNDEFINED +missing-return-type-doc:4:0:4:18:_private_func1:Missing return type documentation:UNDEFINED +missing-yield-doc:9:0:9:18:_private_func2:Missing yield documentation:UNDEFINED +missing-yield-type-doc:9:0:9:18:_private_func2:Missing yield type documentation:UNDEFINED +missing-raises-doc:14:0:14:18:_private_func3:"""Exception"" not documented as being raised":UNDEFINED +missing-any-param-doc:19:0:19:16:public_func1:"Missing any documentation in ""public_func1""":UNDEFINED +missing-return-doc:24:0:24:30:_async_private_func1:Missing return documentation:UNDEFINED +missing-return-type-doc:24:0:24:30:_async_private_func1:Missing return type documentation:UNDEFINED +missing-yield-doc:29:0:29:30:_async_private_func2:Missing yield documentation:UNDEFINED +missing-yield-type-doc:29:0:29:30:_async_private_func2:Missing yield type documentation:UNDEFINED +missing-raises-doc:34:0:34:30:_async_private_func3:"""Exception"" not documented as being raised":UNDEFINED +missing-any-param-doc:39:0:39:28:async_public_func1:"Missing any documentation in ""async_public_func1""":UNDEFINED diff --git a/tests/functional/ext/docparams/missing_param_doc.txt b/tests/functional/ext/docparams/missing_param_doc.txt index c528212073..c43bdbd7ec 100644 --- a/tests/functional/ext/docparams/missing_param_doc.txt +++ b/tests/functional/ext/docparams/missing_param_doc.txt @@ -1,18 +1,18 @@ -missing-any-param-doc:3:0:6:21:foobar1:"Missing any documentation in ""foobar1""":UNDEFINED -missing-any-param-doc:8:0:13:21:foobar2:"Missing any documentation in ""foobar2""":UNDEFINED -missing-param-doc:15:0:22:27:foobar3:"""arg1, arg2, arg3"" missing in parameter documentation":UNDEFINED -missing-type-doc:15:0:22:27:foobar3:"""arg2"" missing in parameter type documentation":UNDEFINED -missing-param-doc:24:0:31:21:foobar4:"""arg2"" missing in parameter documentation":UNDEFINED -missing-type-doc:24:0:31:21:foobar4:"""arg2"" missing in parameter type documentation":UNDEFINED -missing-param-doc:33:0:41:21:foobar5:"""arg2"" missing in parameter documentation":UNDEFINED -missing-type-doc:33:0:41:21:foobar5:"""arg1"" missing in parameter type documentation":UNDEFINED -missing-param-doc:43:0:51:27:foobar6:"""arg2, arg3"" missing in parameter documentation":UNDEFINED -missing-type-doc:43:0:51:27:foobar6:"""arg3"" missing in parameter type documentation":UNDEFINED -missing-any-param-doc:53:0:59:21:foobar7:"Missing any documentation in ""foobar7""":UNDEFINED -missing-any-param-doc:61:0:64:15:foobar8:"Missing any documentation in ""foobar8""":UNDEFINED -missing-param-doc:66:0:74:27:foobar9:"""arg1, arg2, arg3"" missing in parameter documentation":UNDEFINED -missing-param-doc:76:0:86:27:foobar10:"""arg2"" missing in parameter documentation":UNDEFINED -missing-type-doc:76:0:86:27:foobar10:"""arg1, arg3"" missing in parameter type documentation":UNDEFINED -missing-any-param-doc:88:0:95:21:foobar11:"Missing any documentation in ""foobar11""":UNDEFINED -missing-param-doc:97:0:106:27:foobar12:"""arg1, arg3"" missing in parameter documentation":UNDEFINED -missing-type-doc:97:0:106:27:foobar12:"""arg2, arg3"" missing in parameter type documentation":UNDEFINED +missing-any-param-doc:3:0:3:11:foobar1:"Missing any documentation in ""foobar1""":UNDEFINED +missing-any-param-doc:8:0:8:11:foobar2:"Missing any documentation in ""foobar2""":UNDEFINED +missing-param-doc:15:0:15:11:foobar3:"""arg1, arg2, arg3"" missing in parameter documentation":UNDEFINED +missing-type-doc:15:0:15:11:foobar3:"""arg2"" missing in parameter type documentation":UNDEFINED +missing-param-doc:24:0:24:11:foobar4:"""arg2"" missing in parameter documentation":UNDEFINED +missing-type-doc:24:0:24:11:foobar4:"""arg2"" missing in parameter type documentation":UNDEFINED +missing-param-doc:33:0:33:11:foobar5:"""arg2"" missing in parameter documentation":UNDEFINED +missing-type-doc:33:0:33:11:foobar5:"""arg1"" missing in parameter type documentation":UNDEFINED +missing-param-doc:43:0:43:11:foobar6:"""arg2, arg3"" missing in parameter documentation":UNDEFINED +missing-type-doc:43:0:43:11:foobar6:"""arg3"" missing in parameter type documentation":UNDEFINED +missing-any-param-doc:53:0:53:11:foobar7:"Missing any documentation in ""foobar7""":UNDEFINED +missing-any-param-doc:61:0:61:11:foobar8:"Missing any documentation in ""foobar8""":UNDEFINED +missing-param-doc:66:0:66:11:foobar9:"""arg1, arg2, arg3"" missing in parameter documentation":UNDEFINED +missing-param-doc:76:0:76:12:foobar10:"""arg2"" missing in parameter documentation":UNDEFINED +missing-type-doc:76:0:76:12:foobar10:"""arg1, arg3"" missing in parameter type documentation":UNDEFINED +missing-any-param-doc:88:0:88:12:foobar11:"Missing any documentation in ""foobar11""":UNDEFINED +missing-param-doc:97:0:97:12:foobar12:"""arg1, arg3"" missing in parameter documentation":UNDEFINED +missing-type-doc:97:0:97:12:foobar12:"""arg2, arg3"" missing in parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt index e1672e427c..1db477b902 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required.txt @@ -1,3 +1,3 @@ -missing-any-param-doc:7:0:12:38:test_don_t_tolerate_no_param_documentation_at_all:"Missing any documentation in ""test_don_t_tolerate_no_param_documentation_at_all""":UNDEFINED -missing-param-doc:44:0:51:7:test_kwonlyargs_are_taken_in_account:"""missing_kwonly"" missing in parameter documentation":UNDEFINED -missing-type-doc:44:0:51:7:test_kwonlyargs_are_taken_in_account:"""missing_kwonly"" missing in parameter type documentation":UNDEFINED +missing-any-param-doc:7:0:7:53:test_don_t_tolerate_no_param_documentation_at_all:"Missing any documentation in ""test_don_t_tolerate_no_param_documentation_at_all""":UNDEFINED +missing-param-doc:44:0:44:40:test_kwonlyargs_are_taken_in_account:"""missing_kwonly"" missing in parameter documentation":UNDEFINED +missing-type-doc:44:0:44:40:test_kwonlyargs_are_taken_in_account:"""missing_kwonly"" missing in parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt index 6efbd6b1c5..c0daa6ee21 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.txt @@ -1,26 +1,26 @@ -missing-param-doc:24:0:35:7:test_missing_func_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:24:0:35:7:test_missing_func_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-type-doc:80:0:92:7:test_missing_func_params_with_partial_annotations_in_google_docstring:"""x"" missing in parameter type documentation":UNDEFINED -differing-param-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter documentation":UNDEFINED -differing-type-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter type documentation":UNDEFINED -missing-param-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter documentation":UNDEFINED -missing-type-doc:129:0:142:28:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter type documentation":UNDEFINED -missing-param-doc:146:4:156:11:Foo.test_missing_method_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:146:4:156:11:Foo.test_missing_method_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -differing-param-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter documentation":UNDEFINED -missing-type-doc:177:0:189:22:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED -differing-param-doc:192:0:203:22:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:192:0:203:22:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:219:0:233:12:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:219:0:233:12:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:237:4:246:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:237:4:246:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:249:0:270:11:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:249:0:270:11:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -multiple-constructor-doc:249:0:270:11:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED -missing-param-doc:263:4:270:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:263:4:270:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:273:0:283:24:test_warns_missing_args_google:"""*args"" missing in parameter documentation":UNDEFINED -missing-param-doc:286:0:296:24:test_warns_missing_kwargs_google:"""**kwargs"" missing in parameter documentation":UNDEFINED +missing-param-doc:24:0:24:48:test_missing_func_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:24:0:24:48:test_missing_func_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-type-doc:80:0:80:73:test_missing_func_params_with_partial_annotations_in_google_docstring:"""x"" missing in parameter type documentation":UNDEFINED +differing-param-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter documentation":UNDEFINED +differing-type-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""these"" differing in parameter type documentation":UNDEFINED +missing-param-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter documentation":UNDEFINED +missing-type-doc:129:0:129:65:test_func_params_and_wrong_keyword_params_in_google_docstring:"""that"" missing in parameter type documentation":UNDEFINED +missing-param-doc:146:4:146:54:Foo.test_missing_method_params_in_google_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:146:4:146:54:Foo.test_missing_method_params_in_google_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +differing-param-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter documentation":UNDEFINED +missing-type-doc:177:0:177:58:test_wrong_name_of_func_params_in_google_docstring_one:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED +differing-param-doc:192:0:192:58:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:192:0:192:58:test_wrong_name_of_func_params_in_google_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:219:0:219:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:219:0:219:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:237:4:237:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:237:4:237:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:249:0:249:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:249:0:249:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +multiple-constructor-doc:249:0:249:14:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED +missing-param-doc:263:4:263:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:263:4:263:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:273:0:273:34:test_warns_missing_args_google:"""*args"" missing in parameter documentation":UNDEFINED +missing-param-doc:286:0:286:36:test_warns_missing_kwargs_google:"""**kwargs"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt index d4f8be211d..bd73de9cce 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.txt @@ -1,22 +1,22 @@ -missing-param-doc:9:0:23:7:test_missing_func_params_in_numpy_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:9:0:23:7:test_missing_func_params_in_numpy_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:27:4:39:11:Foo.test_missing_method_params_in_numpy_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:27:4:39:11:Foo.test_missing_method_params_in_numpy_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -differing-param-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg, zarg"" missing in parameter documentation":UNDEFINED -missing-type-doc:66:0:82:22:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED -differing-param-doc:85:0:98:22:test_wrong_name_of_func_params_in_numpy_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:85:0:98:22:test_wrong_name_of_func_params_in_numpy_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:116:0:132:12:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:116:0:132:12:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:156:4:169:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:156:4:169:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:172:0:197:11:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:172:0:197:11:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -multiple-constructor-doc:172:0:197:11:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED -missing-param-doc:188:4:197:11:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:188:4:197:11:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:200:0:214:24:test_warns_missing_args_numpy:"""*args"" missing in parameter documentation":UNDEFINED -missing-param-doc:217:0:231:24:test_warns_missing_kwargs_numpy:"""**kwargs"" missing in parameter documentation":UNDEFINED -missing-type-doc:234:0:256:24:test_finds_args_without_type_numpy:"""untyped_arg"" missing in parameter type documentation":UNDEFINED +missing-param-doc:9:0:9:47:test_missing_func_params_in_numpy_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:9:0:9:47:test_missing_func_params_in_numpy_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:27:4:27:53:Foo.test_missing_method_params_in_numpy_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:27:4:27:53:Foo.test_missing_method_params_in_numpy_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +differing-param-doc:66:0:66:53:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:66:0:66:53:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg1, zarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:66:0:66:53:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg, zarg"" missing in parameter documentation":UNDEFINED +missing-type-doc:66:0:66:53:test_wrong_name_of_func_params_in_numpy_docstring:"""xarg, zarg"" missing in parameter type documentation":UNDEFINED +differing-param-doc:85:0:85:57:test_wrong_name_of_func_params_in_numpy_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:85:0:85:57:test_wrong_name_of_func_params_in_numpy_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:116:0:116:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:116:0:116:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:156:4:156:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:156:4:156:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:172:0:172:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:172:0:172:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +multiple-constructor-doc:172:0:172:14:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED +missing-param-doc:188:4:188:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:188:4:188:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:200:0:200:33:test_warns_missing_args_numpy:"""*args"" missing in parameter documentation":UNDEFINED +missing-param-doc:217:0:217:35:test_warns_missing_kwargs_numpy:"""**kwargs"" missing in parameter documentation":UNDEFINED +missing-type-doc:234:0:234:38:test_finds_args_without_type_numpy:"""untyped_arg"" missing in parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt index 7745f76199..dcc77f48ed 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.txt @@ -1,37 +1,37 @@ -missing-param-doc:8:0:18:8:test_missing_func_params_in_sphinx_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:8:0:18:8:test_missing_func_params_in_sphinx_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:22:4:32:12:Foo.test_missing_method_params_in_sphinx_docstring:"""y"" missing in parameter documentation":UNDEFINED -missing-type-doc:22:4:32:12:Foo.test_missing_method_params_in_sphinx_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED -differing-param-doc:55:0:69:22:test_wrong_name_of_func_params_in_sphinx_docstring:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:55:0:69:22:test_wrong_name_of_func_params_in_sphinx_docstring:"""yarg1, zarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:55:0:69:22:test_wrong_name_of_func_params_in_sphinx_docstring:"""xarg, zarg"" missing in parameter documentation":UNDEFINED -missing-type-doc:55:0:69:22:test_wrong_name_of_func_params_in_sphinx_docstring:"""yarg, zarg"" missing in parameter type documentation":UNDEFINED -differing-param-doc:72:0:83:22:test_wrong_name_of_func_params_in_sphinx_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED -differing-type-doc:72:0:83:22:test_wrong_name_of_func_params_in_sphinx_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED -missing-param-doc:99:0:112:12:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:99:0:112:12:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:116:4:128:12:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:116:4:128:12:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -missing-param-doc:131:0:151:12:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:131:0:151:12:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED -multiple-constructor-doc:131:0:151:12:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED -missing-param-doc:144:4:151:12:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED -missing-type-doc:144:4:151:12:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED -inconsistent-return-statements:154:0:166:24:test_warns_missing_args_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -missing-param-doc:154:0:166:24:test_warns_missing_args_sphinx:"""*args"" missing in parameter documentation":UNDEFINED -inconsistent-return-statements:169:0:181:24:test_warns_missing_kwargs_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -missing-param-doc:169:0:181:24:test_warns_missing_kwargs_sphinx:"""**kwargs"" missing in parameter documentation":UNDEFINED -inconsistent-return-statements:184:0:198:24:test_finds_args_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -missing-param-doc:184:0:198:24:test_finds_args_without_type_sphinx:"""*args"" missing in parameter documentation":UNDEFINED -inconsistent-return-statements:201:0:215:24:test_finds_kwargs_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -missing-param-doc:201:0:215:24:test_finds_kwargs_without_type_sphinx:"""**kwargs"" missing in parameter documentation":UNDEFINED -inconsistent-return-statements:218:0:234:24:test_finds_args_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:237:0:253:24:test_finds_kwargs_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -missing-raises-doc:263:4:268:17:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +missing-param-doc:8:0:8:48:test_missing_func_params_in_sphinx_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:8:0:8:48:test_missing_func_params_in_sphinx_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:22:4:22:54:Foo.test_missing_method_params_in_sphinx_docstring:"""y"" missing in parameter documentation":UNDEFINED +missing-type-doc:22:4:22:54:Foo.test_missing_method_params_in_sphinx_docstring:"""x, y"" missing in parameter type documentation":UNDEFINED +differing-param-doc:55:0:55:54:test_wrong_name_of_func_params_in_sphinx_docstring:"""xarg1, zarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:55:0:55:54:test_wrong_name_of_func_params_in_sphinx_docstring:"""yarg1, zarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:55:0:55:54:test_wrong_name_of_func_params_in_sphinx_docstring:"""xarg, zarg"" missing in parameter documentation":UNDEFINED +missing-type-doc:55:0:55:54:test_wrong_name_of_func_params_in_sphinx_docstring:"""yarg, zarg"" missing in parameter type documentation":UNDEFINED +differing-param-doc:72:0:72:58:test_wrong_name_of_func_params_in_sphinx_docstring_two:"""yarg1"" differing in parameter documentation":UNDEFINED +differing-type-doc:72:0:72:58:test_wrong_name_of_func_params_in_sphinx_docstring_two:"""yarg1"" differing in parameter type documentation":UNDEFINED +missing-param-doc:99:0:99:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:99:0:99:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:116:4:116:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:116:4:116:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +missing-param-doc:131:0:131:14:ClassFoo:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:131:0:131:14:ClassFoo:"""x, y"" missing in parameter type documentation":UNDEFINED +multiple-constructor-doc:131:0:131:14:ClassFoo:"""ClassFoo"" has constructor parameters documented in class and __init__":UNDEFINED +missing-param-doc:144:4:144:16:ClassFoo.__init__:"""x"" missing in parameter documentation":UNDEFINED +missing-type-doc:144:4:144:16:ClassFoo.__init__:"""x, y"" missing in parameter type documentation":UNDEFINED +inconsistent-return-statements:154:0:154:34:test_warns_missing_args_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +missing-param-doc:154:0:154:34:test_warns_missing_args_sphinx:"""*args"" missing in parameter documentation":UNDEFINED +inconsistent-return-statements:169:0:169:36:test_warns_missing_kwargs_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +missing-param-doc:169:0:169:36:test_warns_missing_kwargs_sphinx:"""**kwargs"" missing in parameter documentation":UNDEFINED +inconsistent-return-statements:184:0:184:39:test_finds_args_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +missing-param-doc:184:0:184:39:test_finds_args_without_type_sphinx:"""*args"" missing in parameter documentation":UNDEFINED +inconsistent-return-statements:201:0:201:41:test_finds_kwargs_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +missing-param-doc:201:0:201:41:test_finds_kwargs_without_type_sphinx:"""**kwargs"" missing in parameter documentation":UNDEFINED +inconsistent-return-statements:218:0:218:39:test_finds_args_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:237:0:237:41:test_finds_kwargs_without_type_sphinx:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +missing-raises-doc:263:4:263:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED unreachable:289:8:289:17:Foo.foo:Unreachable code:UNDEFINED -missing-param-doc:292:4:297:30:Foo.foo:"""value"" missing in parameter documentation":UNDEFINED -missing-raises-doc:292:4:297:30:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED -missing-type-doc:292:4:297:30:Foo.foo:"""value"" missing in parameter type documentation":UNDEFINED +missing-param-doc:292:4:292:11:Foo.foo:"""value"" missing in parameter documentation":UNDEFINED +missing-raises-doc:292:4:292:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +missing-type-doc:292:4:292:11:Foo.foo:"""value"" missing in parameter type documentation":UNDEFINED unreachable:328:8:328:17:Foo.foo:Unreachable code:UNDEFINED -useless-param-doc:332:4:346:12:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED -useless-type-doc:332:4:346:12:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_"" useless ignored parameter type documentation":UNDEFINED +useless-param-doc:332:4:332:55:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:332:4:332:55:Foo.test_useless_docs_ignored_argument_names_sphinx:"""_"" useless ignored parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt index 7cebb0a3cd..7b30afcb55 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_check_init.txt @@ -1 +1 @@ -missing-param-doc:10:4:13:11:MyClass.__init__:"""my_param"" missing in parameter documentation":UNDEFINED +missing-param-doc:10:4:10:16:MyClass.__init__:"""my_param"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt index d79aa94dd5..d42bc96251 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_no_doc_rgx_test_all.txt @@ -1 +1 @@ -missing-param-doc:25:4:28:11:MyClass.__init__:"""my_param"" missing in parameter documentation":UNDEFINED +missing-param-doc:25:4:25:16:MyClass.__init__:"""my_param"" missing in parameter documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc.txt b/tests/functional/ext/docparams/raise/missing_raises_doc.txt index 6da4d57be8..7a93e4b1cd 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc.txt @@ -1,4 +1,4 @@ unreachable:25:4:25:25:test_ignores_raise_uninferable:Unreachable code:UNDEFINED -missing-raises-doc:28:0:42:25:test_ignores_returns_from_inner_functions:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:28:0:28:45:test_ignores_returns_from_inner_functions:"""RuntimeError"" not documented as being raised":UNDEFINED unreachable:42:4:42:25:test_ignores_returns_from_inner_functions:Unreachable code:UNDEFINED raising-bad-type:54:4:54:22:test_ignores_returns_use_only_names:Raising int while only classes or instances are allowed:UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt index 0ac9505593..6e98f05d9c 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt @@ -1,14 +1,14 @@ -missing-raises-doc:6:0:13:25:test_find_missing_google_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:6:0:6:35:test_find_missing_google_raises:"""RuntimeError"" not documented as being raised":UNDEFINED unreachable:13:4:13:25:test_find_missing_google_raises:Unreachable code:UNDEFINED -missing-raises-doc:38:0:46:21:test_find_valid_missing_google_attr_raises:"""error"" not documented as being raised":UNDEFINED +missing-raises-doc:38:0:38:46:test_find_valid_missing_google_attr_raises:"""error"" not documented as being raised":UNDEFINED unreachable:83:4:83:25:test_find_all_google_raises:Unreachable code:UNDEFINED unreachable:94:4:94:25:test_find_multiple_google_raises:Unreachable code:UNDEFINED unreachable:95:4:95:30:test_find_multiple_google_raises:Unreachable code:UNDEFINED unreachable:96:4:96:27:test_find_multiple_google_raises:Unreachable code:UNDEFINED -missing-raises-doc:99:0:110:25:test_find_rethrown_google_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -missing-raises-doc:113:0:124:25:test_find_rethrown_google_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED -missing-raises-doc:148:4:158:17:Foo.foo_method:"""AttributeError"" not documented as being raised":UNDEFINED +missing-raises-doc:99:0:99:36:test_find_rethrown_google_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:113:0:113:45:test_find_rethrown_google_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED +missing-raises-doc:148:4:148:18:Foo.foo_method:"""AttributeError"" not documented as being raised":UNDEFINED unreachable:158:8:158:17:Foo.foo_method:Unreachable code:UNDEFINED unreachable:180:8:180:17:Foo.foo_method:Unreachable code:UNDEFINED -missing-raises-doc:183:4:192:28:Foo.foo_method:"""AttributeError"" not documented as being raised":UNDEFINED +missing-raises-doc:183:4:183:18:Foo.foo_method:"""AttributeError"" not documented as being raised":UNDEFINED using-constant-test:190:8:191:34:Foo.foo_method:Using a conditional statement with a constant value:UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt index 8e63c4d9be..91002c02df 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt @@ -1,11 +1,11 @@ -missing-raises-doc:11:0:20:25:test_find_missing_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:11:0:11:34:test_find_missing_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED unreachable:20:4:20:25:test_find_missing_numpy_raises:Unreachable code:UNDEFINED unreachable:34:4:34:25:test_find_all_numpy_raises:Unreachable code:UNDEFINED -missing-raises-doc:37:0:50:25:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -missing-raises-doc:53:0:66:25:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED -missing-raises-doc:111:0:121:21:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":UNDEFINED -missing-raises-doc:146:4:158:17:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +missing-raises-doc:37:0:37:35:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:53:0:53:44:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED +missing-raises-doc:111:0:111:45:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":UNDEFINED +missing-raises-doc:146:4:146:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED unreachable:158:8:158:17:Foo.foo:Unreachable code:UNDEFINED unreachable:182:8:182:17:Foo.foo:Unreachable code:UNDEFINED -missing-raises-doc:185:4:196:28:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED +missing-raises-doc:185:4:185:11:Foo.foo:"""AttributeError"" not documented as being raised":UNDEFINED unreachable:215:8:215:17:Foo.foo:Unreachable code:UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt index 77dbea7080..20c2b4d380 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt @@ -1,13 +1,13 @@ -missing-raises-doc:7:0:13:25:test_find_missing_sphinx_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:7:0:7:35:test_find_missing_sphinx_raises:"""RuntimeError"" not documented as being raised":UNDEFINED unreachable:13:4:13:25:test_find_missing_sphinx_raises:Unreachable code:UNDEFINED unreachable:36:4:36:25:test_find_all_sphinx_raises:Unreachable code:UNDEFINED unreachable:37:4:37:30:test_find_all_sphinx_raises:Unreachable code:UNDEFINED unreachable:38:4:38:27:test_find_all_sphinx_raises:Unreachable code:UNDEFINED unreachable:48:4:48:25:test_find_multiple_sphinx_raises:Unreachable code:UNDEFINED -missing-raises-doc:51:0:61:25:test_finds_rethrown_sphinx_raises:"""RuntimeError"" not documented as being raised":UNDEFINED -missing-raises-doc:64:0:74:25:test_finds_rethrown_sphinx_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED -missing-raises-doc:90:0:97:25:test_find_missing_sphinx_raises_infer_from_instance:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:51:0:51:37:test_finds_rethrown_sphinx_raises:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:64:0:64:46:test_finds_rethrown_sphinx_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":UNDEFINED +missing-raises-doc:90:0:90:55:test_find_missing_sphinx_raises_infer_from_instance:"""RuntimeError"" not documented as being raised":UNDEFINED unreachable:97:4:97:25:test_find_missing_sphinx_raises_infer_from_instance:Unreachable code:UNDEFINED -missing-raises-doc:100:0:110:25:test_find_missing_sphinx_raises_infer_from_function:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:100:0:100:55:test_find_missing_sphinx_raises_infer_from_function:"""RuntimeError"" not documented as being raised":UNDEFINED unreachable:110:4:110:25:test_find_missing_sphinx_raises_infer_from_function:Unreachable code:UNDEFINED -missing-raises-doc:133:0:140:21:test_find_valid_missing_sphinx_attr_raises:"""error"" not documented as being raised":UNDEFINED +missing-raises-doc:133:0:133:46:test_find_valid_missing_sphinx_attr_raises:"""error"" not documented as being raised":UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_required.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_required.txt index 3c9f68e163..f04a2b9fdf 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_required.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_required.txt @@ -1 +1 @@ -missing-raises-doc:6:0:8:28:test_warns_unknown_style:"""RuntimeError"" not documented as being raised":UNDEFINED +missing-raises-doc:6:0:6:28:test_warns_unknown_style:"""RuntimeError"" not documented as being raised":UNDEFINED diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt index e566c8a856..e955a4aec5 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_required_exc_inheritance.txt @@ -1 +1 @@ -missing-raises-doc:12:0:18:25:test_find_missing_raise_for_parent:"""NameError"" not documented as being raised":UNDEFINED +missing-raises-doc:12:0:12:38:test_find_missing_raise_for_parent:"""NameError"" not documented as being raised":UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Google.txt b/tests/functional/ext/docparams/return/missing_return_doc_Google.txt index ea8e81200b..836114036a 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Google.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_Google.txt @@ -1,7 +1,7 @@ -redundant-returns-doc:43:0:49:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:52:0:58:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:61:0:67:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:43:0:43:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:52:0:52:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:61:0:61:11:my_func:Redundant returns documentation:UNDEFINED unreachable:95:8:95:17:Foo.foo_method:Unreachable code:UNDEFINED unreachable:112:8:112:17:Foo.foo_method:Unreachable code:UNDEFINED -useless-param-doc:167:4:175:12:Foo.foo_method:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED -useless-type-doc:167:4:175:12:Foo.foo_method:"""_"" useless ignored parameter type documentation":UNDEFINED +useless-param-doc:167:4:167:18:Foo.foo_method:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:167:4:167:18:Foo.foo_method:"""_"" useless ignored parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt b/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt index d137612e6e..fbcfd1287c 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_Numpy.txt @@ -1,5 +1,5 @@ -redundant-returns-doc:62:0:70:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:73:0:80:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:98:0:106:11:my_func:Redundant returns documentation:UNDEFINED -useless-param-doc:164:4:177:11:Foo.foo:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED -useless-type-doc:164:4:177:11:Foo.foo:"""_"" useless ignored parameter type documentation":UNDEFINED +redundant-returns-doc:62:0:62:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:73:0:73:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:98:0:98:11:my_func:Redundant returns documentation:UNDEFINED +useless-param-doc:164:4:164:11:Foo.foo:"""_, _ignored"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:164:4:164:11:Foo.foo:"""_"" useless ignored parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_Sphinx.txt b/tests/functional/ext/docparams/return/missing_return_doc_Sphinx.txt index 604b5d0955..51d324ba7b 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_Sphinx.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_Sphinx.txt @@ -1,2 +1,2 @@ -redundant-returns-doc:44:0:49:15:my_func:Redundant returns documentation:UNDEFINED -redundant-returns-doc:52:0:57:15:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:44:0:44:11:my_func:Redundant returns documentation:UNDEFINED +redundant-returns-doc:52:0:52:11:my_func:Redundant returns documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required.txt b/tests/functional/ext/docparams/return/missing_return_doc_required.txt index 29042dbaa3..8e15b91a20 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_required.txt @@ -1,2 +1,2 @@ -missing-return-doc:6:0:7:16:warns_no_docstring:Missing return documentation:UNDEFINED -missing-return-type-doc:6:0:7:16:warns_no_docstring:Missing return type documentation:UNDEFINED +missing-return-doc:6:0:6:22:warns_no_docstring:Missing return documentation:UNDEFINED +missing-return-type-doc:6:0:6:22:warns_no_docstring:Missing return type documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt b/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt index aa6775f5f0..dac5ad2808 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Google.txt @@ -1,10 +1,10 @@ -missing-return-type-doc:7:0:13:16:my_func:Missing return type documentation:UNDEFINED -missing-return-doc:16:0:22:16:my_func:Missing return documentation:UNDEFINED -missing-return-doc:25:0:31:16:my_func:Missing return documentation:UNDEFINED -missing-return-type-doc:25:0:31:16:my_func:Missing return type documentation:UNDEFINED -missing-return-doc:34:0:40:29:my_func:Missing return documentation:UNDEFINED -missing-return-type-doc:50:4:57:17:Foo.foo_method:Missing return type documentation:UNDEFINED +missing-return-type-doc:7:0:7:11:my_func:Missing return type documentation:UNDEFINED +missing-return-doc:16:0:16:11:my_func:Missing return documentation:UNDEFINED +missing-return-doc:25:0:25:11:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:25:0:25:11:my_func:Missing return type documentation:UNDEFINED +missing-return-doc:34:0:34:11:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:50:4:50:18:Foo.foo_method:Missing return type documentation:UNDEFINED unreachable:57:8:57:17:Foo.foo_method:Unreachable code:UNDEFINED -missing-return-doc:66:4:74:17:Foo.foo_method:Missing return documentation:UNDEFINED -missing-return-type-doc:66:4:74:17:Foo.foo_method:Missing return type documentation:UNDEFINED +missing-return-doc:66:4:66:18:Foo.foo_method:Missing return documentation:UNDEFINED +missing-return-type-doc:66:4:66:18:Foo.foo_method:Missing return type documentation:UNDEFINED unreachable:74:8:74:17:Foo.foo_method:Unreachable code:UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt b/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt index f3dc18f8bb..61aac4ebb6 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Numpy.txt @@ -1,11 +1,11 @@ -missing-return-doc:7:0:19:16:my_func:Missing return documentation:UNDEFINED -missing-return-doc:22:0:30:16:my_func:Missing return documentation:UNDEFINED -missing-return-type-doc:22:0:30:16:my_func:Missing return type documentation:UNDEFINED -missing-return-doc:33:0:40:29:my_func:Missing return documentation:UNDEFINED -missing-return-type-doc:50:4:59:17:Foo.foo_prop:Missing return type documentation:UNDEFINED +missing-return-doc:7:0:7:11:my_func:Missing return documentation:UNDEFINED +missing-return-doc:22:0:22:11:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:22:0:22:11:my_func:Missing return type documentation:UNDEFINED +missing-return-doc:33:0:33:11:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:50:4:50:16:Foo.foo_prop:Missing return type documentation:UNDEFINED unreachable:59:8:59:17:Foo.foo_prop:Unreachable code:UNDEFINED -missing-return-doc:68:4:78:17:Foo.foo_method:Missing return documentation:UNDEFINED -missing-return-type-doc:68:4:78:17:Foo.foo_method:Missing return type documentation:UNDEFINED +missing-return-doc:68:4:68:18:Foo.foo_method:Missing return documentation:UNDEFINED +missing-return-type-doc:68:4:68:18:Foo.foo_method:Missing return type documentation:UNDEFINED unreachable:78:8:78:17:Foo.foo_method:Unreachable code:UNDEFINED -missing-return-doc:87:4:97:17:Foo.foo_method:Missing return documentation:UNDEFINED +missing-return-doc:87:4:87:18:Foo.foo_method:Missing return documentation:UNDEFINED unreachable:97:8:97:17:Foo.foo_method:Unreachable code:UNDEFINED diff --git a/tests/functional/ext/docparams/return/missing_return_doc_required_Sphinx.txt b/tests/functional/ext/docparams/return/missing_return_doc_required_Sphinx.txt index 0d35b88edd..f9c156e0aa 100644 --- a/tests/functional/ext/docparams/return/missing_return_doc_required_Sphinx.txt +++ b/tests/functional/ext/docparams/return/missing_return_doc_required_Sphinx.txt @@ -1,9 +1,9 @@ -missing-return-type-doc:8:0:13:16:my_func:Missing return type documentation:UNDEFINED -missing-return-doc:24:0:29:16:my_func:Missing return documentation:UNDEFINED -missing-return-doc:32:0:40:16:warn_missing_sphinx_returns:Missing return documentation:UNDEFINED -missing-return-type-doc:32:0:40:16:warn_missing_sphinx_returns:Missing return type documentation:UNDEFINED -missing-return-doc:43:0:48:29:my_func:Missing return documentation:UNDEFINED -missing-return-type-doc:58:4:64:17:Foo.foo:Missing return type documentation:UNDEFINED +missing-return-type-doc:8:0:8:11:my_func:Missing return type documentation:UNDEFINED +missing-return-doc:24:0:24:11:my_func:Missing return documentation:UNDEFINED +missing-return-doc:32:0:32:31:warn_missing_sphinx_returns:Missing return documentation:UNDEFINED +missing-return-type-doc:32:0:32:31:warn_missing_sphinx_returns:Missing return type documentation:UNDEFINED +missing-return-doc:43:0:43:11:my_func:Missing return documentation:UNDEFINED +missing-return-type-doc:58:4:58:11:Foo.foo:Missing return type documentation:UNDEFINED unreachable:64:8:64:17:Foo.foo:Unreachable code:UNDEFINED -missing-return-doc:72:4:79:17:Foo.test_ignores_non_property_return_type_sphinx:Missing return documentation:UNDEFINED -missing-return-type-doc:72:4:79:17:Foo.test_ignores_non_property_return_type_sphinx:Missing return type documentation:UNDEFINED +missing-return-doc:72:4:72:52:Foo.test_ignores_non_property_return_type_sphinx:Missing return documentation:UNDEFINED +missing-return-type-doc:72:4:72:52:Foo.test_ignores_non_property_return_type_sphinx:Missing return type documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/useless_type_doc.txt b/tests/functional/ext/docparams/useless_type_doc.txt index ae879d7f18..3408f18036 100644 --- a/tests/functional/ext/docparams/useless_type_doc.txt +++ b/tests/functional/ext/docparams/useless_type_doc.txt @@ -1,4 +1,4 @@ -useless-param-doc:34:0:47:11:function_useless_doc:"""_some_private_param"" useless ignored parameter documentation":UNDEFINED -useless-type-doc:34:0:47:11:function_useless_doc:"""_some_private_param"" useless ignored parameter type documentation":UNDEFINED -useless-param-doc:67:0:73:13:test_two:"""_new"" useless ignored parameter documentation":UNDEFINED -useless-type-doc:67:0:73:13:test_two:"""_new"" useless ignored parameter type documentation":UNDEFINED +useless-param-doc:34:0:34:24:function_useless_doc:"""_some_private_param"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:34:0:34:24:function_useless_doc:"""_some_private_param"" useless ignored parameter type documentation":UNDEFINED +useless-param-doc:67:0:67:12:test_two:"""_new"" useless ignored parameter documentation":UNDEFINED +useless-type-doc:67:0:67:12:test_two:"""_new"" useless ignored parameter type documentation":UNDEFINED diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_Google.txt b/tests/functional/ext/docparams/yield/missing_yield_doc_Google.txt index febb5f9ba2..8315f89bbc 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_Google.txt +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_Google.txt @@ -1 +1 @@ -redundant-yields-doc:19:0:25:12:my_func:Redundant yields documentation:UNDEFINED +redundant-yields-doc:19:0:19:11:my_func:Redundant yields documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.txt b/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.txt index b855ed84df..1324cb5dc7 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.txt +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_Numpy.txt @@ -1 +1 @@ -redundant-yields-doc:22:0:30:12:my_func:Redundant yields documentation:UNDEFINED +redundant-yields-doc:22:0:22:11:my_func:Redundant yields documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_required.txt b/tests/functional/ext/docparams/yield/missing_yield_doc_required.txt index 16a4f535c1..d9162494e4 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_required.txt +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_required.txt @@ -1,2 +1,2 @@ -missing-yield-doc:6:0:7:15:my_func:Missing yield documentation:UNDEFINED -missing-yield-type-doc:6:0:7:15:my_func:Missing yield type documentation:UNDEFINED +missing-yield-doc:6:0:6:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-type-doc:6:0:6:11:my_func:Missing yield type documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_required_Google.txt b/tests/functional/ext/docparams/yield/missing_yield_doc_required_Google.txt index e1d8087187..0a655a744d 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_required_Google.txt +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_required_Google.txt @@ -1,5 +1,5 @@ -missing-yield-doc:34:0:40:28:my_func:Missing yield documentation:UNDEFINED -missing-yield-type-doc:43:0:49:15:my_func:Missing yield type documentation:UNDEFINED -missing-yield-doc:52:0:58:15:my_func:Missing yield documentation:UNDEFINED -missing-yield-doc:61:0:67:15:my_func:Missing yield documentation:UNDEFINED -missing-yield-type-doc:61:0:67:15:my_func:Missing yield type documentation:UNDEFINED +missing-yield-doc:34:0:34:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-type-doc:43:0:43:11:my_func:Missing yield type documentation:UNDEFINED +missing-yield-doc:52:0:52:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-doc:61:0:61:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-type-doc:61:0:61:11:my_func:Missing yield type documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_required_Numpy.txt b/tests/functional/ext/docparams/yield/missing_yield_doc_required_Numpy.txt index 26e5989f46..7ca1f80b0a 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_required_Numpy.txt +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_required_Numpy.txt @@ -1,3 +1,3 @@ -missing-yield-doc:40:0:47:28:my_func:Missing yield documentation:UNDEFINED -missing-yield-doc:50:0:58:15:my_func:Missing yield documentation:UNDEFINED -missing-yield-type-doc:50:0:58:15:my_func:Missing yield type documentation:UNDEFINED +missing-yield-doc:40:0:40:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-doc:50:0:50:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-type-doc:50:0:50:11:my_func:Missing yield type documentation:UNDEFINED diff --git a/tests/functional/ext/docparams/yield/missing_yield_doc_required_Sphinx.txt b/tests/functional/ext/docparams/yield/missing_yield_doc_required_Sphinx.txt index ca4744af2a..3b1931e017 100644 --- a/tests/functional/ext/docparams/yield/missing_yield_doc_required_Sphinx.txt +++ b/tests/functional/ext/docparams/yield/missing_yield_doc_required_Sphinx.txt @@ -1,5 +1,5 @@ -missing-yield-doc:35:0:40:28:my_func:Missing yield documentation:UNDEFINED -missing-yield-type-doc:43:0:48:15:my_func:Missing yield type documentation:UNDEFINED -missing-yield-doc:51:0:56:15:my_func:Missing yield documentation:UNDEFINED -missing-yield-doc:59:0:65:15:my_func:Missing yield documentation:UNDEFINED -missing-yield-type-doc:59:0:65:15:my_func:Missing yield type documentation:UNDEFINED +missing-yield-doc:35:0:35:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-type-doc:43:0:43:11:my_func:Missing yield type documentation:UNDEFINED +missing-yield-doc:51:0:51:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-doc:59:0:59:11:my_func:Missing yield documentation:UNDEFINED +missing-yield-type-doc:59:0:59:11:my_func:Missing yield type documentation:UNDEFINED diff --git a/tests/functional/ext/docstyle/docstyle_first_line_empty.txt b/tests/functional/ext/docstyle/docstyle_first_line_empty.txt index 3d1e7aa6a6..6113fffaa7 100644 --- a/tests/functional/ext/docstyle/docstyle_first_line_empty.txt +++ b/tests/functional/ext/docstyle/docstyle_first_line_empty.txt @@ -1,3 +1,3 @@ -docstring-first-line-empty:4:0:7:19:check_messages:First line empty in function docstring:HIGH -docstring-first-line-empty:14:0:22:11:FFFF:First line empty in class docstring:HIGH -docstring-first-line-empty:19:4:22:11:FFFF.method1:First line empty in method docstring:HIGH +docstring-first-line-empty:4:0:4:18:check_messages:First line empty in function docstring:HIGH +docstring-first-line-empty:14:0:14:10:FFFF:First line empty in class docstring:HIGH +docstring-first-line-empty:19:4:19:15:FFFF.method1:First line empty in method docstring:HIGH diff --git a/tests/functional/ext/docstyle/docstyle_quotes_py38.txt b/tests/functional/ext/docstyle/docstyle_quotes_py38.txt index a158059989..a83c3ab282 100644 --- a/tests/functional/ext/docstyle/docstyle_quotes_py38.txt +++ b/tests/functional/ext/docstyle/docstyle_quotes_py38.txt @@ -1,4 +1,4 @@ -bad-docstring-quotes:6:4:9:11:FFFF.method1:"Bad docstring quotes in method, expected """""", given '''":HIGH -bad-docstring-quotes:11:4:12:25:FFFF.method2:"Bad docstring quotes in method, expected """""", given """:HIGH -bad-docstring-quotes:14:4:15:25:FFFF.method3:"Bad docstring quotes in method, expected """""", given '":HIGH -bad-docstring-quotes:17:4:18:30:FFFF.method4:"Bad docstring quotes in method, expected """""", given '":HIGH +bad-docstring-quotes:6:4:6:15:FFFF.method1:"Bad docstring quotes in method, expected """""", given '''":HIGH +bad-docstring-quotes:11:4:11:15:FFFF.method2:"Bad docstring quotes in method, expected """""", given """:HIGH +bad-docstring-quotes:14:4:14:15:FFFF.method3:"Bad docstring quotes in method, expected """""", given '":HIGH +bad-docstring-quotes:17:4:17:15:FFFF.method4:"Bad docstring quotes in method, expected """""", given '":HIGH diff --git a/tests/functional/ext/eq_without_hash/eq_without_hash.txt b/tests/functional/ext/eq_without_hash/eq_without_hash.txt index b7e2dc46da..e867a5ab38 100644 --- a/tests/functional/ext/eq_without_hash/eq_without_hash.txt +++ b/tests/functional/ext/eq_without_hash/eq_without_hash.txt @@ -1 +1 @@ -eq-without-hash:6:0:11:62:AClass:Implementing __eq__ without also implementing __hash__:HIGH +eq-without-hash:6:0:6:12:AClass:Implementing __eq__ without also implementing __hash__:HIGH diff --git a/tests/functional/ext/mccabe/mccabe.txt b/tests/functional/ext/mccabe/mccabe.txt index 9f7663e696..d031943bbd 100644 --- a/tests/functional/ext/mccabe/mccabe.txt +++ b/tests/functional/ext/mccabe/mccabe.txt @@ -1,15 +1,15 @@ -too-complex:9:0:11:8:f1:'f1' is too complex. The McCabe rating is 1:HIGH -too-complex:14:0:18:12:f2:'f2' is too complex. The McCabe rating is 1:HIGH -too-complex:21:0:28:47:f3:'f3' is too complex. The McCabe rating is 3:HIGH -too-complex:31:0:34:16:f4:'f4' is too complex. The McCabe rating is 2:HIGH -too-complex:37:0:42:19:f5:'f5' is too complex. The McCabe rating is 2:HIGH -too-complex:45:0:50:16:f6:'f6' is too complex. The McCabe rating is 2:HIGH -too-complex:53:0:65:7:f7:'f7' is too complex. The McCabe rating is 3:HIGH -too-complex:68:0:77:16:f8:'f8' is too complex. The McCabe rating is 4:HIGH -too-complex:80:0:103:25:f9:'f9' is too complex. The McCabe rating is 9:HIGH -too-complex:106:0:130:16:f10:'f10' is too complex. The McCabe rating is 11:HIGH -too-complex:138:4:140:12:MyClass1.method1:'method1' is too complex. The McCabe rating is 1:HIGH -too-complex:142:4:195:21:MyClass1.method2:'method2' is too complex. The McCabe rating is 18:HIGH -too-many-branches:142:4:195:21:MyClass1.method2:Too many branches (20/12):UNDEFINED +too-complex:9:0:9:6:f1:'f1' is too complex. The McCabe rating is 1:HIGH +too-complex:14:0:14:6:f2:'f2' is too complex. The McCabe rating is 1:HIGH +too-complex:21:0:21:6:f3:'f3' is too complex. The McCabe rating is 3:HIGH +too-complex:31:0:31:6:f4:'f4' is too complex. The McCabe rating is 2:HIGH +too-complex:37:0:37:6:f5:'f5' is too complex. The McCabe rating is 2:HIGH +too-complex:45:0:45:6:f6:'f6' is too complex. The McCabe rating is 2:HIGH +too-complex:53:0:53:6:f7:'f7' is too complex. The McCabe rating is 3:HIGH +too-complex:68:0:68:6:f8:'f8' is too complex. The McCabe rating is 4:HIGH +too-complex:80:0:80:6:f9:'f9' is too complex. The McCabe rating is 9:HIGH +too-complex:106:0:106:7:f10:'f10' is too complex. The McCabe rating is 11:HIGH +too-complex:138:4:138:15:MyClass1.method1:'method1' is too complex. The McCabe rating is 1:HIGH +too-complex:142:4:142:15:MyClass1.method2:'method2' is too complex. The McCabe rating is 18:HIGH +too-many-branches:142:4:142:15:MyClass1.method2:Too many branches (20/12):UNDEFINED too-complex:198:0:204:15::This 'for' is too complex. The McCabe rating is 4:HIGH -too-complex:207:0:216:15:method3:'method3' is too complex. The McCabe rating is 2:HIGH +too-complex:207:0:207:11:method3:'method3' is too complex. The McCabe rating is 2:HIGH diff --git a/tests/functional/f/first_arg.txt b/tests/functional/f/first_arg.txt index 8517a8ad8a..e75743b33e 100644 --- a/tests/functional/f/first_arg.txt +++ b/tests/functional/f/first_arg.txt @@ -1,9 +1,9 @@ -bad-classmethod-argument:10:4:11:12:Obj.__new__:Class method __new__ should have 'cls' as first argument:UNDEFINED +bad-classmethod-argument:10:4:10:15:Obj.__new__:Class method __new__ should have 'cls' as first argument:UNDEFINED no-classmethod-decorator:16:4:16:10:Obj:Consider using a decorator instead of calling classmethod:UNDEFINED -bad-classmethod-argument:18:4:19:12:Obj.class2:Class method class2 should have 'cls' as first argument:UNDEFINED +bad-classmethod-argument:18:4:18:14:Obj.class2:Class method class2 should have 'cls' as first argument:UNDEFINED no-classmethod-decorator:20:4:20:10:Obj:Consider using a decorator instead of calling classmethod:UNDEFINED -bad-mcs-classmethod-argument:25:4:26:12:Meta.__new__:Metaclass class method __new__ should have 'cls' as first argument:UNDEFINED -bad-mcs-method-argument:32:4:33:12:Meta.method2:Metaclass method method2 should have 'cls' as first argument:UNDEFINED +bad-mcs-classmethod-argument:25:4:25:15:Meta.__new__:Metaclass class method __new__ should have 'cls' as first argument:UNDEFINED +bad-mcs-method-argument:32:4:32:15:Meta.method2:Metaclass method method2 should have 'cls' as first argument:UNDEFINED no-classmethod-decorator:38:4:38:10:Meta:Consider using a decorator instead of calling classmethod:UNDEFINED -bad-mcs-classmethod-argument:40:4:41:12:Meta.class2:Metaclass class method class2 should have 'cls' as first argument:UNDEFINED +bad-mcs-classmethod-argument:40:4:40:14:Meta.class2:Metaclass class method class2 should have 'cls' as first argument:UNDEFINED no-classmethod-decorator:42:4:42:10:Meta:Consider using a decorator instead of calling classmethod:UNDEFINED diff --git a/tests/functional/f/function_redefined.txt b/tests/functional/f/function_redefined.txt index 2a538f66aa..724fe13ca1 100644 --- a/tests/functional/f/function_redefined.txt +++ b/tests/functional/f/function_redefined.txt @@ -1,7 +1,7 @@ -function-redefined:18:4:19:23:AAAA.method2:method already defined line 15:UNDEFINED -function-redefined:21:0:28:17:AAAA:class already defined line 8:UNDEFINED -function-redefined:35:0:38:23:func2:function already defined line 32:UNDEFINED +function-redefined:18:4:18:15:AAAA.method2:method already defined line 15:UNDEFINED +function-redefined:21:0:21:10:AAAA:class already defined line 8:UNDEFINED +function-redefined:35:0:35:9:func2:function already defined line 32:UNDEFINED redefined-outer-name:37:4:37:16:func2:Redefining name '__revision__' from outer scope (line 7):UNDEFINED -function-redefined:54:4:55:51:exclusive_func2:function already defined line 48:UNDEFINED -function-redefined:89:0:90:8:ceil:function already defined line 88:UNDEFINED -function-redefined:93:0:94:8:math:function already defined line 92:UNDEFINED +function-redefined:54:4:54:23:exclusive_func2:function already defined line 48:UNDEFINED +function-redefined:89:0:89:8:ceil:function already defined line 88:UNDEFINED +function-redefined:93:0:93:8:math:function already defined line 92:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_collections.txt b/tests/functional/g/generic_alias/generic_alias_collections.txt index 6d3914a78b..663b81abb2 100644 --- a/tests/functional/g/generic_alias/generic_alias_collections.txt +++ b/tests/functional/g/generic_alias/generic_alias_collections.txt @@ -1,16 +1,16 @@ unsubscriptable-object:66:0:66:24::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:67:0:67:21::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED -abstract-method:74:0:75:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:77:0:78:8:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:80:0:81:8:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED -abstract-method:80:0:81:8:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:80:0:81:8:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:99:0:100:8:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:99:0:100:8:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:104:0:105:8:CustomAbstractCls2:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:104:0:105:8:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:106:0:107:8:CustomImplementation:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:106:0:107:8:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:74:0:74:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:77:0:77:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:80:0:80:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED +abstract-method:80:0:80:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:80:0:80:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:99:0:99:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:99:0:99:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:104:0:104:24:CustomAbstractCls2:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:104:0:104:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:106:0:106:26:CustomImplementation:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:106:0:106:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:125:9:125:12::Value 'int' is unsubscriptable:UNDEFINED unsubscriptable-object:126:15:126:39::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:127:12:127:33::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37.txt b/tests/functional/g/generic_alias/generic_alias_collections_py37.txt index cd4b30b6f6..84a217d2fc 100644 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37.txt +++ b/tests/functional/g/generic_alias/generic_alias_collections_py37.txt @@ -39,7 +39,7 @@ unsubscriptable-object:63:0:63:8::Value 're.Match' is unsubscriptable:UNDEFINED unsubscriptable-object:69:0:69:24::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:70:0:70:21::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED unsubscriptable-object:73:0:73:26::Value 'collections.abc.ByteString' is unsubscriptable:UNDEFINED -abstract-method:77:0:78:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:77:0:77:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED unsubscriptable-object:80:22:80:46:DerivedIterable:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED unsubscriptable-object:83:24:83:50:DerivedCollection:Value 'collections.abc.Collection' is unsubscriptable:UNDEFINED unsubscriptable-object:88:18:88:22:DerivedList:Value 'list' is unsubscriptable:UNDEFINED @@ -47,11 +47,11 @@ unsubscriptable-object:91:17:91:20:DerivedSet:Value 'set' is unsubscriptable:UND unsubscriptable-object:94:25:94:48:DerivedOrderedDict:Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED unsubscriptable-object:97:31:97:55:DerivedListIterable:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED unsubscriptable-object:97:26:97:30:DerivedListIterable:Value 'list' is unsubscriptable:UNDEFINED -abstract-method:102:0:103:8:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:102:0:103:8:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:107:0:108:8:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:102:0:102:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:102:0:102:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:107:0:107:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:107:48:107:72:CustomAbstractCls2:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -abstract-method:109:0:110:8:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:109:0:109:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:114:11:114:16::Value 'tuple' is unsubscriptable:UNDEFINED unsubscriptable-object:115:10:115:14::Value 'dict' is unsubscriptable:UNDEFINED unsubscriptable-object:116:17:116:40::Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.txt b/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.txt index 2d52319ada..ee1407bdb6 100644 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.txt +++ b/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.txt @@ -39,7 +39,7 @@ unsubscriptable-object:65:0:65:8::Value 're.Match' is unsubscriptable:UNDEFINED unsubscriptable-object:71:0:71:24::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:72:0:72:21::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED unsubscriptable-object:75:0:75:26::Value 'collections.abc.ByteString' is unsubscriptable:UNDEFINED -abstract-method:79:0:80:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:79:0:79:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED unsubscriptable-object:82:22:82:46:DerivedIterable:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED unsubscriptable-object:85:24:85:50:DerivedCollection:Value 'collections.abc.Collection' is unsubscriptable:UNDEFINED unsubscriptable-object:90:18:90:22:DerivedList:Value 'list' is unsubscriptable:UNDEFINED @@ -47,11 +47,11 @@ unsubscriptable-object:93:17:93:20:DerivedSet:Value 'set' is unsubscriptable:UND unsubscriptable-object:96:25:96:48:DerivedOrderedDict:Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED unsubscriptable-object:99:31:99:55:DerivedListIterable:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED unsubscriptable-object:99:26:99:30:DerivedListIterable:Value 'list' is unsubscriptable:UNDEFINED -abstract-method:104:0:105:8:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:104:0:105:8:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:109:0:110:8:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:104:0:104:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:104:0:104:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:109:0:109:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:109:48:109:72:CustomAbstractCls2:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -abstract-method:111:0:112:8:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:111:0:111:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:116:11:116:16::Value 'tuple' is unsubscriptable:UNDEFINED unsubscriptable-object:117:10:117:14::Value 'dict' is unsubscriptable:UNDEFINED unsubscriptable-object:118:17:118:40::Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt b/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt index c42ad40062..2bafe20ed8 100644 --- a/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt +++ b/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt @@ -1,5 +1,5 @@ -abstract-method:34:0:35:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:37:0:38:8:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:40:0:41:8:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED -abstract-method:40:0:41:8:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:40:0:41:8:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:34:0:34:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:37:0:37:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:40:0:40:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED +abstract-method:40:0:40:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:40:0:40:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_mixed_py39.txt b/tests/functional/g/generic_alias/generic_alias_mixed_py39.txt index fd5746a70c..06dbdc1976 100644 --- a/tests/functional/g/generic_alias/generic_alias_mixed_py39.txt +++ b/tests/functional/g/generic_alias/generic_alias_mixed_py39.txt @@ -1,5 +1,5 @@ -abstract-method:29:0:30:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:32:0:33:8:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:35:0:36:8:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED -abstract-method:35:0:36:8:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:35:0:36:8:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:29:0:29:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:32:0:32:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:35:0:35:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED +abstract-method:35:0:35:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:35:0:35:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.txt b/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.txt index 266644535c..56eca5192b 100644 --- a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.txt +++ b/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.txt @@ -39,7 +39,7 @@ unsubscriptable-object:68:0:68:8::Value 're.Match' is unsubscriptable:UNDEFINED unsubscriptable-object:74:0:74:24::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:75:0:75:21::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED unsubscriptable-object:78:0:78:26::Value 'collections.abc.ByteString' is unsubscriptable:UNDEFINED -abstract-method:82:0:83:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:82:0:82:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED unsubscriptable-object:85:22:85:46:DerivedIterable:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED unsubscriptable-object:88:24:88:50:DerivedCollection:Value 'collections.abc.Collection' is unsubscriptable:UNDEFINED unsubscriptable-object:93:18:93:22:DerivedList:Value 'list' is unsubscriptable:UNDEFINED @@ -47,10 +47,10 @@ unsubscriptable-object:96:17:96:20:DerivedSet:Value 'set' is unsubscriptable:UND unsubscriptable-object:99:25:99:48:DerivedOrderedDict:Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED unsubscriptable-object:102:31:102:55:DerivedListIterable:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED unsubscriptable-object:102:26:102:30:DerivedListIterable:Value 'list' is unsubscriptable:UNDEFINED -abstract-method:107:0:108:8:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:107:0:108:8:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:112:0:113:8:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:107:0:107:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:107:0:107:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:112:0:112:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:112:48:112:72:CustomAbstractCls2:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -abstract-method:114:0:115:8:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:114:0:114:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED unsubscriptable-object:175:19:175:43::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:176:16:176:37::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_related.txt b/tests/functional/g/generic_alias/generic_alias_related.txt index 2d65b62137..d13f75fa76 100644 --- a/tests/functional/g/generic_alias/generic_alias_related.txt +++ b/tests/functional/g/generic_alias/generic_alias_related.txt @@ -2,4 +2,4 @@ unsubscriptable-object:34:0:34:20::Value 'ClsUnsubscriptable()' is unsubscriptab unsubscriptable-object:35:0:35:18::Value 'ClsUnsubscriptable' is unsubscriptable:UNDEFINED unsubscriptable-object:38:0:38:10::Value 'ClsGetItem' is unsubscriptable:UNDEFINED unsubscriptable-object:40:0:40:17::Value 'ClsClassGetItem()' is unsubscriptable:UNDEFINED -abstract-method:53:0:54:8:Derived:Method 'abstract_method' is abstract in class 'ClsAbstract' but is not overridden:UNDEFINED +abstract-method:53:0:53:13:Derived:Method 'abstract_method' is abstract in class 'ClsAbstract' but is not overridden:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_related_py39.txt b/tests/functional/g/generic_alias/generic_alias_related_py39.txt index 295288f04d..114376f5ef 100644 --- a/tests/functional/g/generic_alias/generic_alias_related_py39.txt +++ b/tests/functional/g/generic_alias/generic_alias_related_py39.txt @@ -2,4 +2,4 @@ unsubscriptable-object:36:0:36:20::Value 'ClsUnsubscriptable()' is unsubscriptab unsubscriptable-object:37:0:37:18::Value 'ClsUnsubscriptable' is unsubscriptable:UNDEFINED unsubscriptable-object:40:0:40:10::Value 'ClsGetItem' is unsubscriptable:UNDEFINED unsubscriptable-object:42:0:42:17::Value 'ClsClassGetItem()' is unsubscriptable:UNDEFINED -abstract-method:55:0:56:8:Derived:Method 'abstract_method' is abstract in class 'ClsAbstract' but is not overridden:UNDEFINED +abstract-method:55:0:55:13:Derived:Method 'abstract_method' is abstract in class 'ClsAbstract' but is not overridden:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_side_effects.txt b/tests/functional/g/generic_alias/generic_alias_side_effects.txt index 3e7548da61..b22f185ea8 100644 --- a/tests/functional/g/generic_alias/generic_alias_side_effects.txt +++ b/tests/functional/g/generic_alias/generic_alias_side_effects.txt @@ -1,8 +1,8 @@ -dangerous-default-value:19:0:21:16:function4:Dangerous default value set() (builtins.set) as argument:UNDEFINED -dangerous-default-value:27:0:29:16:function7:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED -dangerous-default-value:31:0:33:16:function8:Dangerous default value list() (builtins.list) as argument:UNDEFINED -dangerous-default-value:35:0:37:16:function17:Dangerous default value deque() (collections.deque) as argument:UNDEFINED -dangerous-default-value:39:0:41:16:function18:Dangerous default value ChainMap() (collections.ChainMap) as argument:UNDEFINED -dangerous-default-value:43:0:45:16:function19:Dangerous default value Counter() (collections.Counter) as argument:UNDEFINED -dangerous-default-value:47:0:49:16:function20:Dangerous default value OrderedDict() (collections.OrderedDict) as argument:UNDEFINED -dangerous-default-value:51:0:53:16:function21:Dangerous default value defaultdict() (collections.defaultdict) as argument:UNDEFINED +dangerous-default-value:19:0:19:13:function4:Dangerous default value set() (builtins.set) as argument:UNDEFINED +dangerous-default-value:27:0:27:13:function7:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED +dangerous-default-value:31:0:31:13:function8:Dangerous default value list() (builtins.list) as argument:UNDEFINED +dangerous-default-value:35:0:35:14:function17:Dangerous default value deque() (collections.deque) as argument:UNDEFINED +dangerous-default-value:39:0:39:14:function18:Dangerous default value ChainMap() (collections.ChainMap) as argument:UNDEFINED +dangerous-default-value:43:0:43:14:function19:Dangerous default value Counter() (collections.Counter) as argument:UNDEFINED +dangerous-default-value:47:0:47:14:function20:Dangerous default value OrderedDict() (collections.OrderedDict) as argument:UNDEFINED +dangerous-default-value:51:0:51:14:function21:Dangerous default value defaultdict() (collections.defaultdict) as argument:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_typing.txt b/tests/functional/g/generic_alias/generic_alias_typing.txt index b5cb449b88..8ed10fe10b 100644 --- a/tests/functional/g/generic_alias/generic_alias_typing.txt +++ b/tests/functional/g/generic_alias/generic_alias_typing.txt @@ -1,18 +1,18 @@ unsubscriptable-object:66:0:66:17::Value 'typing.ByteString' is unsubscriptable:UNDEFINED unsubscriptable-object:67:0:67:15::Value 'typing.Hashable' is unsubscriptable:UNDEFINED unsubscriptable-object:68:0:68:12::Value 'typing.Sized' is unsubscriptable:UNDEFINED -abstract-method:72:0:73:8:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:75:0:76:8:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:78:0:79:8:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED -abstract-method:78:0:79:8:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:78:0:79:8:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:100:0:101:8:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED -abstract-method:100:0:101:8:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:105:0:106:8:CustomAbstractCls2:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:105:0:106:8:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:107:0:108:8:CustomImplementation:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED -abstract-method:107:0:108:8:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED -abstract-method:118:0:119:8:DerivedIterable2:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:72:0:72:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:75:0:75:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:78:0:78:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden:UNDEFINED +abstract-method:78:0:78:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:78:0:78:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:100:0:100:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden:UNDEFINED +abstract-method:100:0:100:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:105:0:105:24:CustomAbstractCls2:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:105:0:105:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:107:0:107:26:CustomImplementation:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED +abstract-method:107:0:107:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden:UNDEFINED +abstract-method:118:0:118:22:DerivedIterable2:Method '__iter__' is abstract in class 'Iterable' but is not overridden:UNDEFINED unsubscriptable-object:138:9:138:12::Value 'int' is unsubscriptable:UNDEFINED unsubscriptable-object:139:17:139:34::Value 'typing.ByteString' is unsubscriptable:UNDEFINED unsubscriptable-object:140:15:140:30::Value 'typing.Hashable' is unsubscriptable:UNDEFINED diff --git a/tests/functional/i/inconsistent/inconsistent_mro.txt b/tests/functional/i/inconsistent/inconsistent_mro.txt index 8760dfae6f..ea5ca14233 100644 --- a/tests/functional/i/inconsistent/inconsistent_mro.txt +++ b/tests/functional/i/inconsistent/inconsistent_mro.txt @@ -1 +1 @@ -inconsistent-mro:8:0:9:8:Inconsistent:Inconsistent method resolution order for class 'Inconsistent':UNDEFINED +inconsistent-mro:8:0:8:18:Inconsistent:Inconsistent method resolution order for class 'Inconsistent':UNDEFINED diff --git a/tests/functional/i/inconsistent/inconsistent_returns.txt b/tests/functional/i/inconsistent/inconsistent_returns.txt index cf67b13ea2..8e67a83f8f 100644 --- a/tests/functional/i/inconsistent/inconsistent_returns.txt +++ b/tests/functional/i/inconsistent/inconsistent_returns.txt @@ -1,17 +1,17 @@ -inconsistent-return-statements:160:0:162:29:explicit_implicit_returns:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:164:0:167:25:empty_explicit_returns:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:169:0:175:19:explicit_implicit_returns2:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:177:0:183:23:explicit_implicit_returns3:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:185:0:193:16:returns_missing_in_catched_exceptions:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:195:0:200:22:complex_func:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:203:4:208:26:inconsistent_returns_in_nested_function.not_consistent_returns_inner:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:211:0:215:22:bug_1771_counter_example:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:229:0:235:24:bug_1772_counter_example:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:237:0:244:14:bug_1794_inner_func_in_if_counter_example_1:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:246:0:253:14:bug_1794_inner_func_in_if_counter_example_2:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:255:0:265:18:bug_1794_inner_func_in_if_counter_example_3:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:262:8:265:18:bug_1794_inner_func_in_if_counter_example_3._inner2:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:267:0:275:12:bug_3468:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:277:0:289:13:bug_3468_variant:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:322:0:329:20:bug_pylint_3873_1:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:349:0:355:15:bug_pylint_4019_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:160:0:160:29:explicit_implicit_returns:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:164:0:164:26:empty_explicit_returns:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:169:0:169:30:explicit_implicit_returns2:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:177:0:177:30:explicit_implicit_returns3:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:185:0:185:41:returns_missing_in_catched_exceptions:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:195:0:195:16:complex_func:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:203:4:203:36:inconsistent_returns_in_nested_function.not_consistent_returns_inner:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:211:0:211:28:bug_1771_counter_example:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:229:0:229:28:bug_1772_counter_example:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:237:0:237:47:bug_1794_inner_func_in_if_counter_example_1:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:246:0:246:47:bug_1794_inner_func_in_if_counter_example_2:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:255:0:255:47:bug_1794_inner_func_in_if_counter_example_3:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:262:8:262:19:bug_1794_inner_func_in_if_counter_example_3._inner2:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:267:0:267:12:bug_3468:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:277:0:277:20:bug_3468_variant:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:322:0:322:21:bug_pylint_3873_1:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:349:0:349:25:bug_pylint_4019_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED diff --git a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt index 8e36f5b59d..0e3ca50745 100644 --- a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt +++ b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt @@ -1 +1 @@ -inconsistent-return-statements:32:0:42:44:bug_pylint_4122_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:32:0:32:25:bug_pylint_4122_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED diff --git a/tests/functional/i/inherit_non_class.txt b/tests/functional/i/inherit_non_class.txt index da0f55df72..4f90838329 100644 --- a/tests/functional/i/inherit_non_class.txt +++ b/tests/functional/i/inherit_non_class.txt @@ -1,11 +1,11 @@ -inherit-non-class:21:0:22:40:Bad:Inheriting '1', which is not a class.:UNDEFINED -inherit-non-class:24:0:25:38:Bad1:"Inheriting 'lambda abc: 42', which is not a class.":UNDEFINED -inherit-non-class:27:0:28:53:Bad2:Inheriting 'object()', which is not a class.:UNDEFINED -inherit-non-class:30:0:31:40:Bad3:Inheriting 'return_class', which is not a class.:UNDEFINED -inherit-non-class:33:0:34:40:Bad4:Inheriting 'Empty()', which is not a class.:UNDEFINED -inherit-non-class:68:0:69:8:NotInheritableBool:Inheriting 'bool', which is not a class.:UNDEFINED -inherit-non-class:72:0:73:8:NotInheritableRange:Inheriting 'range', which is not a class.:UNDEFINED -inherit-non-class:76:0:77:8:NotInheritableSlice:Inheriting 'slice', which is not a class.:UNDEFINED -inherit-non-class:80:0:81:8:NotInheritableMemoryView:Inheriting 'memoryview', which is not a class.:UNDEFINED -inherit-non-class:99:0:100:8:Child2:Inheriting 'ParentBad[int]', which is not a class.:UNDEFINED +inherit-non-class:21:0:21:9:Bad:Inheriting '1', which is not a class.:UNDEFINED +inherit-non-class:24:0:24:10:Bad1:"Inheriting 'lambda abc: 42', which is not a class.":UNDEFINED +inherit-non-class:27:0:27:10:Bad2:Inheriting 'object()', which is not a class.:UNDEFINED +inherit-non-class:30:0:30:10:Bad3:Inheriting 'return_class', which is not a class.:UNDEFINED +inherit-non-class:33:0:33:10:Bad4:Inheriting 'Empty()', which is not a class.:UNDEFINED +inherit-non-class:68:0:68:24:NotInheritableBool:Inheriting 'bool', which is not a class.:UNDEFINED +inherit-non-class:72:0:72:25:NotInheritableRange:Inheriting 'range', which is not a class.:UNDEFINED +inherit-non-class:76:0:76:25:NotInheritableSlice:Inheriting 'slice', which is not a class.:UNDEFINED +inherit-non-class:80:0:80:30:NotInheritableMemoryView:Inheriting 'memoryview', which is not a class.:UNDEFINED +inherit-non-class:99:0:99:12:Child2:Inheriting 'ParentBad[int]', which is not a class.:UNDEFINED unsubscriptable-object:103:13:103:18:Child3:Value 'Empty' is unsubscriptable:UNDEFINED diff --git a/tests/functional/i/init_is_generator.txt b/tests/functional/i/init_is_generator.txt index 3566e6f585..789e322e87 100644 --- a/tests/functional/i/init_is_generator.txt +++ b/tests/functional/i/init_is_generator.txt @@ -1 +1 @@ -init-is-generator:4:4:5:18:SomeClass.__init__:__init__ method is a generator:UNDEFINED +init-is-generator:4:4:4:16:SomeClass.__init__:__init__ method is a generator:UNDEFINED diff --git a/tests/functional/i/init_not_called.txt b/tests/functional/i/init_not_called.txt index f1779a26a0..ae5e8f91fa 100644 --- a/tests/functional/i/init_not_called.txt +++ b/tests/functional/i/init_not_called.txt @@ -1,2 +1,2 @@ -super-init-not-called:25:4:26:27:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called:INFERENCE -super-init-not-called:58:4:59:20:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called:INFERENCE +super-init-not-called:25:4:25:16:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called:INFERENCE +super-init-not-called:58:4:58:16:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called:INFERENCE diff --git a/tests/functional/i/invalid/invalid_bool_returned.txt b/tests/functional/i/invalid/invalid_bool_returned.txt index 616209139a..f8c6fc125f 100644 --- a/tests/functional/i/invalid/invalid_bool_returned.txt +++ b/tests/functional/i/invalid/invalid_bool_returned.txt @@ -1,3 +1,3 @@ -invalid-bool-returned:36:4:37:16:FirstBadBool.__bool__:__bool__ does not return bool:UNDEFINED -invalid-bool-returned:43:4:44:21:SecondBadBool.__bool__:__bool__ does not return bool:UNDEFINED -invalid-bool-returned:50:4:51:24:ThirdBadBool.__bool__:__bool__ does not return bool:UNDEFINED +invalid-bool-returned:36:4:36:16:FirstBadBool.__bool__:__bool__ does not return bool:UNDEFINED +invalid-bool-returned:43:4:43:16:SecondBadBool.__bool__:__bool__ does not return bool:UNDEFINED +invalid-bool-returned:50:4:50:16:ThirdBadBool.__bool__:__bool__ does not return bool:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_bytes_returned.txt b/tests/functional/i/invalid/invalid_bytes_returned.txt index 9d8f0ece69..c05ea92939 100644 --- a/tests/functional/i/invalid/invalid_bytes_returned.txt +++ b/tests/functional/i/invalid/invalid_bytes_returned.txt @@ -1,3 +1,3 @@ -invalid-bytes-returned:36:4:37:20:FirstBadBytes.__bytes__:__bytes__ does not return bytes:UNDEFINED -invalid-bytes-returned:43:4:44:16:SecondBadBytes.__bytes__:__bytes__ does not return bytes:UNDEFINED -invalid-bytes-returned:50:4:51:36:ThirdBadBytes.__bytes__:__bytes__ does not return bytes:UNDEFINED +invalid-bytes-returned:36:4:36:17:FirstBadBytes.__bytes__:__bytes__ does not return bytes:UNDEFINED +invalid-bytes-returned:43:4:43:17:SecondBadBytes.__bytes__:__bytes__ does not return bytes:UNDEFINED +invalid-bytes-returned:50:4:50:17:ThirdBadBytes.__bytes__:__bytes__ does not return bytes:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_format_returned.txt b/tests/functional/i/invalid/invalid_format_returned.txt index fc753eab3c..17ebefc960 100644 --- a/tests/functional/i/invalid/invalid_format_returned.txt +++ b/tests/functional/i/invalid/invalid_format_returned.txt @@ -1,3 +1,3 @@ -invalid-format-returned:36:4:37:21:FirstBadFormat.__format__:__format__ does not return str:UNDEFINED -invalid-format-returned:43:4:44:16:SecondBadFormat.__format__:__format__ does not return str:UNDEFINED -invalid-format-returned:50:4:51:36:ThirdBadFormat.__format__:__format__ does not return str:UNDEFINED +invalid-format-returned:36:4:36:18:FirstBadFormat.__format__:__format__ does not return str:UNDEFINED +invalid-format-returned:43:4:43:18:SecondBadFormat.__format__:__format__ does not return str:UNDEFINED +invalid-format-returned:50:4:50:18:ThirdBadFormat.__format__:__format__ does not return str:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.txt b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.txt index 198808e79e..b657c5f40b 100644 --- a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.txt +++ b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.txt @@ -1,6 +1,6 @@ -invalid-getnewargs-ex-returned:36:4:37:16:FirstBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED -invalid-getnewargs-ex-returned:43:4:44:41:SecondBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED -invalid-getnewargs-ex-returned:50:4:51:41:ThirdBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED -invalid-getnewargs-ex-returned:57:4:58:29:FourthBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED -invalid-getnewargs-ex-returned:64:4:65:33:FifthBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED -invalid-getnewargs-ex-returned:71:4:72:29:SixthBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED +invalid-getnewargs-ex-returned:36:4:36:25:FirstBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED +invalid-getnewargs-ex-returned:43:4:43:25:SecondBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED +invalid-getnewargs-ex-returned:50:4:50:25:ThirdBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED +invalid-getnewargs-ex-returned:57:4:57:25:FourthBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED +invalid-getnewargs-ex-returned:64:4:64:25:FifthBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED +invalid-getnewargs-ex-returned:71:4:71:25:SixthBadGetNewArgsEx.__getnewargs_ex__:__getnewargs_ex__ does not return a tuple containing (tuple, dict):UNDEFINED diff --git a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.txt b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.txt index 1c719317d8..83c577bfac 100644 --- a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.txt +++ b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.txt @@ -1,3 +1,3 @@ -invalid-getnewargs-returned:36:4:37:16:FirstBadGetNewArgs.__getnewargs__:__getnewargs__ does not return a tuple:UNDEFINED -invalid-getnewargs-returned:43:4:44:26:SecondBadGetNewArgs.__getnewargs__:__getnewargs__ does not return a tuple:UNDEFINED -invalid-getnewargs-returned:50:4:51:34:ThirdBadGetNewArgs.__getnewargs__:__getnewargs__ does not return a tuple:UNDEFINED +invalid-getnewargs-returned:36:4:36:22:FirstBadGetNewArgs.__getnewargs__:__getnewargs__ does not return a tuple:UNDEFINED +invalid-getnewargs-returned:43:4:43:22:SecondBadGetNewArgs.__getnewargs__:__getnewargs__ does not return a tuple:UNDEFINED +invalid-getnewargs-returned:50:4:50:22:ThirdBadGetNewArgs.__getnewargs__:__getnewargs__ does not return a tuple:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_hash_returned.txt b/tests/functional/i/invalid/invalid_hash_returned.txt index 3b5f2d6810..0184f5db5c 100644 --- a/tests/functional/i/invalid/invalid_hash_returned.txt +++ b/tests/functional/i/invalid/invalid_hash_returned.txt @@ -1,4 +1,4 @@ -invalid-hash-returned:36:4:37:17:FirstBadHash.__hash__:__hash__ does not return int:UNDEFINED -invalid-hash-returned:43:4:44:21:SecondBadHash.__hash__:__hash__ does not return int:UNDEFINED -invalid-hash-returned:50:4:51:19:ThirdBadHash.__hash__:__hash__ does not return int:UNDEFINED -invalid-hash-returned:57:4:58:24:FourthBadHash.__hash__:__hash__ does not return int:UNDEFINED +invalid-hash-returned:36:4:36:16:FirstBadHash.__hash__:__hash__ does not return int:UNDEFINED +invalid-hash-returned:43:4:43:16:SecondBadHash.__hash__:__hash__ does not return int:UNDEFINED +invalid-hash-returned:50:4:50:16:ThirdBadHash.__hash__:__hash__ does not return int:UNDEFINED +invalid-hash-returned:57:4:57:16:FourthBadHash.__hash__:__hash__ does not return int:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_index_returned.txt b/tests/functional/i/invalid/invalid_index_returned.txt index 4479b28cf1..5be63e6591 100644 --- a/tests/functional/i/invalid/invalid_index_returned.txt +++ b/tests/functional/i/invalid/invalid_index_returned.txt @@ -1,4 +1,4 @@ -invalid-index-returned:36:4:37:25:FirstBadIndex.__index__:__index__ does not return int:UNDEFINED -invalid-index-returned:43:4:44:19:SecondBadIndex.__index__:__index__ does not return int:UNDEFINED -invalid-index-returned:50:4:51:19:ThirdBadIndex.__index__:__index__ does not return int:UNDEFINED -invalid-index-returned:57:4:58:24:FourthBadIndex.__index__:__index__ does not return int:UNDEFINED +invalid-index-returned:36:4:36:17:FirstBadIndex.__index__:__index__ does not return int:UNDEFINED +invalid-index-returned:43:4:43:17:SecondBadIndex.__index__:__index__ does not return int:UNDEFINED +invalid-index-returned:50:4:50:17:ThirdBadIndex.__index__:__index__ does not return int:UNDEFINED +invalid-index-returned:57:4:57:17:FourthBadIndex.__index__:__index__ does not return int:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.txt b/tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.txt index 2447d07438..8856bc95e9 100644 --- a/tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.txt +++ b/tests/functional/i/invalid/invalid_length/invalid_length_hint_returned.txt @@ -1,3 +1,3 @@ -invalid-length-hint-returned:38:4:39:17:FirstBadLengthHint.__length_hint__:__length_hint__ does not return non-negative integer:UNDEFINED -invalid-length-hint-returned:45:4:46:18:SecondBadLengthHint.__length_hint__:__length_hint__ does not return non-negative integer:UNDEFINED -invalid-length-hint-returned:52:4:53:24:ThirdBadLengthHint.__length_hint__:__length_hint__ does not return non-negative integer:UNDEFINED +invalid-length-hint-returned:38:4:38:23:FirstBadLengthHint.__length_hint__:__length_hint__ does not return non-negative integer:UNDEFINED +invalid-length-hint-returned:45:4:45:23:SecondBadLengthHint.__length_hint__:__length_hint__ does not return non-negative integer:UNDEFINED +invalid-length-hint-returned:52:4:52:23:ThirdBadLengthHint.__length_hint__:__length_hint__ does not return non-negative integer:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_length/invalid_length_returned.txt b/tests/functional/i/invalid/invalid_length/invalid_length_returned.txt index 776945e343..f9ed7ac0e3 100644 --- a/tests/functional/i/invalid/invalid_length/invalid_length_returned.txt +++ b/tests/functional/i/invalid/invalid_length/invalid_length_returned.txt @@ -1,4 +1,4 @@ -invalid-length-returned:38:4:39:17:FirstBadLen.__len__:__len__ does not return non-negative integer:UNDEFINED -invalid-length-returned:45:4:46:18:SecondBadLen.__len__:__len__ does not return non-negative integer:UNDEFINED -invalid-length-returned:52:4:53:24:ThirdBadLen.__len__:__len__ does not return non-negative integer:UNDEFINED -invalid-length-returned:59:4:60:18:NonRegression.__len__:__len__ does not return non-negative integer:UNDEFINED +invalid-length-returned:38:4:38:15:FirstBadLen.__len__:__len__ does not return non-negative integer:UNDEFINED +invalid-length-returned:45:4:45:15:SecondBadLen.__len__:__len__ does not return non-negative integer:UNDEFINED +invalid-length-returned:52:4:52:15:ThirdBadLen.__len__:__len__ does not return non-negative integer:UNDEFINED +invalid-length-returned:59:4:59:15:NonRegression.__len__:__len__ does not return non-negative integer:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_metaclass.txt b/tests/functional/i/invalid/invalid_metaclass.txt index c5ca48d760..006065705e 100644 --- a/tests/functional/i/invalid/invalid_metaclass.txt +++ b/tests/functional/i/invalid/invalid_metaclass.txt @@ -1,6 +1,6 @@ -invalid-metaclass:37:0:38:8:FirstInvalid:Invalid metaclass 'int' used:UNDEFINED -invalid-metaclass:41:0:42:8:SecondInvalid:Invalid metaclass 'InvalidAsMetaclass' used:UNDEFINED -invalid-metaclass:45:0:46:8:ThirdInvalid:Invalid metaclass '2' used:UNDEFINED -invalid-metaclass:49:0:50:8:FourthInvalid:Invalid metaclass 'Instance of invalid_metaclass.InvalidAsMetaclass' used:UNDEFINED -invalid-metaclass:61:0:62:8:Invalid:Invalid metaclass 'int' used:UNDEFINED -invalid-metaclass:65:0:66:8:InvalidSecond:Invalid metaclass '1' used:UNDEFINED +invalid-metaclass:37:0:37:18:FirstInvalid:Invalid metaclass 'int' used:UNDEFINED +invalid-metaclass:41:0:41:19:SecondInvalid:Invalid metaclass 'InvalidAsMetaclass' used:UNDEFINED +invalid-metaclass:45:0:45:18:ThirdInvalid:Invalid metaclass '2' used:UNDEFINED +invalid-metaclass:49:0:49:19:FourthInvalid:Invalid metaclass 'Instance of invalid_metaclass.InvalidAsMetaclass' used:UNDEFINED +invalid-metaclass:61:0:61:13:Invalid:Invalid metaclass 'int' used:UNDEFINED +invalid-metaclass:65:0:65:19:InvalidSecond:Invalid metaclass '1' used:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_name.txt b/tests/functional/i/invalid/invalid_name.txt index bec705f981..12199a97e4 100644 --- a/tests/functional/i/invalid/invalid_name.txt +++ b/tests/functional/i/invalid/invalid_name.txt @@ -1,8 +1,8 @@ invalid-name:12:0:12:3::"Constant name ""aaa"" doesn't conform to UPPER_CASE naming style":HIGH invalid-name:16:4:16:8::"Constant name ""time"" doesn't conform to UPPER_CASE naming style":HIGH -invalid-name:32:0:33:12:a:"Function name ""a"" doesn't conform to snake_case naming style":HIGH +invalid-name:32:0:32:5:a:"Function name ""a"" doesn't conform to snake_case naming style":HIGH invalid-name:46:4:46:13::"Constant name ""Foocapfor"" doesn't conform to UPPER_CASE naming style":HIGH -invalid-name:62:0:64:16:a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad:"Function name ""a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad"" doesn't conform to snake_case naming style":HIGH +invalid-name:62:0:62:68:a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad:"Function name ""a_very_very_very_long_function_name_WithCamelCase_to_make_it_sad"" doesn't conform to snake_case naming style":HIGH invalid-name:70:23:70:29:FooBar.__init__:"Argument name ""fooBar"" doesn't conform to snake_case naming style":HIGH invalid-name:76:8:76:14:FooBar.func1:"Argument name ""fooBar"" doesn't conform to snake_case naming style":HIGH invalid-name:96:8:96:15:FooBar.test_disable_mixed:"Argument name ""fooBar2"" doesn't conform to snake_case naming style":HIGH diff --git a/tests/functional/i/invalid/invalid_name/invalid_name_module_level.txt b/tests/functional/i/invalid/invalid_name/invalid_name_module_level.txt index 1d51c6b679..eb095f2606 100644 --- a/tests/functional/i/invalid/invalid_name/invalid_name_module_level.txt +++ b/tests/functional/i/invalid/invalid_name/invalid_name_module_level.txt @@ -1 +1 @@ -invalid-name:16:0:17:18:A:"Function name ""A"" doesn't conform to snake_case naming style":HIGH +invalid-name:16:0:16:5:A:"Function name ""A"" doesn't conform to snake_case naming style":HIGH diff --git a/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.txt b/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.txt index f948f48156..2c7e14ccc7 100644 --- a/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.txt +++ b/tests/functional/i/invalid/invalid_name/invalid_name_multinaming_style.txt @@ -1 +1 @@ -invalid-name:4:0:5:59:UPPER:"Function name ""UPPER"" doesn't conform to the `down` group in the '^(?:(?P[A-Z]+)|(?P[a-z]+))$' pattern":HIGH +invalid-name:4:0:4:9:UPPER:"Function name ""UPPER"" doesn't conform to the `down` group in the '^(?:(?P[A-Z]+)|(?P[a-z]+))$' pattern":HIGH diff --git a/tests/functional/i/invalid/invalid_name/invalid_name_property.txt b/tests/functional/i/invalid/invalid_name/invalid_name_property.txt index dfbc2f4651..84d804ce04 100644 --- a/tests/functional/i/invalid/invalid_name/invalid_name_property.txt +++ b/tests/functional/i/invalid/invalid_name/invalid_name_property.txt @@ -1,3 +1,3 @@ invalid-name:9:16:9:17:custom_prop:"Argument name ""f"" doesn't conform to snake_case naming style":HIGH -invalid-name:21:4:22:12:FooClass.bar:"Attribute name ""bar"" doesn't conform to '[A-Z]+' pattern":INFERENCE -invalid-name:37:4:38:12:AnotherFooClass.foo:"Attribute name ""foo"" doesn't conform to '[A-Z]+' pattern":INFERENCE +invalid-name:21:4:21:11:FooClass.bar:"Attribute name ""bar"" doesn't conform to '[A-Z]+' pattern":INFERENCE +invalid-name:37:4:37:11:AnotherFooClass.foo:"Attribute name ""foo"" doesn't conform to '[A-Z]+' pattern":INFERENCE diff --git a/tests/functional/i/invalid/invalid_overridden_method.txt b/tests/functional/i/invalid/invalid_overridden_method.txt index 65a2d76ab2..5506109df0 100644 --- a/tests/functional/i/invalid/invalid_overridden_method.txt +++ b/tests/functional/i/invalid/invalid_overridden_method.txt @@ -1,6 +1,6 @@ -invalid-overridden-method:38:4:39:19:InvalidDerived.prop:Method 'prop' was expected to be 'property', found it instead as 'method':UNDEFINED -invalid-overridden-method:41:4:42:19:InvalidDerived.async_method:Method 'async_method' was expected to be 'async', found it instead as 'non-async':UNDEFINED -invalid-overridden-method:45:4:46:19:InvalidDerived.method_a:Method 'method_a' was expected to be 'method', found it instead as 'property':UNDEFINED -invalid-overridden-method:48:4:49:19:InvalidDerived.method_b:Method 'method_b' was expected to be 'non-async', found it instead as 'async':UNDEFINED -invalid-overridden-method:122:4:123:20:B.bar:Method 'bar' was expected to be 'property', found it instead as 'method':UNDEFINED -invalid-overridden-method:126:4:127:20:B.bar2:Method 'bar2' was expected to be 'property', found it instead as 'method':UNDEFINED +invalid-overridden-method:38:4:38:12:InvalidDerived.prop:Method 'prop' was expected to be 'property', found it instead as 'method':UNDEFINED +invalid-overridden-method:41:4:41:20:InvalidDerived.async_method:Method 'async_method' was expected to be 'async', found it instead as 'non-async':UNDEFINED +invalid-overridden-method:45:4:45:16:InvalidDerived.method_a:Method 'method_a' was expected to be 'method', found it instead as 'property':UNDEFINED +invalid-overridden-method:48:4:48:22:InvalidDerived.method_b:Method 'method_b' was expected to be 'non-async', found it instead as 'async':UNDEFINED +invalid-overridden-method:122:4:122:11:B.bar:Method 'bar' was expected to be 'property', found it instead as 'method':UNDEFINED +invalid-overridden-method:126:4:126:12:B.bar2:Method 'bar2' was expected to be 'property', found it instead as 'method':UNDEFINED diff --git a/tests/functional/i/invalid/invalid_repr_returned.txt b/tests/functional/i/invalid/invalid_repr_returned.txt index 0f557701ab..53d00abe05 100644 --- a/tests/functional/i/invalid/invalid_repr_returned.txt +++ b/tests/functional/i/invalid/invalid_repr_returned.txt @@ -1,3 +1,3 @@ -invalid-repr-returned:36:4:37:21:FirstBadRepr.__repr__:__repr__ does not return str:UNDEFINED -invalid-repr-returned:43:4:44:16:SecondBadRepr.__repr__:__repr__ does not return str:UNDEFINED -invalid-repr-returned:50:4:51:34:ThirdBadRepr.__repr__:__repr__ does not return str:UNDEFINED +invalid-repr-returned:36:4:36:16:FirstBadRepr.__repr__:__repr__ does not return str:UNDEFINED +invalid-repr-returned:43:4:43:16:SecondBadRepr.__repr__:__repr__ does not return str:UNDEFINED +invalid-repr-returned:50:4:50:16:ThirdBadRepr.__repr__:__repr__ does not return str:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_str_returned.txt b/tests/functional/i/invalid/invalid_str_returned.txt index 522fdb9bff..4e7d96d555 100644 --- a/tests/functional/i/invalid/invalid_str_returned.txt +++ b/tests/functional/i/invalid/invalid_str_returned.txt @@ -1,3 +1,3 @@ -invalid-str-returned:36:4:37:21:FirstBadStr.__str__:__str__ does not return str:UNDEFINED -invalid-str-returned:43:4:44:16:SecondBadStr.__str__:__str__ does not return str:UNDEFINED -invalid-str-returned:50:4:51:33:ThirdBadStr.__str__:__str__ does not return str:UNDEFINED +invalid-str-returned:36:4:36:15:FirstBadStr.__str__:__str__ does not return str:UNDEFINED +invalid-str-returned:43:4:43:15:SecondBadStr.__str__:__str__ does not return str:UNDEFINED +invalid-str-returned:50:4:50:15:ThirdBadStr.__str__:__str__ does not return str:UNDEFINED diff --git a/tests/functional/k/keyword_arg_before_vararg.txt b/tests/functional/k/keyword_arg_before_vararg.txt index f20a319a71..87a88f1a4e 100644 --- a/tests/functional/k/keyword_arg_before_vararg.txt +++ b/tests/functional/k/keyword_arg_before_vararg.txt @@ -1,4 +1,4 @@ -keyword-arg-before-vararg:5:0:7:8:check_kwargs_before_args:Keyword argument before variable positional arguments list in the definition of check_kwargs_before_args function:UNDEFINED -keyword-arg-before-vararg:14:4:16:12:AAAA.func_in_class:Keyword argument before variable positional arguments list in the definition of func_in_class function:UNDEFINED -keyword-arg-before-vararg:19:4:21:12:AAAA.static_method_in_class:Keyword argument before variable positional arguments list in the definition of static_method_in_class function:UNDEFINED -keyword-arg-before-vararg:24:4:26:12:AAAA.class_method_in_class:Keyword argument before variable positional arguments list in the definition of class_method_in_class function:UNDEFINED +keyword-arg-before-vararg:5:0:5:28:check_kwargs_before_args:Keyword argument before variable positional arguments list in the definition of check_kwargs_before_args function:UNDEFINED +keyword-arg-before-vararg:14:4:14:21:AAAA.func_in_class:Keyword argument before variable positional arguments list in the definition of func_in_class function:UNDEFINED +keyword-arg-before-vararg:19:4:19:30:AAAA.static_method_in_class:Keyword argument before variable positional arguments list in the definition of static_method_in_class function:UNDEFINED +keyword-arg-before-vararg:24:4:24:29:AAAA.class_method_in_class:Keyword argument before variable positional arguments list in the definition of class_method_in_class function:UNDEFINED diff --git a/tests/functional/m/method_hidden.txt b/tests/functional/m/method_hidden.txt index c15486dce6..23651bd4a0 100644 --- a/tests/functional/m/method_hidden.txt +++ b/tests/functional/m/method_hidden.txt @@ -1,3 +1,3 @@ -method-hidden:18:4:20:19:Cdef.abcd:An attribute defined in functional.m.method_hidden line 12 hides this method:UNDEFINED -method-hidden:86:4:87:12:One.one:An attribute defined in functional.m.method_hidden line 84 hides this method:UNDEFINED -method-hidden:113:4:114:12:Child._protected:An attribute defined in functional.m.method_hidden line 109 hides this method:UNDEFINED +method-hidden:18:4:18:12:Cdef.abcd:An attribute defined in functional.m.method_hidden line 12 hides this method:UNDEFINED +method-hidden:86:4:86:11:One.one:An attribute defined in functional.m.method_hidden line 84 hides this method:UNDEFINED +method-hidden:113:4:113:18:Child._protected:An attribute defined in functional.m.method_hidden line 109 hides this method:UNDEFINED diff --git a/tests/functional/m/missing/missing_class_docstring.txt b/tests/functional/m/missing/missing_class_docstring.txt index 2acd9bc197..4aaa0a8216 100644 --- a/tests/functional/m/missing/missing_class_docstring.txt +++ b/tests/functional/m/missing/missing_class_docstring.txt @@ -1 +1 @@ -missing-class-docstring:5:0:6:8:Klass:Missing class docstring:HIGH +missing-class-docstring:5:0:5:11:Klass:Missing class docstring:HIGH diff --git a/tests/functional/m/missing/missing_docstring.txt b/tests/functional/m/missing/missing_docstring.txt index af030e11c4..4ed84ed06c 100644 --- a/tests/functional/m/missing/missing_docstring.txt +++ b/tests/functional/m/missing/missing_docstring.txt @@ -1,3 +1,3 @@ missing-module-docstring:1:0:None:None::Missing module docstring:HIGH -missing-class-docstring:21:0:22:8:ClassUndocumented:Missing class docstring:HIGH -missing-function-docstring:25:0:26:8:public_undocumented:Missing function or method docstring:HIGH +missing-class-docstring:21:0:21:23:ClassUndocumented:Missing class docstring:HIGH +missing-function-docstring:25:0:25:23:public_undocumented:Missing function or method docstring:HIGH diff --git a/tests/functional/m/missing/missing_docstring_new_style.txt b/tests/functional/m/missing/missing_docstring_new_style.txt index f0a456c24a..645791a65f 100644 --- a/tests/functional/m/missing/missing_docstring_new_style.txt +++ b/tests/functional/m/missing/missing_docstring_new_style.txt @@ -1,5 +1,5 @@ missing-module-docstring:1:0:None:None::Missing module docstring:HIGH -missing-class-docstring:9:0:10:8:UndocumentedClass:Missing class docstring:HIGH -missing-class-docstring:19:0:20:8:OtherClassUndocumented:Missing class docstring:HIGH -missing-function-docstring:36:0:37:8:public_undocumented:Missing function or method docstring:HIGH -missing-function-docstring:46:0:47:8:undocumented_other_function:Missing function or method docstring:HIGH +missing-class-docstring:9:0:9:23:UndocumentedClass:Missing class docstring:HIGH +missing-class-docstring:19:0:19:28:OtherClassUndocumented:Missing class docstring:HIGH +missing-function-docstring:36:0:36:23:public_undocumented:Missing function or method docstring:HIGH +missing-function-docstring:46:0:46:31:undocumented_other_function:Missing function or method docstring:HIGH diff --git a/tests/functional/m/missing/missing_function_docstring.txt b/tests/functional/m/missing/missing_function_docstring.txt index 9eea693c09..2f75b8e123 100644 --- a/tests/functional/m/missing/missing_function_docstring.txt +++ b/tests/functional/m/missing/missing_function_docstring.txt @@ -1,2 +1,2 @@ -missing-function-docstring:5:0:6:8:func:Missing function or method docstring:HIGH -missing-function-docstring:18:4:19:12:MyClass.__init__:Missing function or method docstring:INFERENCE +missing-function-docstring:5:0:5:8:func:Missing function or method docstring:HIGH +missing-function-docstring:18:4:18:16:MyClass.__init__:Missing function or method docstring:INFERENCE diff --git a/tests/functional/m/missing/missing_function_docstring_min_length.txt b/tests/functional/m/missing/missing_function_docstring_min_length.txt index cf38aceee1..477c8ee8cc 100644 --- a/tests/functional/m/missing/missing_function_docstring_min_length.txt +++ b/tests/functional/m/missing/missing_function_docstring_min_length.txt @@ -1,2 +1,2 @@ -missing-function-docstring:9:0:11:8:func_two:Missing function or method docstring:HIGH -missing-function-docstring:14:0:18:12:func_three:Missing function or method docstring:HIGH +missing-function-docstring:9:0:9:12:func_two:Missing function or method docstring:HIGH +missing-function-docstring:14:0:14:14:func_three:Missing function or method docstring:HIGH diff --git a/tests/functional/m/missing/missing_function_docstring_rgx.txt b/tests/functional/m/missing/missing_function_docstring_rgx.txt index ee085ec067..b3d5227a7e 100644 --- a/tests/functional/m/missing/missing_function_docstring_rgx.txt +++ b/tests/functional/m/missing/missing_function_docstring_rgx.txt @@ -1 +1 @@ -missing-function-docstring:10:4:11:19:Child.__eq__:Missing function or method docstring:INFERENCE +missing-function-docstring:10:4:10:14:Child.__eq__:Missing function or method docstring:INFERENCE diff --git a/tests/functional/m/missing/missing_self_argument.txt b/tests/functional/m/missing/missing_self_argument.txt index 855f83f584..14a9a98633 100644 --- a/tests/functional/m/missing/missing_self_argument.txt +++ b/tests/functional/m/missing/missing_self_argument.txt @@ -1,3 +1,3 @@ -no-method-argument:12:4:13:47:MyClass.method:Method has no argument:UNDEFINED -no-method-argument:15:4:17:20:MyClass.setup:Method has no argument:UNDEFINED +no-method-argument:12:4:12:14:MyClass.method:Method has no argument:UNDEFINED +no-method-argument:15:4:15:13:MyClass.setup:Method has no argument:UNDEFINED undefined-variable:17:8:17:12:MyClass.setup:Undefined variable 'self':UNDEFINED diff --git a/tests/functional/n/name/name_good_bad_names_regex.txt b/tests/functional/n/name/name_good_bad_names_regex.txt index 78d57990d6..66c1c99f15 100644 --- a/tests/functional/n/name/name_good_bad_names_regex.txt +++ b/tests/functional/n/name/name_good_bad_names_regex.txt @@ -1,3 +1,3 @@ disallowed-name:5:0:5:26::"Disallowed name ""explicit_bad_some_constant""":UNDEFINED invalid-name:7:0:7:28::"Constant name ""snake_case_bad_SOME_CONSTANT"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]*|__.*__)$' pattern)":HIGH -disallowed-name:19:0:20:8:disallowed_2_snake_case:"Disallowed name ""disallowed_2_snake_case""":UNDEFINED +disallowed-name:19:0:19:27:disallowed_2_snake_case:"Disallowed name ""disallowed_2_snake_case""":UNDEFINED diff --git a/tests/functional/n/name/name_preset_snake_case.txt b/tests/functional/n/name/name_preset_snake_case.txt index 664b2db90e..339f27bd8a 100644 --- a/tests/functional/n/name/name_preset_snake_case.txt +++ b/tests/functional/n/name/name_preset_snake_case.txt @@ -1,5 +1,5 @@ invalid-name:6:0:6:13::"Constant name ""SOME_CONSTANT"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]*|__.*__)$' pattern)":HIGH -invalid-name:13:0:22:83:MyClass:"Class name ""MyClass"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH -invalid-name:25:0:26:8:sayHello:"Function name ""sayHello"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]{2,}|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)":HIGH -invalid-name:29:0:31:22:FooEnum:"Class name ""FooEnum"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH -invalid-name:34:0:35:45:Bar:"Class name ""Bar"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH +invalid-name:13:0:13:13:MyClass:"Class name ""MyClass"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH +invalid-name:25:0:25:12:sayHello:"Function name ""sayHello"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]{2,}|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)":HIGH +invalid-name:29:0:29:13:FooEnum:"Class name ""FooEnum"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH +invalid-name:34:0:34:9:Bar:"Class name ""Bar"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)":HIGH diff --git a/tests/functional/n/name/name_styles.txt b/tests/functional/n/name/name_styles.txt index a6d18a1667..ad7dad05fb 100644 --- a/tests/functional/n/name/name_styles.txt +++ b/tests/functional/n/name/name_styles.txt @@ -1,17 +1,17 @@ invalid-name:11:0:11:14::"Constant name ""bad_const_name"" doesn't conform to UPPER_CASE naming style":HIGH -invalid-name:14:0:17:24:BADFUNCTION_name:"Function name ""BADFUNCTION_name"" doesn't conform to snake_case naming style":HIGH +invalid-name:14:0:14:20:BADFUNCTION_name:"Function name ""BADFUNCTION_name"" doesn't conform to snake_case naming style":HIGH invalid-name:16:4:16:17:BADFUNCTION_name:"Variable name ""BAD_LOCAL_VAR"" doesn't conform to snake_case naming style":HIGH invalid-name:20:21:20:29:func_bad_argname:"Argument name ""NOT_GOOD"" doesn't conform to snake_case naming style":HIGH -invalid-name:30:0:31:32:bad_class_name:"Class name ""bad_class_name"" doesn't conform to PascalCase naming style":HIGH +invalid-name:30:0:30:20:bad_class_name:"Class name ""bad_class_name"" doesn't conform to PascalCase naming style":HIGH invalid-name:41:8:41:27:CorrectClassName.__init__:"Attribute name ""_Bad_AtTR_name"" doesn't conform to snake_case naming style":HIGH invalid-name:42:8:42:28:CorrectClassName.__init__:"Attribute name ""Bad_PUBLIC_name"" doesn't conform to snake_case naming style":HIGH -invalid-name:47:4:48:39:CorrectClassName.BadMethodName:"Method name ""BadMethodName"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:53:4:54:41:CorrectClassName.__DunDER_IS_not_free_for_all__:"Method name ""__DunDER_IS_not_free_for_all__"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:47:4:47:21:CorrectClassName.BadMethodName:"Method name ""BadMethodName"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:53:4:53:38:CorrectClassName.__DunDER_IS_not_free_for_all__:"Method name ""__DunDER_IS_not_free_for_all__"" doesn't conform to snake_case naming style":INFERENCE invalid-name:83:0:83:18::"Class name ""BAD_NAME_FOR_CLASS"" doesn't conform to PascalCase naming style":HIGH invalid-name:84:0:84:23::"Class name ""NEXT_BAD_NAME_FOR_CLASS"" doesn't conform to PascalCase naming style":HIGH invalid-name:91:0:91:11::"Class name ""NOT_CORRECT"" doesn't conform to PascalCase naming style":HIGH invalid-name:97:4:97:22:test_globals:"Constant name ""AlsoCorrect"" doesn't conform to UPPER_CASE naming style":HIGH -invalid-name:110:4:112:12:FooClass.PROPERTY_NAME:"Attribute name ""PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:116:4:118:12:FooClass.ABSTRACT_PROPERTY_NAME:"Attribute name ""ABSTRACT_PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:121:4:123:12:FooClass.PROPERTY_NAME_SETTER:"Attribute name ""PROPERTY_NAME_SETTER"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:110:4:110:21:FooClass.PROPERTY_NAME:"Attribute name ""PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:116:4:116:30:FooClass.ABSTRACT_PROPERTY_NAME:"Attribute name ""ABSTRACT_PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:121:4:121:28:FooClass.PROPERTY_NAME_SETTER:"Attribute name ""PROPERTY_NAME_SETTER"" doesn't conform to snake_case naming style":INFERENCE invalid-name:152:4:152:17:FooEnum:"Class constant name ""bad_enum_name"" doesn't conform to UPPER_CASE naming style":HIGH diff --git a/tests/functional/n/namePresetCamelCase.txt b/tests/functional/n/namePresetCamelCase.txt index 7b9ec86231..9461c6a536 100644 --- a/tests/functional/n/namePresetCamelCase.txt +++ b/tests/functional/n/namePresetCamelCase.txt @@ -1,3 +1,3 @@ invalid-name:3:0:3:13::"Constant name ""SOME_CONSTANT"" doesn't conform to camelCase naming style ('([^\\W\\dA-Z][^\\W_]*|__.*__)$' pattern)":HIGH -invalid-name:10:0:19:79:MyClass:"Class name ""MyClass"" doesn't conform to camelCase naming style ('[^\\W\\dA-Z][^\\W_]+$' pattern)":HIGH -invalid-name:22:0:23:8:say_hello:"Function name ""say_hello"" doesn't conform to camelCase naming style ('([^\\W\\dA-Z][^\\W_]{2,}|__[^\\W\\dA-Z_]\\w+__)$' pattern)":HIGH +invalid-name:10:0:10:13:MyClass:"Class name ""MyClass"" doesn't conform to camelCase naming style ('[^\\W\\dA-Z][^\\W_]+$' pattern)":HIGH +invalid-name:22:0:22:13:say_hello:"Function name ""say_hello"" doesn't conform to camelCase naming style ('([^\\W\\dA-Z][^\\W_]{2,}|__[^\\W\\dA-Z_]\\w+__)$' pattern)":HIGH diff --git a/tests/functional/n/no/no_self_argument.txt b/tests/functional/n/no/no_self_argument.txt index 74f6ac7b1a..45d12cc321 100644 --- a/tests/functional/n/no/no_self_argument.txt +++ b/tests/functional/n/no/no_self_argument.txt @@ -1,2 +1,2 @@ -no-self-argument:8:4:10:16:NoSelfArgument.__init__:"Method should have ""self"" as first argument":UNDEFINED -no-self-argument:12:4:14:18:NoSelfArgument.abdc:"Method should have ""self"" as first argument":UNDEFINED +no-self-argument:8:4:8:16:NoSelfArgument.__init__:"Method should have ""self"" as first argument":UNDEFINED +no-self-argument:12:4:12:12:NoSelfArgument.abdc:"Method should have ""self"" as first argument":UNDEFINED diff --git a/tests/functional/n/no/no_self_argument_py37.txt b/tests/functional/n/no/no_self_argument_py37.txt index 05cd3143d4..9f5779ab69 100644 --- a/tests/functional/n/no/no_self_argument_py37.txt +++ b/tests/functional/n/no/no_self_argument_py37.txt @@ -1 +1 @@ -no-self-argument:13:4:15:12:Toto.__class_other__:"Method should have ""self"" as first argument":UNDEFINED +no-self-argument:13:4:13:23:Toto.__class_other__:"Method should have ""self"" as first argument":UNDEFINED diff --git a/tests/functional/n/no/no_self_use.txt b/tests/functional/n/no/no_self_use.txt index a349f0bc76..4cc4774ff2 100644 --- a/tests/functional/n/no/no_self_use.txt +++ b/tests/functional/n/no/no_self_use.txt @@ -1,2 +1,2 @@ -no-self-use:16:4:18:22:Toto.function_method:Method could be a function:UNDEFINED -no-self-use:24:4:26:22:Toto.async_function_method:Method could be a function:UNDEFINED +no-self-use:16:4:16:23:Toto.function_method:Method could be a function:UNDEFINED +no-self-use:24:4:24:35:Toto.async_function_method:Method could be a function:UNDEFINED diff --git a/tests/functional/n/non/non_ascii_name.txt b/tests/functional/n/non/non_ascii_name.txt index 2cda6962ef..41f1730612 100644 --- a/tests/functional/n/non/non_ascii_name.txt +++ b/tests/functional/n/non/non_ascii_name.txt @@ -1,2 +1,2 @@ non-ascii-name:3:0:3:10::"Variable name ""áéíóú"" contains a non-ASCII character, consider renaming it.":HIGH -non-ascii-name:5:0:6:12:úóíéá:"Function name ""úóíéá"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:5:0:5:9:úóíéá:"Function name ""úóíéá"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non/non_init_parent_called.txt b/tests/functional/n/non/non_init_parent_called.txt index 97f6eb4edc..06de9a2443 100644 --- a/tests/functional/n/non/non_init_parent_called.txt +++ b/tests/functional/n/non/non_init_parent_called.txt @@ -2,5 +2,5 @@ import-error:7:0:7:18::Unable to import 'nonexistant':UNDEFINED non-parent-init-called:15:8:15:26:AAAA.__init__:__init__ method from a non direct base class 'BBBBMixin' is called:UNDEFINED no-member:23:50:23:77:CCC:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE no-member:28:8:28:35:CCC.__init__:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE -super-init-not-called:49:4:51:25:Super2.__init__:__init__ method from base class 'dict' is not called:INFERENCE +super-init-not-called:49:4:49:16:Super2.__init__:__init__ method from base class 'dict' is not called:INFERENCE no-member:51:8:51:23:Super2.__init__:Super of 'Super2' has no '__woohoo__' member:INFERENCE diff --git a/tests/functional/n/non/non_iterator_returned.txt b/tests/functional/n/non/non_iterator_returned.txt index a5d3337dfe..a0d3d665f6 100644 --- a/tests/functional/n/non/non_iterator_returned.txt +++ b/tests/functional/n/non/non_iterator_returned.txt @@ -1,4 +1,4 @@ -non-iterator-returned:79:4:80:17:FirstBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED -non-iterator-returned:86:4:87:19:SecondBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED -non-iterator-returned:93:4:94:34:ThirdBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED -non-iterator-returned:100:4:101:31:FourthBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED +non-iterator-returned:79:4:79:16:FirstBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED +non-iterator-returned:86:4:86:16:SecondBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED +non-iterator-returned:93:4:93:16:ThirdBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED +non-iterator-returned:100:4:100:16:FourthBadIterator.__iter__:__iter__ returns non-iterator:UNDEFINED diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function.txt index 949059da9e..c985a91ac2 100644 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function.txt +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function.txt @@ -1 +1 @@ -non-ascii-name:13:0:15:28:sayНello:"Function name ""sayНello"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:13:0:13:12:sayНello:"Function name ""sayНello"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt index 8bc3e8ce69..5aa2dc7e96 100644 --- a/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt +++ b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt @@ -1 +1 @@ -non-ascii-name:11:4:13:19:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:11:4:11:16:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt index 92c4430b31..eda2ba0986 100644 --- a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt @@ -1 +1 @@ -non-ascii-name:11:0:16:19:НoldIt:"Class name ""НoldIt"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:11:0:11:12:НoldIt:"Class name ""НoldIt"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt index 8d7606e679..43249c212f 100644 --- a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt @@ -1 +1 @@ -non-ascii-name:12:4:14:19:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:12:4:12:16:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/nonlocal_and_global.txt b/tests/functional/n/nonlocal_and_global.txt index 2bc929cdb6..8cf0948f71 100644 --- a/tests/functional/n/nonlocal_and_global.txt +++ b/tests/functional/n/nonlocal_and_global.txt @@ -1 +1 @@ -nonlocal-and-global:4:0:6:18:bad:Name 'missing' is nonlocal and global:UNDEFINED +nonlocal-and-global:4:0:4:7:bad:Name 'missing' is nonlocal and global:UNDEFINED diff --git a/tests/functional/o/overridden_final_method_py38.txt b/tests/functional/o/overridden_final_method_py38.txt index 8f7af49e2a..d5c4374db9 100644 --- a/tests/functional/o/overridden_final_method_py38.txt +++ b/tests/functional/o/overridden_final_method_py38.txt @@ -1,2 +1,2 @@ -overridden-final-method:15:4:16:12:Subclass.my_method:Method 'my_method' overrides a method decorated with typing.final which is defined in class 'Base':UNDEFINED -overridden-final-method:30:4:31:12:Subclass2.my_method:Method 'my_method' overrides a method decorated with typing.final which is defined in class 'BaseConditional':UNDEFINED +overridden-final-method:15:4:15:17:Subclass.my_method:Method 'my_method' overrides a method decorated with typing.final which is defined in class 'Base':UNDEFINED +overridden-final-method:30:4:30:17:Subclass2.my_method:Method 'my_method' overrides a method decorated with typing.final which is defined in class 'BaseConditional':UNDEFINED diff --git a/tests/functional/p/property_with_parameters.txt b/tests/functional/p/property_with_parameters.txt index 326f195436..bc07bc6d19 100644 --- a/tests/functional/p/property_with_parameters.txt +++ b/tests/functional/p/property_with_parameters.txt @@ -1 +1 @@ -property-with-parameters:7:4:8:29:Cls.attribute:Cannot have defined parameters for properties:UNDEFINED +property-with-parameters:7:4:7:17:Cls.attribute:Cannot have defined parameters for properties:UNDEFINED diff --git a/tests/functional/p/protocol_classes.txt b/tests/functional/p/protocol_classes.txt index f08492c277..c9d3861467 100644 --- a/tests/functional/p/protocol_classes.txt +++ b/tests/functional/p/protocol_classes.txt @@ -1,3 +1,3 @@ -no-self-use:31:4:32:11:HasherFake.update:Method could be a function:UNDEFINED +no-self-use:31:4:31:14:HasherFake.update:Method could be a function:UNDEFINED unused-argument:31:21:31:32:HasherFake.update:Unused argument 'blob':INFERENCE -no-self-use:33:4:34:11:HasherFake.digest:Method could be a function:UNDEFINED +no-self-use:33:4:33:14:HasherFake.digest:Method could be a function:UNDEFINED diff --git a/tests/functional/r/regression/regression_4680.txt b/tests/functional/r/regression/regression_4680.txt index 19732c5d1e..072e54f394 100644 --- a/tests/functional/r/regression/regression_4680.txt +++ b/tests/functional/r/regression/regression_4680.txt @@ -1,2 +1,2 @@ import-error:3:0:3:14::Unable to import 'pkg.sub':UNDEFINED -undefined-variable:10:0:11:8:FailedTwo:Undefined variable 'ab':UNDEFINED +undefined-variable:10:0:10:15:FailedTwo:Undefined variable 'ab':UNDEFINED diff --git a/tests/functional/r/regression/regression_4723.txt b/tests/functional/r/regression/regression_4723.txt index a187311baa..74d3df5a22 100644 --- a/tests/functional/r/regression/regression_4723.txt +++ b/tests/functional/r/regression/regression_4723.txt @@ -1 +1 @@ -no-method-argument:15:4:16:12:B.play:Method has no argument:UNDEFINED +no-method-argument:15:4:15:12:B.play:Method has no argument:UNDEFINED diff --git a/tests/functional/r/return_in_init.txt b/tests/functional/r/return_in_init.txt index 3accd68341..7586c50391 100644 --- a/tests/functional/r/return_in_init.txt +++ b/tests/functional/r/return_in_init.txt @@ -1 +1 @@ -return-in-init:5:4:6:16:MyClass.__init__:Explicit return in __init__:UNDEFINED +return-in-init:5:4:5:16:MyClass.__init__:Explicit return in __init__:UNDEFINED diff --git a/tests/functional/s/signature_differs.txt b/tests/functional/s/signature_differs.txt index 4311f4ef18..8c355bc6c3 100644 --- a/tests/functional/s/signature_differs.txt +++ b/tests/functional/s/signature_differs.txt @@ -1 +1 @@ -signature-differs:21:4:22:24:Cdef.abcd:Signature differs from overridden 'abcd' method:UNDEFINED +signature-differs:21:4:21:12:Cdef.abcd:Signature differs from overridden 'abcd' method:UNDEFINED diff --git a/tests/functional/s/singledispatch_functions.txt b/tests/functional/s/singledispatch_functions.txt index 1879d136da..88973375f5 100644 --- a/tests/functional/s/singledispatch_functions.txt +++ b/tests/functional/s/singledispatch_functions.txt @@ -1,5 +1,5 @@ unused-variable:60:4:60:10:_:Unused variable 'unused':UNDEFINED unused-argument:65:24:65:27:not_single_dispatch:Unused argument 'arg':HIGH unused-argument:70:24:70:27:bad_single_dispatch:Unused argument 'arg':HIGH -function-redefined:75:0:76:13:bad_single_dispatch:function already defined line 70:UNDEFINED +function-redefined:75:0:75:23:bad_single_dispatch:function already defined line 70:UNDEFINED unused-argument:75:24:75:27:bad_single_dispatch:Unused argument 'arg':HIGH diff --git a/tests/functional/s/slots_checks.txt b/tests/functional/s/slots_checks.txt index c3069131b9..17b3195522 100644 --- a/tests/functional/s/slots_checks.txt +++ b/tests/functional/s/slots_checks.txt @@ -1,11 +1,11 @@ -invalid-slots:36:0:37:20:Bad:Invalid __slots__ object:UNDEFINED -invalid-slots:39:0:40:17:SecondBad:Invalid __slots__ object:UNDEFINED +invalid-slots:36:0:36:9:Bad:Invalid __slots__ object:UNDEFINED +invalid-slots:39:0:39:15:SecondBad:Invalid __slots__ object:UNDEFINED invalid-slots-object:43:22:43:23:ThirdBad:Invalid object '2' in __slots__, must contain only non empty strings:UNDEFINED -invalid-slots:45:0:46:29:FourthBad:Invalid __slots__ object:UNDEFINED +invalid-slots:45:0:45:15:FourthBad:Invalid __slots__ object:UNDEFINED invalid-slots-object:49:27:49:29:FifthBad:"Invalid object ""''"" in __slots__, must contain only non empty strings":UNDEFINED -single-string-used-for-slots:51:0:52:19:SixthBad:Class __slots__ should be a non-string iterable:UNDEFINED -single-string-used-for-slots:54:0:55:23:SeventhBad:Class __slots__ should be a non-string iterable:UNDEFINED -single-string-used-for-slots:57:0:58:30:EighthBad:Class __slots__ should be a non-string iterable:UNDEFINED +single-string-used-for-slots:51:0:51:14:SixthBad:Class __slots__ should be a non-string iterable:UNDEFINED +single-string-used-for-slots:54:0:54:16:SeventhBad:Class __slots__ should be a non-string iterable:UNDEFINED +single-string-used-for-slots:57:0:57:15:EighthBad:Class __slots__ should be a non-string iterable:UNDEFINED class-variable-slots-conflict:85:17:85:24:ValueInSlotConflict:Value 'first' in slots conflicts with class variable:UNDEFINED class-variable-slots-conflict:85:45:85:53:ValueInSlotConflict:Value 'fourth' in slots conflicts with class variable:UNDEFINED class-variable-slots-conflict:85:36:85:43:ValueInSlotConflict:Value 'third' in slots conflicts with class variable:UNDEFINED diff --git a/tests/functional/s/subclassed_final_class_py38.txt b/tests/functional/s/subclassed_final_class_py38.txt index d7de834b1d..9dec8110e8 100644 --- a/tests/functional/s/subclassed_final_class_py38.txt +++ b/tests/functional/s/subclassed_final_class_py38.txt @@ -1 +1 @@ -subclassed-final-class:14:0:15:8:Subclass:"Class 'Subclass' is a subclass of a class decorated with typing.final: 'Base'":UNDEFINED +subclassed-final-class:14:0:14:14:Subclass:"Class 'Subclass' is a subclass of a class decorated with typing.final: 'Base'":UNDEFINED diff --git a/tests/functional/s/super/super_init_not_called.txt b/tests/functional/s/super/super_init_not_called.txt index b6a220c69f..615b24a542 100644 --- a/tests/functional/s/super/super_init_not_called.txt +++ b/tests/functional/s/super/super_init_not_called.txt @@ -1,2 +1,2 @@ undefined-variable:18:23:18:40:UninferableChild:Undefined variable 'UninferableParent':UNDEFINED -super-init-not-called:49:4:50:11:ChildThree.__init__:__init__ method from base class 'ParentWithoutInit' is not called:INFERENCE +super-init-not-called:49:4:49:16:ChildThree.__init__:__init__ method from base class 'ParentWithoutInit' is not called:INFERENCE diff --git a/tests/functional/s/super/super_init_not_called_extensions.txt b/tests/functional/s/super/super_init_not_called_extensions.txt index 6a8ad14fe6..b80cb80be4 100644 --- a/tests/functional/s/super/super_init_not_called_extensions.txt +++ b/tests/functional/s/super/super_init_not_called_extensions.txt @@ -1 +1 @@ -super-init-not-called:21:4:22:11:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE +super-init-not-called:21:4:21:16:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE diff --git a/tests/functional/s/super/super_init_not_called_extensions_py310.txt b/tests/functional/s/super/super_init_not_called_extensions_py310.txt index 6a8ad14fe6..b80cb80be4 100644 --- a/tests/functional/s/super/super_init_not_called_extensions_py310.txt +++ b/tests/functional/s/super/super_init_not_called_extensions_py310.txt @@ -1 +1 @@ -super-init-not-called:21:4:22:11:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE +super-init-not-called:21:4:21:16:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE diff --git a/tests/functional/t/too/too_few_public_methods.txt b/tests/functional/t/too/too_few_public_methods.txt index 854a780785..24f46d08f9 100644 --- a/tests/functional/t/too/too_few_public_methods.txt +++ b/tests/functional/t/too/too_few_public_methods.txt @@ -1 +1 @@ -too-few-public-methods:8:0:17:19:Aaaa:Too few public methods (1/2):UNDEFINED +too-few-public-methods:8:0:8:10:Aaaa:Too few public methods (1/2):UNDEFINED diff --git a/tests/functional/t/too/too_few_public_methods_excluded.txt b/tests/functional/t/too/too_few_public_methods_excluded.txt index 00ceaea786..b007f9b183 100644 --- a/tests/functional/t/too/too_few_public_methods_excluded.txt +++ b/tests/functional/t/too/too_few_public_methods_excluded.txt @@ -1 +1 @@ -too-few-public-methods:4:0:5:7:Control:Too few public methods (0/10):UNDEFINED +too-few-public-methods:4:0:4:13:Control:Too few public methods (0/10):UNDEFINED diff --git a/tests/functional/t/too/too_many_ancestors.txt b/tests/functional/t/too/too_many_ancestors.txt index 204e2e5993..b4d4ecd6f5 100644 --- a/tests/functional/t/too/too_many_ancestors.txt +++ b/tests/functional/t/too/too_many_ancestors.txt @@ -1,2 +1,2 @@ -too-many-ancestors:21:0:22:8:Iiii:Too many ancestors (8/7):UNDEFINED -too-many-ancestors:24:0:25:8:Jjjj:Too many ancestors (9/7):UNDEFINED +too-many-ancestors:21:0:21:10:Iiii:Too many ancestors (8/7):UNDEFINED +too-many-ancestors:24:0:24:10:Jjjj:Too many ancestors (9/7):UNDEFINED diff --git a/tests/functional/t/too/too_many_ancestors_ignored_parents.txt b/tests/functional/t/too/too_many_ancestors_ignored_parents.txt index 6ba1a7d296..fcbafdeb05 100644 --- a/tests/functional/t/too/too_many_ancestors_ignored_parents.txt +++ b/tests/functional/t/too/too_many_ancestors_ignored_parents.txt @@ -1 +1 @@ -too-many-ancestors:39:0:40:19:A:Too many ancestors (3/2):UNDEFINED +too-many-ancestors:39:0:39:7:A:Too many ancestors (3/2):UNDEFINED diff --git a/tests/functional/t/too/too_many_arguments.txt b/tests/functional/t/too/too_many_arguments.txt index 71d1bfd395..f9749e9495 100644 --- a/tests/functional/t/too/too_many_arguments.txt +++ b/tests/functional/t/too/too_many_arguments.txt @@ -1 +1 @@ -too-many-arguments:3:0:4:63:stupid_function:Too many arguments (9/5):UNDEFINED +too-many-arguments:3:0:3:19:stupid_function:Too many arguments (9/5):UNDEFINED diff --git a/tests/functional/t/too/too_many_branches.txt b/tests/functional/t/too/too_many_branches.txt index f74a70b6aa..3d37bb6df5 100644 --- a/tests/functional/t/too/too_many_branches.txt +++ b/tests/functional/t/too/too_many_branches.txt @@ -1 +1 @@ -too-many-branches:3:0:30:12:wrong:Too many branches (13/12):UNDEFINED +too-many-branches:3:0:3:9:wrong:Too many branches (13/12):UNDEFINED diff --git a/tests/functional/t/too/too_many_instance_attributes.txt b/tests/functional/t/too/too_many_instance_attributes.txt index fd1e2e58e5..d3f1282228 100644 --- a/tests/functional/t/too/too_many_instance_attributes.txt +++ b/tests/functional/t/too/too_many_instance_attributes.txt @@ -1 +1 @@ -too-many-instance-attributes:4:0:27:26:Aaaa:Too many instance attributes (21/7):UNDEFINED +too-many-instance-attributes:4:0:4:10:Aaaa:Too many instance attributes (21/7):UNDEFINED diff --git a/tests/functional/t/too/too_many_locals.txt b/tests/functional/t/too/too_many_locals.txt index 5d2055c0c3..19272626f1 100644 --- a/tests/functional/t/too/too_many_locals.txt +++ b/tests/functional/t/too/too_many_locals.txt @@ -1,3 +1,3 @@ -too-many-locals:4:0:9:51:function:Too many local variables (16/15):UNDEFINED -too-many-locals:12:0:30:17:too_many_locals_function:Too many local variables (16/15):UNDEFINED -too-many-arguments:32:0:39:15:too_many_arguments_function:Too many arguments (6/5):UNDEFINED +too-many-locals:4:0:4:12:function:Too many local variables (16/15):UNDEFINED +too-many-locals:12:0:12:28:too_many_locals_function:Too many local variables (16/15):UNDEFINED +too-many-arguments:32:0:32:31:too_many_arguments_function:Too many arguments (6/5):UNDEFINED diff --git a/tests/functional/t/too/too_many_public_methods.txt b/tests/functional/t/too/too_many_public_methods.txt index 799640bbb1..2d5ac78ba4 100644 --- a/tests/functional/t/too/too_many_public_methods.txt +++ b/tests/functional/t/too/too_many_public_methods.txt @@ -1 +1 @@ -too-many-public-methods:3:0:72:24:Aaaa:Too many public methods (21/20):UNDEFINED +too-many-public-methods:3:0:3:10:Aaaa:Too many public methods (21/20):UNDEFINED diff --git a/tests/functional/t/too/too_many_return_statements.txt b/tests/functional/t/too/too_many_return_statements.txt index 5e4de80285..815cacf28e 100644 --- a/tests/functional/t/too/too_many_return_statements.txt +++ b/tests/functional/t/too/too_many_return_statements.txt @@ -1 +1 @@ -too-many-return-statements:3:0:24:15:stupid_function:Too many return statements (11/6):UNDEFINED +too-many-return-statements:3:0:3:19:stupid_function:Too many return statements (11/6):UNDEFINED diff --git a/tests/functional/t/too/too_many_statements.txt b/tests/functional/t/too/too_many_statements.txt index 0e74c361ba..b78c4b9a54 100644 --- a/tests/functional/t/too/too_many_statements.txt +++ b/tests/functional/t/too/too_many_statements.txt @@ -1,3 +1,3 @@ -too-many-statements:5:0:60:16:stupid_function:Too many statements (55/5):UNDEFINED -too-many-statements:62:0:125:13:long_function_with_inline_def:Too many statements (62/5):UNDEFINED -too-many-statements:128:0:133:12:exmaple_function:Too many statements (6/5):UNDEFINED +too-many-statements:5:0:5:19:stupid_function:Too many statements (55/5):UNDEFINED +too-many-statements:62:0:62:33:long_function_with_inline_def:Too many statements (62/5):UNDEFINED +too-many-statements:128:0:128:20:exmaple_function:Too many statements (6/5):UNDEFINED diff --git a/tests/functional/t/typing_use.txt b/tests/functional/t/typing_use.txt index 26d353a526..0007db21d5 100644 --- a/tests/functional/t/typing_use.txt +++ b/tests/functional/t/typing_use.txt @@ -1 +1 @@ -function-redefined:21:0:23:18:double_with_docstring:function already defined line 16:UNDEFINED +function-redefined:21:0:21:25:double_with_docstring:function already defined line 16:UNDEFINED diff --git a/tests/functional/u/undefined/undefined_variable_py30.txt b/tests/functional/u/undefined/undefined_variable_py30.txt index 364b5a1487..31c1ef9f49 100644 --- a/tests/functional/u/undefined/undefined_variable_py30.txt +++ b/tests/functional/u/undefined/undefined_variable_py30.txt @@ -4,7 +4,7 @@ undefined-variable:36:25:36:28:Undefined1.InnerScope.test1:Undefined variable 'A undefined-variable:51:28:51:32:FalsePositive342.test_bad:Undefined variable 'trop':UNDEFINED undefined-variable:54:31:54:36:FalsePositive342.test_bad1:Undefined variable 'trop1':UNDEFINED undefined-variable:57:31:57:36:FalsePositive342.test_bad2:Undefined variable 'trop2':UNDEFINED -undefined-variable:63:0:64:27:Bad:Undefined variable 'ABCMet':UNDEFINED -undefined-variable:66:0:67:35:SecondBad:Undefined variable 'ab':UNDEFINED +undefined-variable:63:0:63:9:Bad:Undefined variable 'ABCMet':UNDEFINED +undefined-variable:66:0:66:15:SecondBad:Undefined variable 'ab':UNDEFINED undefined-variable:97:53:97:61:InheritingClass:Undefined variable 'variable':UNDEFINED -undefined-variable:103:0:104:8:Inheritor:Undefined variable 'DefinedTooLate':UNDEFINED +undefined-variable:103:0:103:15:Inheritor:Undefined variable 'DefinedTooLate':UNDEFINED diff --git a/tests/functional/u/unexpected_special_method_signature.txt b/tests/functional/u/unexpected_special_method_signature.txt index 9751b6447e..dd9297f16d 100644 --- a/tests/functional/u/unexpected_special_method_signature.txt +++ b/tests/functional/u/unexpected_special_method_signature.txt @@ -1,16 +1,16 @@ -unexpected-special-method-signature:8:4:9:12:Invalid.__enter__:The special method '__enter__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:11:4:12:12:Invalid.__del__:The special method '__del__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:14:4:15:12:Invalid.__format__:The special method '__format__' expects 1 param(s), 2 were given:UNDEFINED -unexpected-special-method-signature:17:4:18:12:Invalid.__setattr__:The special method '__setattr__' expects 2 param(s), 0 was given:UNDEFINED -unexpected-special-method-signature:20:4:21:12:Invalid.__round__:The special method '__round__' expects between 0 or 1 param(s), 2 were given:UNDEFINED -unexpected-special-method-signature:23:4:24:12:Invalid.__deepcopy__:The special method '__deepcopy__' expects 1 param(s), 2 were given:UNDEFINED -no-method-argument:26:4:27:12:Invalid.__iter__:Method has no argument:UNDEFINED -unexpected-special-method-signature:30:4:31:12:Invalid.__getattr__:The special method '__getattr__' expects 1 param(s), 2 were given:UNDEFINED -unexpected-special-method-signature:37:4:38:12:FirstBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:43:4:44:12:SecondBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED -unexpected-special-method-signature:51:4:52:12:ThirdBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED -unexpected-special-method-signature:57:4:58:12:Async.__aiter__:The special method '__aiter__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:59:4:60:12:Async.__anext__:The special method '__anext__' expects 0 param(s), 2 were given:UNDEFINED -unexpected-special-method-signature:61:4:62:12:Async.__await__:The special method '__await__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:63:4:64:12:Async.__aenter__:The special method '__aenter__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:65:4:66:12:Async.__aexit__:The special method '__aexit__' expects 3 param(s), 0 was given:UNDEFINED +unexpected-special-method-signature:8:4:8:17:Invalid.__enter__:The special method '__enter__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:11:4:11:15:Invalid.__del__:The special method '__del__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:14:4:14:18:Invalid.__format__:The special method '__format__' expects 1 param(s), 2 were given:UNDEFINED +unexpected-special-method-signature:17:4:17:19:Invalid.__setattr__:The special method '__setattr__' expects 2 param(s), 0 was given:UNDEFINED +unexpected-special-method-signature:20:4:20:17:Invalid.__round__:The special method '__round__' expects between 0 or 1 param(s), 2 were given:UNDEFINED +unexpected-special-method-signature:23:4:23:20:Invalid.__deepcopy__:The special method '__deepcopy__' expects 1 param(s), 2 were given:UNDEFINED +no-method-argument:26:4:26:16:Invalid.__iter__:Method has no argument:UNDEFINED +unexpected-special-method-signature:30:4:30:19:Invalid.__getattr__:The special method '__getattr__' expects 1 param(s), 2 were given:UNDEFINED +unexpected-special-method-signature:37:4:37:16:FirstBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:43:4:43:16:SecondBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED +unexpected-special-method-signature:51:4:51:16:ThirdBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED +unexpected-special-method-signature:57:4:57:17:Async.__aiter__:The special method '__aiter__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:59:4:59:17:Async.__anext__:The special method '__anext__' expects 0 param(s), 2 were given:UNDEFINED +unexpected-special-method-signature:61:4:61:17:Async.__await__:The special method '__await__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:63:4:63:18:Async.__aenter__:The special method '__aenter__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:65:4:65:17:Async.__aexit__:The special method '__aexit__' expects 3 param(s), 0 was given:UNDEFINED diff --git a/tests/functional/u/unused/unused_private_member.txt b/tests/functional/u/unused/unused_private_member.txt index 99f34b7f53..71c95c9764 100644 --- a/tests/functional/u/unused/unused_private_member.txt +++ b/tests/functional/u/unused/unused_private_member.txt @@ -1,20 +1,20 @@ -unused-private-member:4:4:5:12:AnotherClass.__test:Unused private member `AnotherClass.__test(self)`:UNDEFINED +unused-private-member:4:4:4:14:AnotherClass.__test:Unused private member `AnotherClass.__test(self)`:UNDEFINED unused-private-member:8:4:8:15:HasUnusedInClass:Unused private member `HasUnusedInClass.__my_secret`:UNDEFINED -unused-private-member:12:4:13:35:HasUnusedInClass.__private_class_method_unused:Unused private member `HasUnusedInClass.__private_class_method_unused(cls)`:UNDEFINED -unused-private-member:20:4:21:12:HasUnusedInClass.__private_static_method_unused:Unused private member `HasUnusedInClass.__private_static_method_unused()`:UNDEFINED +unused-private-member:12:4:12:37:HasUnusedInClass.__private_class_method_unused:Unused private member `HasUnusedInClass.__private_class_method_unused(cls)`:UNDEFINED +unused-private-member:20:4:20:38:HasUnusedInClass.__private_static_method_unused:Unused private member `HasUnusedInClass.__private_static_method_unused()`:UNDEFINED unused-private-member:28:8:28:30:HasUnusedInClass.__init__:Unused private member `HasUnusedInClass.__instance_secret`:UNDEFINED -unused-private-member:34:4:38:13:HasUnusedInClass.__test:Unused private member `HasUnusedInClass.__test(self, x, y, z)`:UNDEFINED -unused-private-member:55:4:56:31:HasUnusedInClass.__test_recursive:Unused private member `HasUnusedInClass.__test_recursive(self)`:UNDEFINED +unused-private-member:34:4:34:14:HasUnusedInClass.__test:Unused private member `HasUnusedInClass.__test(self, x, y, z)`:UNDEFINED +unused-private-member:55:4:55:24:HasUnusedInClass.__test_recursive:Unused private member `HasUnusedInClass.__test_recursive(self)`:UNDEFINED unused-private-member:133:8:133:21:FalsePositive4657.__init__:Unused private member `FalsePositive4657.__attr_c`:UNDEFINED undefined-variable:138:15:138:18:FalsePositive4657.attr_c:Undefined variable 'cls':UNDEFINED unused-private-member:157:8:157:26:FalsePositive4668.__new__:Unused private member `FalsePositive4668.__unused`:UNDEFINED -unused-private-member:181:8:182:27:FalsePositive4673.do_thing.__true_positive:Unused private member `FalsePositive4673.do_thing.__true_positive(in_thing)`:UNDEFINED -unused-private-member:201:8:202:16:FalsePositive4673.complicated_example.__inner_4:Unused private member `FalsePositive4673.complicated_example.__inner_4()`:UNDEFINED +unused-private-member:181:8:181:27:FalsePositive4673.do_thing.__true_positive:Unused private member `FalsePositive4673.do_thing.__true_positive(in_thing)`:UNDEFINED +unused-private-member:201:8:201:21:FalsePositive4673.complicated_example.__inner_4:Unused private member `FalsePositive4673.complicated_example.__inner_4()`:UNDEFINED unused-private-member:212:8:212:23:Crash4755Context.__init__:Unused private member `Crash4755Context.__messages`:UNDEFINED unused-private-member:229:4:229:24:FalsePositive4681:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED unused-private-member:239:12:239:50:FalsePositive4681.__init__:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED unused-private-member:243:12:243:50:FalsePositive4681.__init__:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED -unused-private-member:274:4:275:26:FalsePositive4849.__unused_private_method:Unused private member `FalsePositive4849.__unused_private_method()`:UNDEFINED -unused-private-member:291:4:294:44:Pony.__init_defaults:Unused private member `Pony.__init_defaults(self)`:UNDEFINED -unused-private-member:296:4:298:20:Pony.__get_fur_color:Unused private member `Pony.__get_fur_color(self)`:UNDEFINED +unused-private-member:274:4:274:31:FalsePositive4849.__unused_private_method:Unused private member `FalsePositive4849.__unused_private_method()`:UNDEFINED +unused-private-member:291:4:291:23:Pony.__init_defaults:Unused private member `Pony.__init_defaults(self)`:UNDEFINED +unused-private-member:296:4:296:23:Pony.__get_fur_color:Unused private member `Pony.__get_fur_color(self)`:UNDEFINED unused-private-member:318:8:318:15:TypeSelfCallInMethod.b:Unused private member `TypeSelfCallInMethod.__a`:UNDEFINED diff --git a/tests/functional/u/unused/unused_variable_py38.txt b/tests/functional/u/unused/unused_variable_py38.txt index 62a9e8b6ce..c870838e6e 100644 --- a/tests/functional/u/unused/unused_variable_py38.txt +++ b/tests/functional/u/unused/unused_variable_py38.txt @@ -1,6 +1,6 @@ -unused-variable:4:0:8:16:typed_assignment_in_function_default:Unused variable 'typed_assignment_in_function_default':UNDEFINED +unused-variable:4:0:4:40:typed_assignment_in_function_default:Unused variable 'typed_assignment_in_function_default':UNDEFINED unused-variable:5:18:5:31::Unused variable 'typed_default':UNDEFINED -unused-variable:11:0:15:16:assignment_in_function_default:Unused variable 'assignment_in_function_default':UNDEFINED +unused-variable:11:0:11:34:assignment_in_function_default:Unused variable 'assignment_in_function_default':UNDEFINED unused-variable:12:11:12:18::Unused variable 'default':UNDEFINED -unused-variable:18:0:23:16:assignment_used_in_function_scope:Unused variable 'assignment_used_in_function_scope':UNDEFINED -unused-variable:26:0:30:16:assignment_used_in_global_scope:Unused variable 'assignment_used_in_global_scope':UNDEFINED +unused-variable:18:0:18:37:assignment_used_in_function_scope:Unused variable 'assignment_used_in_function_scope':UNDEFINED +unused-variable:26:0:26:35:assignment_used_in_global_scope:Unused variable 'assignment_used_in_global_scope':UNDEFINED diff --git a/tests/functional/u/use/use_symbolic_message_instead.txt b/tests/functional/u/use/use_symbolic_message_instead.txt index bbc1f24bd4..5a01b7cdbe 100644 --- a/tests/functional/u/use/use_symbolic_message_instead.txt +++ b/tests/functional/u/use/use_symbolic_message_instead.txt @@ -3,12 +3,12 @@ use-symbolic-message-instead:1:0:None:None::"'C0111' is cryptic: use '# pylint: use-symbolic-message-instead:1:0:None:None::"'R0903' is cryptic: use '# pylint: disable=too-few-public-methods' instead":UNDEFINED use-symbolic-message-instead:2:0:None:None::"'c0111' is cryptic: use '# pylint: enable=missing-docstring' instead":UNDEFINED use-symbolic-message-instead:2:0:None:None::"'w0223' is cryptic: use '# pylint: enable=abstract-method' instead":UNDEFINED -missing-function-docstring:4:0:5:22:my_function:Missing function or method docstring:HIGH +missing-function-docstring:4:0:4:15:my_function:Missing function or method docstring:HIGH use-symbolic-message-instead:7:0:None:None::"'C0111' is cryptic: use '# pylint: disable=missing-docstring' instead":UNDEFINED use-symbolic-message-instead:8:0:None:None::"'R0903' is cryptic: use '# pylint: enable=too-few-public-methods' instead":UNDEFINED use-symbolic-message-instead:9:0:None:None::"'R0903' is cryptic: use '# pylint: disable=too-few-public-methods' instead":UNDEFINED use-symbolic-message-instead:12:0:None:None::"'C0102' is cryptic: use '# pylint: disable=blacklisted-name' instead":UNDEFINED use-symbolic-message-instead:16:0:None:None::"'C0102' is cryptic: use '# pylint: disable=blacklisted-name' instead":UNDEFINED use-symbolic-message-instead:16:0:None:None::"'R1711' is cryptic: use '# pylint: disable=useless-return' instead":UNDEFINED -missing-function-docstring:20:0:21:8:test_enabled_by_id_msg:Missing function or method docstring:HIGH +missing-function-docstring:20:0:20:26:test_enabled_by_id_msg:Missing function or method docstring:HIGH use-symbolic-message-instead:20:0:None:None::"'C0111' is cryptic: use '# pylint: enable=missing-docstring' instead":UNDEFINED diff --git a/tests/functional/u/useless/useless_object_inheritance.txt b/tests/functional/u/useless/useless_object_inheritance.txt index fe3bd2e4a8..67f48417f9 100644 --- a/tests/functional/u/useless/useless_object_inheritance.txt +++ b/tests/functional/u/useless/useless_object_inheritance.txt @@ -1,4 +1,4 @@ -useless-object-inheritance:8:0:9:8:A:Class 'A' inherits from object, can be safely removed from bases in python3:UNDEFINED -useless-object-inheritance:14:0:15:8:C:Class 'C' inherits from object, can be safely removed from bases in python3:UNDEFINED -useless-object-inheritance:17:0:18:8:D:Class 'D' inherits from object, can be safely removed from bases in python3:UNDEFINED -useless-object-inheritance:20:0:21:8:E:Class 'E' inherits from object, can be safely removed from bases in python3:UNDEFINED +useless-object-inheritance:8:0:8:7:A:Class 'A' inherits from object, can be safely removed from bases in python3:UNDEFINED +useless-object-inheritance:14:0:14:7:C:Class 'C' inherits from object, can be safely removed from bases in python3:UNDEFINED +useless-object-inheritance:17:0:17:7:D:Class 'D' inherits from object, can be safely removed from bases in python3:UNDEFINED +useless-object-inheritance:20:0:20:7:E:Class 'E' inherits from object, can be safely removed from bases in python3:UNDEFINED diff --git a/tests/functional/u/useless/useless_return.txt b/tests/functional/u/useless/useless_return.txt index b64b76be4d..035e951ab2 100644 --- a/tests/functional/u/useless/useless_return.txt +++ b/tests/functional/u/useless/useless_return.txt @@ -1,2 +1,2 @@ -useless-return:4:0:6:10:myfunc:Useless return at end of function or method:UNDEFINED -useless-return:9:4:11:19:SomeClass.mymethod:Useless return at end of function or method:UNDEFINED +useless-return:4:0:4:10:myfunc:Useless return at end of function or method:UNDEFINED +useless-return:9:4:9:16:SomeClass.mymethod:Useless return at end of function or method:UNDEFINED diff --git a/tests/functional/u/useless/useless_super_delegation.txt b/tests/functional/u/useless/useless_super_delegation.txt index 43f52674a9..b69febe691 100644 --- a/tests/functional/u/useless/useless_super_delegation.txt +++ b/tests/functional/u/useless/useless_super_delegation.txt @@ -1,19 +1,19 @@ -useless-super-delegation:214:4:215:60:UselessSuper.equivalent_params:Useless super delegation in method 'equivalent_params':UNDEFINED -useless-super-delegation:217:4:218:67:UselessSuper.equivalent_params_1:Useless super delegation in method 'equivalent_params_1':UNDEFINED -useless-super-delegation:220:4:221:67:UselessSuper.equivalent_params_2:Useless super delegation in method 'equivalent_params_2':UNDEFINED -useless-super-delegation:223:4:224:77:UselessSuper.equivalent_params_3:Useless super delegation in method 'equivalent_params_3':UNDEFINED -useless-super-delegation:226:4:227:60:UselessSuper.equivalent_params_4:Useless super delegation in method 'equivalent_params_4':UNDEFINED -useless-super-delegation:229:4:230:67:UselessSuper.equivalent_params_5:Useless super delegation in method 'equivalent_params_5':UNDEFINED -useless-super-delegation:232:4:233:84:UselessSuper.equivalent_params_6:Useless super delegation in method 'equivalent_params_6':UNDEFINED -useless-super-delegation:235:4:237:82:UselessSuper.with_default_argument:Useless super delegation in method 'with_default_argument':UNDEFINED -useless-super-delegation:239:4:240:80:UselessSuper.without_default_argument:Useless super delegation in method 'without_default_argument':UNDEFINED -useless-super-delegation:242:4:244:80:UselessSuper.with_default_argument_none:Useless super delegation in method 'with_default_argument_none':UNDEFINED -useless-super-delegation:246:4:247:79:UselessSuper.with_default_argument_int:Useless super delegation in method 'with_default_argument_int':UNDEFINED -useless-super-delegation:249:4:250:81:UselessSuper.with_default_argument_tuple:Useless super delegation in method 'with_default_argument_tuple':UNDEFINED -useless-super-delegation:252:4:253:80:UselessSuper.with_default_argument_dict:Useless super delegation in method 'with_default_argument_dict':UNDEFINED -useless-super-delegation:255:4:256:79:UselessSuper.with_default_argument_var:Useless super delegation in method 'with_default_argument_var':UNDEFINED -useless-super-delegation:258:4:259:44:UselessSuper.__init__:Useless super delegation in method '__init__':UNDEFINED -useless-super-delegation:261:4:262:70:UselessSuper.with_default_arg:Useless super delegation in method 'with_default_arg':UNDEFINED -useless-super-delegation:264:4:265:74:UselessSuper.with_default_arg_bis:Useless super delegation in method 'with_default_arg_bis':UNDEFINED -useless-super-delegation:267:4:268:74:UselessSuper.with_default_arg_ter:Useless super delegation in method 'with_default_arg_ter':UNDEFINED -useless-super-delegation:270:4:271:75:UselessSuper.with_default_arg_quad:Useless super delegation in method 'with_default_arg_quad':UNDEFINED +useless-super-delegation:214:4:214:25:UselessSuper.equivalent_params:Useless super delegation in method 'equivalent_params':UNDEFINED +useless-super-delegation:217:4:217:27:UselessSuper.equivalent_params_1:Useless super delegation in method 'equivalent_params_1':UNDEFINED +useless-super-delegation:220:4:220:27:UselessSuper.equivalent_params_2:Useless super delegation in method 'equivalent_params_2':UNDEFINED +useless-super-delegation:223:4:223:27:UselessSuper.equivalent_params_3:Useless super delegation in method 'equivalent_params_3':UNDEFINED +useless-super-delegation:226:4:226:27:UselessSuper.equivalent_params_4:Useless super delegation in method 'equivalent_params_4':UNDEFINED +useless-super-delegation:229:4:229:27:UselessSuper.equivalent_params_5:Useless super delegation in method 'equivalent_params_5':UNDEFINED +useless-super-delegation:232:4:232:27:UselessSuper.equivalent_params_6:Useless super delegation in method 'equivalent_params_6':UNDEFINED +useless-super-delegation:235:4:235:29:UselessSuper.with_default_argument:Useless super delegation in method 'with_default_argument':UNDEFINED +useless-super-delegation:239:4:239:32:UselessSuper.without_default_argument:Useless super delegation in method 'without_default_argument':UNDEFINED +useless-super-delegation:242:4:242:34:UselessSuper.with_default_argument_none:Useless super delegation in method 'with_default_argument_none':UNDEFINED +useless-super-delegation:246:4:246:33:UselessSuper.with_default_argument_int:Useless super delegation in method 'with_default_argument_int':UNDEFINED +useless-super-delegation:249:4:249:35:UselessSuper.with_default_argument_tuple:Useless super delegation in method 'with_default_argument_tuple':UNDEFINED +useless-super-delegation:252:4:252:34:UselessSuper.with_default_argument_dict:Useless super delegation in method 'with_default_argument_dict':UNDEFINED +useless-super-delegation:255:4:255:33:UselessSuper.with_default_argument_var:Useless super delegation in method 'with_default_argument_var':UNDEFINED +useless-super-delegation:258:4:258:16:UselessSuper.__init__:Useless super delegation in method '__init__':UNDEFINED +useless-super-delegation:261:4:261:24:UselessSuper.with_default_arg:Useless super delegation in method 'with_default_arg':UNDEFINED +useless-super-delegation:264:4:264:28:UselessSuper.with_default_arg_bis:Useless super delegation in method 'with_default_arg_bis':UNDEFINED +useless-super-delegation:267:4:267:28:UselessSuper.with_default_arg_ter:Useless super delegation in method 'with_default_arg_ter':UNDEFINED +useless-super-delegation:270:4:270:29:UselessSuper.with_default_arg_quad:Useless super delegation in method 'with_default_arg_quad':UNDEFINED diff --git a/tests/functional/u/useless/useless_super_delegation_py3.txt b/tests/functional/u/useless/useless_super_delegation_py3.txt index bad62c1bda..2497504a06 100644 --- a/tests/functional/u/useless/useless_super_delegation_py3.txt +++ b/tests/functional/u/useless/useless_super_delegation_py3.txt @@ -1,2 +1,2 @@ -useless-super-delegation:21:4:22:36:UselessSuper.useless:Useless super delegation in method 'useless':UNDEFINED -useless-super-delegation:34:4:35:31:Ham.__init__:Useless super delegation in method '__init__':UNDEFINED +useless-super-delegation:21:4:21:15:UselessSuper.useless:Useless super delegation in method 'useless':UNDEFINED +useless-super-delegation:34:4:34:16:Ham.__init__:Useless super delegation in method '__init__':UNDEFINED diff --git a/tests/functional/u/useless/useless_super_delegation_py35.txt b/tests/functional/u/useless/useless_super_delegation_py35.txt index fdcf74142e..abcf6c5fb4 100644 --- a/tests/functional/u/useless/useless_super_delegation_py35.txt +++ b/tests/functional/u/useless/useless_super_delegation_py35.txt @@ -1 +1 @@ -useless-super-delegation:11:4:12:62:UselessSuper.useless:Useless super delegation in method 'useless':UNDEFINED +useless-super-delegation:11:4:11:15:UselessSuper.useless:Useless super delegation in method 'useless':UNDEFINED diff --git a/tests/functional/u/useless/useless_super_delegation_py38.txt b/tests/functional/u/useless/useless_super_delegation_py38.txt index da08eb3a75..1c4631c938 100644 --- a/tests/functional/u/useless/useless_super_delegation_py38.txt +++ b/tests/functional/u/useless/useless_super_delegation_py38.txt @@ -1 +1 @@ -useless-super-delegation:16:4:17:39:Ham.__init__:Useless super delegation in method '__init__':UNDEFINED +useless-super-delegation:16:4:16:16:Ham.__init__:Useless super delegation in method '__init__':UNDEFINED From 29480d4e119b27ab419c5d53e8a955220de61e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 20:51:24 +0100 Subject: [PATCH 258/357] Fix end position in unittests (#5904) * Fix end position in unittests * Add pragma's for warnings --- pylint/testutils/checker_test_case.py | 4 ++-- tests/checkers/unittest_base.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index f8324b9a9a..739db50810 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -73,14 +73,14 @@ def assertAddsMessages( # pylint: disable=fixme # TODO: Require end_line and end_col_offset and remove the warning if not expected_msg.end_line == gotten_msg.end_line: - warnings.warn( + warnings.warn( # pragma: no cover f"The end_line attribute of {gotten_msg} does not match " f"the expected value in {expected_msg}. In pylint 3.0 correct end_line " "attributes will be required for MessageTest.", DeprecationWarning, ) if not expected_msg.end_col_offset == gotten_msg.end_col_offset: - warnings.warn( + warnings.warn( # pragma: no cover f"The end_col_offset attribute of {gotten_msg} does not match " f"the expected value in {expected_msg}. In pylint 3.0 correct end_col_offset " "attributes will be required for MessageTest.", diff --git a/tests/checkers/unittest_base.py b/tests/checkers/unittest_base.py index fe46efcb4e..c1c76610ca 100644 --- a/tests/checkers/unittest_base.py +++ b/tests/checkers/unittest_base.py @@ -66,8 +66,8 @@ class CLASSC(object): #@ confidence=HIGH, line=2, col_offset=0, - end_line=3, - end_col_offset=8, + end_line=2, + end_col_offset=12, ) with self.assertAddsMessages(message): cls = None @@ -100,8 +100,8 @@ class CLASSC(object): #@ confidence=HIGH, line=2, col_offset=0, - end_line=3, - end_col_offset=8, + end_line=2, + end_col_offset=13, ), MessageTest( "invalid-name", @@ -114,8 +114,8 @@ class CLASSC(object): #@ confidence=HIGH, line=6, col_offset=0, - end_line=7, - end_col_offset=8, + end_line=6, + end_col_offset=12, ), ] with self.assertAddsMessages(*messages): @@ -153,7 +153,7 @@ def FUNC(): #@ confidence=HIGH, line=6, col_offset=0, - end_line=7, + end_line=6, end_col_offset=8, ) with self.assertAddsMessages(message): @@ -190,8 +190,8 @@ def UPPER(): #@ confidence=HIGH, line=8, col_offset=0, - end_line=9, - end_col_offset=8, + end_line=8, + end_col_offset=9, ) with self.assertAddsMessages(message): func = None From 5756fae16b336e8828f6d220c532dc48ddd04183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 21:31:37 +0100 Subject: [PATCH 259/357] Fix disabling of ``ungrouped-imports`` (#5903) Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ pylint/checkers/imports.py | 8 ++++---- .../functional/u/ungrouped_imports_suppression.py | 15 +++++++++++++++ .../u/ungrouped_imports_suppression.txt | 2 ++ 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 tests/functional/u/ungrouped_imports_suppression.py create mode 100644 tests/functional/u/ungrouped_imports_suppression.txt diff --git a/ChangeLog b/ChangeLog index 709c895854..528da76833 100644 --- a/ChangeLog +++ b/ChangeLog @@ -76,6 +76,11 @@ Release date: TBA Closes #5025 +* Fixed an issue where ``ungrouped-imports`` could not be disabled without raising + ``useless-suppression``. + + Ref #2366 + * Added several checkers to deal with unicode security issues (see `Trojan Sources `_ and `PEP 672 `_ for details) that also diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 2314725da5..3c3f088eac 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -216,6 +216,11 @@ Other Changes Closes #5713 +* Fixed an issue where ``ungrouped-imports`` could not be disabled without raising + ``useless-suppression``. + + Ref #2366 + * Fixed a crash on ``__init__`` nodes when the attribute was previously uninferable due to a cache limit size. This limit can be hit when the inheritance pattern of a class (and therefore of the ``__init__`` attribute) is very large. diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index bcad7692ed..689573b211 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -537,10 +537,6 @@ def leave_module(self, node: nodes.Module) -> None: met_from: Set[str] = set() # set for 'from x import y' style current_package = None for import_node, import_name in std_imports + ext_imports + loc_imports: - if not self.linter.is_message_enabled( - "ungrouped-imports", import_node.fromlineno - ): - continue met = met_from if isinstance(import_node, nodes.ImportFrom) else met_import package, _, _ = import_name.partition(".") if ( @@ -551,6 +547,10 @@ def leave_module(self, node: nodes.Module) -> None: ): self.add_message("ungrouped-imports", node=import_node, args=package) current_package = package + if not self.linter.is_message_enabled( + "ungrouped-imports", import_node.fromlineno + ): + continue met.add(package) self._imports_stack = [] diff --git a/tests/functional/u/ungrouped_imports_suppression.py b/tests/functional/u/ungrouped_imports_suppression.py new file mode 100644 index 0000000000..9b482b355a --- /dev/null +++ b/tests/functional/u/ungrouped_imports_suppression.py @@ -0,0 +1,15 @@ +"""Check ungrouped import and interaction with useless-suppression. + +Previously disabling ungrouped-imports would always lead to useless-suppression. +""" +# pylint: enable=useless-suppression +# pylint: disable=unused-import, wrong-import-order + +import logging.config +import os.path +from astroid import are_exclusive # pylint: disable=ungrouped-imports # [useless-suppression] +import logging.handlers # pylint: disable=ungrouped-imports # This should not raise useless-suppression +try: + import os # [ungrouped-imports] +except ImportError: + pass diff --git a/tests/functional/u/ungrouped_imports_suppression.txt b/tests/functional/u/ungrouped_imports_suppression.txt new file mode 100644 index 0000000000..3ba8b0ea0f --- /dev/null +++ b/tests/functional/u/ungrouped_imports_suppression.txt @@ -0,0 +1,2 @@ +useless-suppression:10:0:None:None::Useless suppression of 'ungrouped-imports':UNDEFINED +ungrouped-imports:13:4:13:13::Imports from package os are not grouped:UNDEFINED From 9d46885ab0dba902ce6a0aecfbec90bcbef1bee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 22:11:55 +0100 Subject: [PATCH 260/357] Use ``functools.cached_property`` on 3.8+ (#5907) --- pylint/checkers/classes/class_checker.py | 13 +++++++++--- pylint/checkers/design_analysis.py | 9 +++++++- .../refactoring/refactoring_checker.py | 21 +++++++++++++++++-- pylint/checkers/typecheck.py | 11 ++++++++-- pylint/checkers/variables.py | 14 ++++++++++--- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 8dbb58fd4a..dd9a0b31ce 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -51,8 +51,9 @@ """Classes checker for Python code.""" import collections +import sys from itertools import chain, zip_longest -from typing import Dict, List, Pattern, Set +from typing import TYPE_CHECKING, Dict, List, Pattern, Set import astroid from astroid import bases, nodes @@ -84,6 +85,12 @@ from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option +if sys.version_info >= (3, 8) or TYPE_CHECKING: + from functools import cached_property +else: + # pylint: disable-next=ungrouped-imports + from astroid.decorators import cachedproperty as cached_property + INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"} ASTROID_TYPE_COMPARATORS = { @@ -778,11 +785,11 @@ def open(self) -> None: py_version = get_global_option(self, "py-version") self._py38_plus = py_version >= (3, 8) - @astroid.decorators.cachedproperty + @cached_property def _dummy_rgx(self): return get_global_option(self, "dummy-variables-rgx", default=None) - @astroid.decorators.cachedproperty + @cached_property def _ignore_mixin(self): return get_global_option(self, "ignore-mixin-members", default=True) diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index 94569d4ea8..37cd16c571 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -31,6 +31,7 @@ """Check for signs of poor design.""" import re +import sys from collections import defaultdict from typing import TYPE_CHECKING, FrozenSet, Iterator, List, Set, cast @@ -42,6 +43,12 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker +if sys.version_info >= (3, 8) or TYPE_CHECKING: + from functools import cached_property +else: + # pylint: disable-next=ungrouped-imports + from astroid.decorators import cachedproperty as cached_property + if TYPE_CHECKING: from pylint.lint import PyLinter @@ -441,7 +448,7 @@ def _inc_all_stmts(self, amount): for i, _ in enumerate(self._stmts): self._stmts[i] += amount - @astroid.decorators.cachedproperty + @cached_property def _ignored_argument_names(self): return utils.get_global_option(self, "ignored-argument-names", default=None) diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index b727f1b632..07fcff802c 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -4,9 +4,19 @@ import collections import copy import itertools +import sys import tokenize from functools import reduce -from typing import Dict, Iterator, List, NamedTuple, Optional, Tuple, Union +from typing import ( + TYPE_CHECKING, + Dict, + Iterator, + List, + NamedTuple, + Optional, + Tuple, + Union, +) import astroid from astroid import nodes @@ -17,6 +27,13 @@ from pylint.checkers import utils from pylint.checkers.utils import node_frame_class +if sys.version_info >= (3, 8) or TYPE_CHECKING: + # pylint: disable-next=ungrouped-imports + from functools import cached_property +else: + # pylint: disable-next=ungrouped-imports + from astroid.decorators import cachedproperty as cached_property + KNOWN_INFINITE_ITERATORS = {"itertools.count"} BUILTIN_EXIT_FUNCS = frozenset(("quit", "exit")) CALLS_THAT_COULD_BE_REPLACED_BY_WITH = frozenset( @@ -475,7 +492,7 @@ def open(self): # do this in open since config not fully initialized in __init__ self._never_returning_functions = set(self.config.never_returning_functions) - @astroid.decorators.cachedproperty + @cached_property def _dummy_rgx(self): return lint_utils.get_global_option(self, "dummy-variables-rgx", default=None) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index b28815e704..78e92becb8 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -109,6 +109,13 @@ from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option +if sys.version_info >= (3, 8) or TYPE_CHECKING: + # pylint: disable-next=ungrouped-imports + from functools import cached_property +else: + # pylint: disable-next=ungrouped-imports + from astroid.decorators import cachedproperty as cached_property + if TYPE_CHECKING: from pylint.lint import PyLinter @@ -937,11 +944,11 @@ def open(self) -> None: self._py310_plus = py_version >= (3, 10) self._mixin_class_rgx = get_global_option(self, "mixin-class-rgx") - @astroid.decorators.cachedproperty + @cached_property def _suggestion_mode(self): return get_global_option(self, "suggestion-mode", default=True) - @astroid.decorators.cachedproperty + @cached_property def _compiled_generated_members(self) -> Tuple[Pattern, ...]: # do this lazily since config not fully initialized in __init__ # generated_members may contain regular expressions diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1781866aa2..c03d9b667c 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -60,6 +60,7 @@ import itertools import os import re +import sys from enum import Enum from functools import lru_cache from typing import ( @@ -90,6 +91,13 @@ ) from pylint.utils import get_global_option +if sys.version_info >= (3, 8) or TYPE_CHECKING: + # pylint: disable-next=ungrouped-imports + from functools import cached_property +else: + # pylint: disable-next=ungrouped-imports + from astroid.decorators import cachedproperty as cached_property + if TYPE_CHECKING: from pylint.lint import PyLinter @@ -1810,15 +1818,15 @@ def visit_arguments(self, node: nodes.Arguments) -> None: self._store_type_annotation_node(annotation) # Relying on other checker's options, which might not have been initialized yet. - @astroid.decorators.cachedproperty + @cached_property def _analyse_fallback_blocks(self): return get_global_option(self, "analyse-fallback-blocks", default=False) - @astroid.decorators.cachedproperty + @cached_property def _ignored_modules(self): return get_global_option(self, "ignored-modules", default=[]) - @astroid.decorators.cachedproperty + @cached_property def _allow_global_unused_variables(self): return get_global_option(self, "allow-global-unused-variables", default=True) From 8d57195ddcad664eeed6c7f7d07efd4513a65336 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 13 Mar 2022 01:32:32 -0500 Subject: [PATCH 261/357] Add regression test for #5771 (#5908) --- ChangeLog | 6 ++++++ doc/whatsnew/2.13.rst | 6 ++++++ pylint/checkers/variables.py | 4 +--- tests/functional/u/unused/unused_argument.py | 15 +++++++++++++++ tests/functional/u/unused/unused_argument.txt | 1 + 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 528da76833..a78a1d0048 100644 --- a/ChangeLog +++ b/ChangeLog @@ -154,6 +154,12 @@ Release date: TBA Closes #5408 Ref PyCQA/astroid#1392 +* Fixed false positive for ``unused-argument`` when a method overridden in a subclass + does nothing with the value of a keyword-only argument. + + Closes #5771 + Ref PyCQA/astroid#1382 + * The issue template for crashes is now created for crashes which were previously not covered by this mechanism. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 3c3f088eac..9ddbc2ee7e 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -247,6 +247,12 @@ Other Changes Closes #5408 Ref PyCQA/astroid#1392 +* Fixed false positive for ``unused-argument`` when a method overridden in a subclass + does nothing with the value of a keyword-only argument. + + Closes #5771 + Ref PyCQA/astroid#1382 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index c03d9b667c..b61c5a68ae 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2310,9 +2310,7 @@ def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names): if global_names and _import_name_is_global(stmt, global_names): return - argnames = list( - itertools.chain(node.argnames(), [arg.name for arg in node.args.kwonlyargs]) - ) + argnames = node.argnames() # Care about functions with unknown argument (builtins) if name in argnames: self._check_unused_arguments(name, node, stmt, argnames) diff --git a/tests/functional/u/unused/unused_argument.py b/tests/functional/u/unused/unused_argument.py index a3fd4e6abc..91243ce753 100644 --- a/tests/functional/u/unused/unused_argument.py +++ b/tests/functional/u/unused/unused_argument.py @@ -87,3 +87,18 @@ class BBBB(object): def __init__(self, arg): # [unused-argument] """Constructor with an extra parameter. Should raise a warning""" self.spam = 1 + + +# Regression test for https://github.com/PyCQA/pylint/issues/5771 +# involving keyword-only arguments +class Ancestor: + def __init__(self): + self.thing = None + + def set_thing(self, thing, *, other=None): # [unused-argument] + self.thing = thing + +class Descendant(Ancestor): + def set_thing(self, thing, *, other=None): + """Subclass does not raise unused-argument""" + self.thing = thing diff --git a/tests/functional/u/unused/unused_argument.txt b/tests/functional/u/unused/unused_argument.txt index 9d7a54b604..f73b9b5284 100644 --- a/tests/functional/u/unused/unused_argument.txt +++ b/tests/functional/u/unused/unused_argument.txt @@ -6,3 +6,4 @@ unused-argument:61:21:61:24:AAAA.method:Unused argument 'arg':INFERENCE unused-argument:68:0:None:None:AAAA.selected:Unused argument 'args':INFERENCE unused-argument:68:0:None:None:AAAA.selected:Unused argument 'kwargs':INFERENCE unused-argument:87:23:87:26:BBBB.__init__:Unused argument 'arg':INFERENCE +unused-argument:98:34:98:39:Ancestor.set_thing:Unused argument 'other':INFERENCE From 0b9a07254e8dcb336c9f785e8537fa2236046bd0 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Mar 2022 09:22:17 +0100 Subject: [PATCH 262/357] Create a maintener section in contributors.txt and cleanup --- CONTRIBUTORS.txt | 87 ++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index ec76f57d7b..1ec04b94f1 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -1,48 +1,43 @@ +Maintainers +----------- +- Claudiu Popa +- Pierre Sassoulas +- Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> +- Marc Mueller <30130371+cdce8p@users.noreply.github.com> +- Hippo91 +- Łukasz Rogalski +- Ashley Whetter +- Bryce Guinta +- Jacob Walls +- Dimitri Prybysh + - multiple-imports, not-iterable, not-a-mapping, various patches. + +- Roy Williams (Lyft) + - added check for implementing __eq__ without implementing __hash__, + - Added Python 3 check for accessing Exception.message. + - Added Python 3 check for calling encode/decode with invalid codecs. + - Added Python 3 check for accessing sys.maxint. + - Added Python 3 check for bad import statements. + - Added Python 3 check for accessing deprecated methods on the 'string' module, + various patches. + +- Florian Bruhin +- Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> +- Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> +- Arianna Yang + +Ex-maintainers +-------------- +- Sylvain Thénault : main author / maintainer +- Torsten Marek + Contributors ------------ -Current team: - -* Ashley Whetter: maintainer, contributor - -* Bryce Guinta: maintainer, contributor - -* Claudiu Popa: maintainer, contributor - -* Cara Vinson: astroid committer. - -* Guillaume Peillex: committer - -* Łukasz Rogalski: committer. - -* Roy Williams (Lyft): committer - - added check for implementing __eq__ without implementing __hash__, - Added Python 3 check for accessing Exception.message. - Added Python 3 check for calling encode/decode with invalid codecs. - Added Python 3 check for accessing sys.maxint. - Added Python 3 check for bad import statements. - Added Python 3 check for accessing deprecated methods on the 'string' module, - various patches. - -* Dmitri Prybysh: committer - - multiple-imports, not-iterable, not-a-mapping, various patches. - -* Jim Robertson: committer - -Ex-maintainers: - -* Sylvain Thenault (Logilab): main author / maintainer - -* Torsten Marek (Google): committer / contributor - - We would not be here without folks that contributed patches, pull requests, issues and their time to pylint. We're incredibly grateful to all of these contributors: - * Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant), various patches @@ -150,8 +145,6 @@ contributors: * Daniel Miller: contributor. -* Bryce Guinta: contributor - * Martin Bašti: contributor Added new check for shallow copy of os.environ Added new check for useless `with threading.Lock():` statement @@ -289,8 +282,6 @@ contributors: * Michael Scott Cuthbert: contributor -* Pierre Sassoulas : maintainer, contributor - * Nathan Marrow * Taewon Kim : contributor @@ -447,8 +438,6 @@ contributors: * Matthew Suozzo: contributor -* Marc Mueller (cdce8p): contributor - * David Gilman: contributor * Ikraduya Edian: contributor @@ -472,8 +461,6 @@ contributors: * James Sinclair (irgeek): contributor -* Andreas Finkler: contributor - * Aidan Haase, Elizabeth Bott: contributor * Sebastian Müller: contributor @@ -490,13 +477,9 @@ contributors: * Andrew Haigh (nelfin): contributor -* Pang Yu Shao (yushao2): contributor - * Aditya Gupta (adityagupta1089) : contributor - Added ignore_signatures to duplicate checker -* Jacob Walls: contributor - * ruro: contributor * David Liu (david-yz-liu): contributor @@ -536,8 +519,6 @@ contributors: * Eisuke Kawashima (e-kwsm): contributor -* Daniel van Noord (DanielNoord): contributor - * Michal Vasilek: contributor * Kai Mueller (kasium): contributor @@ -567,8 +548,6 @@ contributors: * Youngsoo Sung: contributor -* Arianna Yang: contributor - * Samuel Freilich (sfreilich): contributor * Mike Fiedler (miketheman): contributor From d9c78bded2a40111b09d69e9828525020be9e357 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Mar 2022 09:27:25 +0100 Subject: [PATCH 263/357] Remove superfluous role for contributors in contributors.txt --- CONTRIBUTORS.txt | 362 +++++++++++++++++++++++------------------------ 1 file changed, 181 insertions(+), 181 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 1ec04b94f1..5c9d21d754 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -102,7 +102,7 @@ contributors: * Aru Sahni: Git ignoring, regex-based ignores -* Mike Frysinger: contributor. +* Mike Frysinger * Moisés López (Vauxoo): Support for deprecated-modules in modules not installed, Refactor wrong-import-order to integrate it with `isort` library @@ -113,7 +113,7 @@ contributors: * Luis Escobar (Vauxoo), Moisés López (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty -* Yannick Brehon: contributor. +* Yannick Brehon * Glenn Matthews: autogenerated documentation for optional extensions, bug fixes and enhancements for docparams (née check_docs) extension @@ -133,7 +133,7 @@ contributors: * Anthony Foglia (Google): Added simple string slots check. -* Derek Gustafson: contributor +* Derek Gustafson * Petr Pulc: require whitespace around annotations @@ -143,27 +143,27 @@ contributors: * Ahirnish Pareek, 'keyword-arg-before-var-arg' check -* Daniel Miller: contributor. +* Daniel Miller -* Martin Bašti: contributor +* Martin Bašti Added new check for shallow copy of os.environ Added new check for useless `with threading.Lock():` statement -* Jacques Kvam: contributor +* Jacques Kvam * Brian Shaginaw: prevent error on exception check for functions * Ioana Tagirta: fix bad thread instantiation check -* Reverb Chu: contributor +* Reverb Chu -* Tobias Hernstig: contributor +* Tobias Hernstig -* Konstantin Manna: contributor +* Konstantin Manna * Andreas Freimuth: fix indentation checking with tabs -* Renat Galimov: contributor +* Renat Galimov * Thomas Snowden: fix missing-docstring for inner functions @@ -171,15 +171,15 @@ contributors: * Marianna Polatoglou: minor contribution for wildcard import check -* Ben Green: contributor +* Ben Green -* Benjamin Freeman: contributor +* Benjamin Freeman -* Fureigh: contributor +* Fureigh * Jace Browning: updated default report format with clickable paths -* Sushobhit (sushobhit27): contributor +* Sushobhit (sushobhit27) Added new check 'comparison-with-itself'. Added new check 'useless-import-alias'. Added support of annotations in missing-type-doc and missing-return-type-doc. @@ -188,44 +188,44 @@ contributors: Added new check 'chained-comparison'. Added new check 'useless-object-inheritance'. -* Mariatta Wijaya: contributor +* Mariatta Wijaya Added new check `logging-fstring-interpolation` Documentation typo fixes -* Jason Owen: contributor +* Jason Owen * Mark Roman Miller: fix inline defs in too-many-statements -* Adam Dangoor: contributor +* Adam Dangoor -* Gary Tyler McLeod: contributor +* Gary Tyler McLeod * Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, Maarten ter Huurne, Mirko Friedenhagen and all the Logilab's team (among others). -* Matej Marusak: contributor +* Matej Marusak -* Nick Drozd: contributor, performance improvements to astroid +* Nick Drozd, performance improvements to astroid -* Kosarchuk Sergey: contributor +* Kosarchuk Sergey -* Kurian Benoy: contributor +* Kurian Benoy * Carey Metcalfe: demoted `try-except-raise` from error to warning -* Marcus Näslund (naslundx): contributor +* Marcus Näslund (naslundx) -* Natalie Serebryakova: contributor +* Natalie Serebryakova -* Caio Carrara: contributor +* Caio Carrara * Roberto Leinardi: PyCharm plugin maintainer -* Aivar Annamaa: contributor +* Aivar Annamaa * Hornwitser: fix import graph -* Yuri Gribov: contributor +* Yuri Gribov * Drew Risinger: committer (docs) @@ -233,28 +233,28 @@ contributors: * Tomer Chachamu, Richard Goodman: simplifiable-if-expression -* Alan Chan: contributor +* Alan Chan * Benjamin Drung: contributing Debian Developer -* Scott Worley: contributor +* Scott Worley * Michael Hudson-Doyle -* Lucas Cimon: contributor +* Lucas Cimon -* Mike Miller: contributor +* Mike Miller -* Sergei Lebedev: contributor +* Sergei Lebedev * Sasha Bagan -* Pablo Galindo Salgado: contributor +* Pablo Galindo Salgado Fix false positive 'Non-iterable value' with async comprehensions. * Matus Valo -* Sardorbek Imomaliev: contributor +* Sardorbek Imomaliev * Justin Li (justinnhli) @@ -262,329 +262,329 @@ contributors: * Pascal Corpet -* Svetoslav Neykov: contributor +* Svetoslav Neykov -* Federico Bond: contributor +* Federico Bond -* Fantix King (UChicago): contributor +* Fantix King (UChicago) -* Yory (yory8): contributor +* Yory (yory8) -* Thomas Hisch: contributor +* Thomas Hisch -* Clément Pit-Claudel : contributor +* Clément Pit-Claudel -* Goudcode: contributor +* Goudcode -* Paul Renvoise : contributor +* Paul Renvoise -* Bluesheeptoken: contributor +* Bluesheeptoken -* Michael Scott Cuthbert: contributor +* Michael Scott Cuthbert * Nathan Marrow -* Taewon Kim : contributor +* Taewon Kim -* Daniil Kharkov: contributor +* Daniil Kharkov -* Tyler N. Thieding: contributor +* Tyler N. Thieding -* Zeb Nicholls: contributor +* Zeb Nicholls - Made W9011 compatible with 'of' syntax in return types -* Martin Vielsmaier: contributor +* Martin Vielsmaier -* Agustin Toledo: contributor +* Agustin Toledo -* Nicholas Smith: contributor +* Nicholas Smith -* Peter Kolbus (Garmin): contributor +* Peter Kolbus (Garmin) -* Oisin Moran: contributor +* Oisin Moran -* Andrzej Klajnert: contributor +* Andrzej Klajnert -* Andrés Pérez Hortal: contributor +* Andrés Pérez Hortal -* Niko Wenselowski: contributor +* Niko Wenselowski -* Danny Hermes: contributor +* Danny Hermes -* Eric Froemling: contributor +* Eric Froemling -* Robert Schweizer: contributor +* Robert Schweizer -* Hugo van Kemenade: contributor +* Hugo van Kemenade -* Mikhail Fesenko: contributor +* Mikhail Fesenko -* Trevor Bekolay: contributor +* Trevor Bekolay - Added --list-msgs-enabled command -* Rémi Cardona: contributor +* Rémi Cardona -* Daniel Draper: contributor +* Daniel Draper -* Gabriel R. Sezefredo: contributor +* Gabriel R. Sezefredo - Fixed "exception-escape" false positive with generators -* laike9m: contributor +* laike9m -* Janne Rönkkö: contributor +* Janne Rönkkö -* Hugues Bruant: contributor +* Hugues Bruant -* Tim Gates: contributor +* Tim Gates -* Enji Cooper: contributor +* Enji Cooper -* Bastien Vallet: contributor +* Bastien Vallet -* Pek Chhan: contributor +* Pek Chhan -* Craig Henriques: contributor +* Craig Henriques -* Matthijs Blom: contributor +* Matthijs Blom -* Andy Palmer: contributor +* Andy Palmer * Wes Turner (Google): added new check 'inconsistent-quotes' * Athos Ribeiro Fixed dict-keys-not-iterating false positive for inverse containment checks -* Anubhav: contributor +* Anubhav -* Ben Graham: contributor +* Ben Graham -* Anthony Tan: contributor +* Anthony Tan -* Benny Müller: contributor +* Benny Müller -* Bernie Gray: contributor +* Bernie Gray -* Slavfox: contributor +* Slavfox -* Matthew Beckers (mattlbeck): contributor +* Matthew Beckers (mattlbeck) -* Yang Yang: contributor +* Yang Yang -* Andrew J. Simmons (anjsimmo): contributor +* Andrew J. Simmons (anjsimmo) -* Damien Baty: contributor +* Damien Baty -* Daniel R. Neal (danrneal): contributor +* Daniel R. Neal (danrneal) -* Jeremy Fleischman (jfly): contributor +* Jeremy Fleischman (jfly) * Shiv Venkatasubrahmanyam -* Jochen Preusche (iilei): contributor +* Jochen Preusche (iilei) * Ram Rachum (cool-RR) -* D. Alphus (Alphadelta14): contributor +* D. Alphus (Alphadelta14) * Pieter Engelbrecht -* Ethan Leba: contributor +* Ethan Leba -* Matěj Grabovský: contributor +* Matěj Grabovský -* Yeting Li (yetingli): contributor +* Yeting Li (yetingli) -* Frost Ming (frostming): contributor +* Frost Ming (frostming) -* Luigi Bertaco Cristofolini (luigibertaco): contributor +* Luigi Bertaco Cristofolini (luigibertaco) * Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only -* Ganden Schaffner: contributor +* Ganden Schaffner -* Josselin Feist: contributor +* Josselin Feist -* David Cain: contributor +* David Cain -* Pedro Algarvio (s0undt3ch): contributor +* Pedro Algarvio (s0undt3ch) -* Luigi Bertaco Cristofolini (luigibertaco): contributor +* Luigi Bertaco Cristofolini (luigibertaco) -* Or Bahari: contributor +* Or Bahari -* Joshua Cannon: contributor +* Joshua Cannon -* Giuseppe Valente: contributor +* Giuseppe Valente -* Takashi Hirashima: contributor +* Takashi Hirashima -* Joffrey Mander: contributor +* Joffrey Mander -* Julien Palard: contributor +* Julien Palard -* Raphael Gaschignard: contributor +* Raphael Gaschignard -* Sorin Sbarnea: contributor +* Sorin Sbarnea -* Gergely Kalmár: contributor +* Gergely Kalmár -* Batuhan Taskaya: contributor +* Batuhan Taskaya -* Frank Harrison (doublethefish): contributor +* Frank Harrison (doublethefish) -* Gauthier Sebaux: contributor +* Gauthier Sebaux -* Logan Miller (komodo472): contributor +* Logan Miller (komodo472) -* Matthew Suozzo: contributor +* Matthew Suozzo -* David Gilman: contributor +* David Gilman -* Ikraduya Edian: contributor +* Ikraduya Edian - Added new checks 'consider-using-generator' and 'use-a-generator'. -* Tiago Honorato: contributor +* Tiago Honorato -* Lefteris Karapetsas: contributor +* Lefteris Karapetsas -* Louis Sautier: contributor +* Louis Sautier -* Quentin Young: contributor +* Quentin Young -* Alexander Kapshuna: contributor +* Alexander Kapshuna -* Mark Byrne: contributor +* Mark Byrne -* Konstantina Saketou: contributor +* Konstantina Saketou -* Andrew Howe: contributor +* Andrew Howe -* James Sinclair (irgeek): contributor +* James Sinclair (irgeek) -* Aidan Haase, Elizabeth Bott: contributor +* Aidan Haase, Elizabeth Bott -* Sebastian Müller: contributor +* Sebastian Müller * Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp -* manderj: contributor +* manderj -* qwiddle: contributor +* qwiddle -* das-intensity: contributor +* das-intensity -* Jiajunsu (victor): contributor +* Jiajunsu (victor) -* Andrew Haigh (nelfin): contributor +* Andrew Haigh (nelfin) -* Aditya Gupta (adityagupta1089) : contributor +* Aditya Gupta (adityagupta1089) - Added ignore_signatures to duplicate checker -* ruro: contributor +* ruro -* David Liu (david-yz-liu): contributor +* David Liu (david-yz-liu) -* Bernard Nauwelaerts: contributor +* Bernard Nauwelaerts -* Fabian Damken: contributor +* Fabian Damken -* Markus Siebenhaar: contributor +* Markus Siebenhaar -* Lorena Buciu (lorena-b): contributor +* Lorena Buciu (lorena-b) -* Sergei Lebedev (superbobry): contributor +* Sergei Lebedev (superbobry) -* Maksym Humetskyi (mhumetskyi): contributor +* Maksym Humetskyi (mhumetskyi) - Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled - Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled - Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled -* Daniel Dorani (doranid): contributor +* Daniel Dorani (doranid) -* Will Shanks: contributor +* Will Shanks -* Mark Bell: contributor +* Mark Bell -* Marco Gorelli: contributor +* Marco Gorelli - Documented Jupyter integration -* Rebecca Turner (9999years): contributor +* Rebecca Turner (9999years) -* Yilei Yang: contributor +* Yilei Yang -* Marcin Kurczewski (rr-): contributor +* Marcin Kurczewski (rr-) -* Tanvi Moharir: contributor +* Tanvi Moharir - Fix for invalid toml config -* Eisuke Kawashima (e-kwsm): contributor +* Eisuke Kawashima (e-kwsm) -* Michal Vasilek: contributor +* Michal Vasilek -* Kai Mueller (kasium): contributor +* Kai Mueller (kasium) -* Sam Vermeiren (PaaEl): contributor +* Sam Vermeiren (PaaEl) -* Phil A. (flying-sheep): contributor +* Phil A. (flying-sheep) -* Melvin Hazeleger (melvio): contributor +* Melvin Hazeleger (melvio) -* Hayden Richards (SupImDos): contributor +* Hayden Richards (SupImDos) - Fixed "no-self-use" for async methods - Fixed "docparams" extension for async functions and methods -* Jeroen Seegers (jeroenseegers): contributor +* Jeroen Seegers (jeroenseegers) - Fixed `toml` dependency issue -* Tim Martin: contributor +* Tim Martin -* Jaehoon Hwang (jaehoonhwang): contributor +* Jaehoon Hwang (jaehoonhwang) -* Samuel Forestier: contributor +* Samuel Forestier -* Nick Pesce: contributor +* Nick Pesce -* James DesLauriers: contributor +* James DesLauriers -* Youngsoo Sung: contributor +* Youngsoo Sung -* Samuel Freilich (sfreilich): contributor +* Samuel Freilich (sfreilich) -* Mike Fiedler (miketheman): contributor +* Mike Fiedler (miketheman) -* Takahide Nojima: contributor +* Takahide Nojima -* Tushar Sadhwani (tusharsadhwani): contributor +* Tushar Sadhwani (tusharsadhwani) -* Ikraduya Edian: contributor +* Ikraduya Edian -* Antonio Quarta (sgheppy): contributor +* Antonio Quarta (sgheppy) -* Harshil (harshil21): contributor +* Harshil (harshil21) -* Jérome Perrin (perrinjerome): contributor +* Jérome Perrin (perrinjerome) -* Felix von Drigalski (felixvd): contributor +* Felix von Drigalski (felixvd) -* Jake Lishman (jakelishman): contributor +* Jake Lishman (jakelishman) -* Philipp Albrecht (pylbrecht): contributor +* Philipp Albrecht (pylbrecht) -* Allan Chandler (allanc65): contributor +* Allan Chandler (allanc65) - Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params -* Eero Vuojolahti: contributor +* Eero Vuojolahti -* Kian-Meng, Ang: contributor +* Kian-Meng, Ang -* Nuzula H. Yudaka (Nuzhuka): contributor +* Nuzula H. Yudaka (Nuzhuka) -* Carli Freudenberg (CarliJoy): contributor +* Carli Freudenberg (CarliJoy) - Fixed issue 5281, added Unicode checker - Improve non-ascii-name checker -* Daniel Brookman: contributor +* Daniel Brookman -* Téo Bouvard: contributor +* Téo Bouvard -* Konrad Weihmann: contributor +* Konrad Weihmann From 82f60059bc4e725e993bbd9ddd0134f8abd367b3 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Mar 2022 09:38:55 +0100 Subject: [PATCH 264/357] Remove superfluous role 'committer' in contributors.txt --- CONTRIBUTORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 5c9d21d754..06be0fa3cf 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -227,7 +227,7 @@ contributors: * Yuri Gribov -* Drew Risinger: committer (docs) +* Drew Risinger * Ben James From 3d6bab0b8e01f7a2492943145a961a25b84203e2 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Mar 2022 09:40:46 +0100 Subject: [PATCH 265/357] Upgrade developments guides for CONTRIBUTORS.txt --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- doc/development_guide/contribute.rst | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5cb6329f62..46b9eb71a1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Thank you for submitting a PR to pylint! To ease the process of reviewing your PR, do make sure to complete the following boxes. -- [ ] Add yourself to CONTRIBUTORS if you are a new contributor. +- [ ] Add yourself to ``CONTRIBUTORS.txt`` if you are a new contributor. - [ ] Add a ChangeLog entry describing what your PR does. - [ ] If it's a new feature, or an important bug fix, add a What's New entry in `doc/whatsnew/`. diff --git a/doc/development_guide/contribute.rst b/doc/development_guide/contribute.rst index 8e484a5147..aa975bb278 100644 --- a/doc/development_guide/contribute.rst +++ b/doc/development_guide/contribute.rst @@ -87,8 +87,7 @@ of Pylint as it gives you access to the latest ``ast`` parser. - Add a short entry in :file:`doc/whatsnew/VERSION.rst`. -- Add yourself to the `CONTRIBUTORS` file, flag yourself appropriately - (if in doubt, you're a ``contributor``). +- Add yourself to the `CONTRIBUTORS.txt` file. - Write a comprehensive commit message From 8a0b23d9d59b968cbe9fee623d038d18577ade44 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Mar 2022 09:52:31 +0100 Subject: [PATCH 266/357] Uniformize formatting in CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 606 ++++++++++++++++++++++++----------------------- 1 file changed, 307 insertions(+), 299 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 06be0fa3cf..2ae3149181 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -10,15 +10,15 @@ Maintainers - Bryce Guinta - Jacob Walls - Dimitri Prybysh - - multiple-imports, not-iterable, not-a-mapping, various patches. + * multiple-imports, not-iterable, not-a-mapping, various patches. - Roy Williams (Lyft) - - added check for implementing __eq__ without implementing __hash__, - - Added Python 3 check for accessing Exception.message. - - Added Python 3 check for calling encode/decode with invalid codecs. - - Added Python 3 check for accessing sys.maxint. - - Added Python 3 check for bad import statements. - - Added Python 3 check for accessing deprecated methods on the 'string' module, + * added check for implementing __eq__ without implementing __hash__, + * Added Python 3 check for accessing Exception.message. + * Added Python 3 check for calling encode/decode with invalid codecs. + * Added Python 3 check for accessing sys.maxint. + * Added Python 3 check for bad import statements. + * Added Python 3 check for accessing deprecated methods on the 'string' module, various patches. - Florian Bruhin @@ -38,553 +38,561 @@ We would not be here without folks that contributed patches, pull requests, issues and their time to pylint. We're incredibly grateful to all of these contributors: -* Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant), +- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant), various patches -* Martin Pool (Google): warnings for anomalous backslashes, symbolic names for +- Martin Pool (Google): warnings for anomalous backslashes, symbolic names for messages (like 'unused'), etc -* Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support +- Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support -* Julien Cristau, Emile Anclin (Logilab): python 3 support +- Julien Cristau, Emile Anclin (Logilab): python 3 support -* Sandro Tosi: Debian packaging +- Sandro Tosi: Debian packaging -* Mads Kiilerich, Boris Feld, Bill Wendling, Sebastian Ulrich: +- Mads Kiilerich, Boris Feld, Bill Wendling, Sebastian Ulrich: various patches -* Brian van den Broek: windows installation documentation +- Brian van den Broek: windows installation documentation -* Amaury Forgeot d'Arc: check names imported from a module exists in the module +- Amaury Forgeot d'Arc: check names imported from a module exists in the module -* Benjamin Niemann: allow block level enabling/disabling of messages +- Benjamin Niemann: allow block level enabling/disabling of messages -* Nathaniel Manista: suspicious lambda checking +- Nathaniel Manista: suspicious lambda checking -* David Shea: invalid sequence and slice index +- David Shea: invalid sequence and slice index -* Carl Crowder: don't evaluate the value of arguments for 'dangerous-default-value' +- Carl Crowder: don't evaluate the value of arguments for 'dangerous-default-value' -* Michal Nowikowski: wrong-spelling-in-comment, wrong-spelling-in-docstring, +- Michal Nowikowski: wrong-spelling-in-comment, wrong-spelling-in-docstring, parallel execution on multiple CPUs and other patches. -* David Lindquist: logging-format-interpolation warning. +- David Lindquist: logging-format-interpolation warning. -* Brett Cannon: Port source code to be Python 2/3 compatible, Python 3 +- Brett Cannon: Port source code to be Python 2/3 compatible, Python 3 checker. -* Vlad Temian: redundant-unittest-assert and the JSON reporter. +- Vlad Temian: redundant-unittest-assert and the JSON reporter. -* Cosmin Poieană: unichr-builtin and improvements to bad-open-mode. +- Cosmin Poieană: unichr-builtin and improvements to bad-open-mode. -* Viorel Știrbu: intern-builtin warning. +- Viorel Știrbu: intern-builtin warning. -* Dan Goldsmith: support for msg-template in HTML reporter. +- Dan Goldsmith: support for msg-template in HTML reporter. -* Chris Rebert: unidiomatic-typecheck. +- Chris Rebert: unidiomatic-typecheck. -* Steven Myint: duplicate-except. +- Steven Myint: duplicate-except. -* Radu Ciorba: not-context-manager and confusing-with-statement warnings. +- Radu Ciorba: not-context-manager and confusing-with-statement warnings. -* Bruno Daniel: check_docs extension. +- Bruno Daniel: check_docs extension. -* James Morgensen: ignored-modules option applies to import errors. +- James Morgensen: ignored-modules option applies to import errors. -* Cezar Elnazli: deprecated-method +- Cezar Elnazli: deprecated-method -* Stéphane Wirtel: nonlocal-without-binding +- Stéphane Wirtel: nonlocal-without-binding -* Laura Medioni (Logilab, on behalf of the CNES): misplaced-comparison-constant, - no-classmethod-decorator, no-staticmethod-decorator, too-many-nested-blocks, - too-many-boolean-expressions, unneeded-not, wrong-import-order, ungrouped-imports, - wrong-import-position, redefined-variable-type +- Laura Medioni (Logilab, on behalf of the CNES): + * misplaced-comparison-constant + * no-classmethod-decorator + * no-staticmethod-decorator + * too-many-nested-blocks, + * too-many-boolean-expressions + * unneeded-not + * wrong-import-order + * ungrouped-imports, + * wrong-import-position + * redefined-variable-type -* Aru Sahni: Git ignoring, regex-based ignores +- Aru Sahni: Git ignoring, regex-based ignores -* Mike Frysinger +- Mike Frysinger -* Moisés López (Vauxoo): Support for deprecated-modules in modules not installed, - Refactor wrong-import-order to integrate it with `isort` library - Add check too-complex with mccabe for cyclomatic complexity - Refactor wrong-import-position to skip try-import and nested cases - Add consider-merging-isinstance, superfluous-else-return - Fix consider-using-ternary for 'True and True and True or True' case +- Moisés López (Vauxoo): + * Support for deprecated-modules in modules not installed, + * Refactor wrong-import-order to integrate it with `isort` library + * Add check too-complex with mccabe for cyclomatic complexity + * Refactor wrong-import-position to skip try-import and nested cases + * Add consider-merging-isinstance, superfluous-else-return + * Fix consider-using-ternary for 'True and True and True or True' case -* Luis Escobar (Vauxoo), Moisés López (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty +- Luis Escobar (Vauxoo), Moisés López (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty -* Yannick Brehon +- Yannick Brehon -* Glenn Matthews: autogenerated documentation for optional extensions, +- Glenn Matthews: autogenerated documentation for optional extensions, bug fixes and enhancements for docparams (née check_docs) extension -* Elias Dorneles: minor adjust to config defaults and docs +- Elias Dorneles: minor adjust to config defaults and docs -* Yuri Bochkarev: Added epytext support to docparams extension. +- Yuri Bochkarev: Added epytext support to docparams extension. -* Alexander Todorov: added new error conditions to 'bad-super-call', - Added new check for incorrect len(SEQUENCE) usage, - Added new extension for comparison against empty string constants, - Added new extension which detects comparing integers to zero, - Added new useless-return checker, - Added new try-except-raise checker +- Alexander Todorov: added new error conditions to 'bad-super-call', + * Added new check for incorrect len(SEQUENCE) usage, + * Added new extension for comparison against empty string constants, + * Added new extension which detects comparing integers to zero, + * Added new useless-return checker, + * Added new try-except-raise checker -* Erik Eriksson - Added overlapping-except error check. +- Erik Eriksson - Added overlapping-except error check. -* Anthony Foglia (Google): Added simple string slots check. +- Anthony Foglia (Google): Added simple string slots check. -* Derek Gustafson +- Derek Gustafson -* Petr Pulc: require whitespace around annotations +- Petr Pulc: require whitespace around annotations -* John Paraskevopoulos: add 'differing-param-doc' and 'differing-type-doc' +- John Paraskevopoulos: add 'differing-param-doc' and 'differing-type-doc' -* Martin von Gagern (Google): Added 'raising-format-tuple' warning. +- Martin von Gagern (Google): Added 'raising-format-tuple' warning. -* Ahirnish Pareek, 'keyword-arg-before-var-arg' check +- Ahirnish Pareek, 'keyword-arg-before-var-arg' check -* Daniel Miller +- Daniel Miller -* Martin Bašti - Added new check for shallow copy of os.environ - Added new check for useless `with threading.Lock():` statement +- Martin Bašti + * Added new check for shallow copy of os.environ + * Added new check for useless `with threading.Lock():` statement -* Jacques Kvam +- Jacques Kvam -* Brian Shaginaw: prevent error on exception check for functions +- Brian Shaginaw: prevent error on exception check for functions -* Ioana Tagirta: fix bad thread instantiation check +- Ioana Tagirta: fix bad thread instantiation check -* Reverb Chu +- Reverb Chu -* Tobias Hernstig +- Tobias Hernstig -* Konstantin Manna +- Konstantin Manna -* Andreas Freimuth: fix indentation checking with tabs +- Andreas Freimuth: fix indentation checking with tabs -* Renat Galimov +- Renat Galimov -* Thomas Snowden: fix missing-docstring for inner functions +- Thomas Snowden: fix missing-docstring for inner functions -* Mitchell Young: minor adjustment to docparams +- Mitchell Young: minor adjustment to docparams -* Marianna Polatoglou: minor contribution for wildcard import check +- Marianna Polatoglou: minor contribution for wildcard import check -* Ben Green +- Ben Green -* Benjamin Freeman +- Benjamin Freeman -* Fureigh +- Fureigh -* Jace Browning: updated default report format with clickable paths +- Jace Browning: updated default report format with clickable paths -* Sushobhit (sushobhit27) - Added new check 'comparison-with-itself'. - Added new check 'useless-import-alias'. - Added support of annotations in missing-type-doc and missing-return-type-doc. - Added new check 'comparison-with-callable'. - Removed six package dependency. - Added new check 'chained-comparison'. - Added new check 'useless-object-inheritance'. +- Sushobhit (sushobhit27) + * Added new check 'comparison-with-itself'. + * Added new check 'useless-import-alias'. + * Added support of annotations in missing-type-doc and missing-return-type-doc. + * Added new check 'comparison-with-callable'. + * Removed six package dependency. + * Added new check 'chained-comparison'. + * Added new check 'useless-object-inheritance'. -* Mariatta Wijaya - Added new check `logging-fstring-interpolation` - Documentation typo fixes +- Mariatta Wijaya + * Added new check `logging-fstring-interpolation` + * Documentation typo fixes -* Jason Owen +- Jason Owen -* Mark Roman Miller: fix inline defs in too-many-statements +- Mark Roman Miller: fix inline defs in too-many-statements -* Adam Dangoor +- Adam Dangoor -* Gary Tyler McLeod +- Gary Tyler McLeod -* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, +- Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, Maarten ter Huurne, Mirko Friedenhagen and all the Logilab's team (among others). -* Matej Marusak +- Matej Marusak -* Nick Drozd, performance improvements to astroid +- Nick Drozd, performance improvements to astroid -* Kosarchuk Sergey +- Kosarchuk Sergey -* Kurian Benoy +- Kurian Benoy -* Carey Metcalfe: demoted `try-except-raise` from error to warning +- Carey Metcalfe: demoted `try-except-raise` from error to warning -* Marcus Näslund (naslundx) +- Marcus Näslund (naslundx) -* Natalie Serebryakova +- Natalie Serebryakova -* Caio Carrara +- Caio Carrara -* Roberto Leinardi: PyCharm plugin maintainer +- Roberto Leinardi: PyCharm plugin maintainer -* Aivar Annamaa +- Aivar Annamaa -* Hornwitser: fix import graph +- Hornwitser: fix import graph -* Yuri Gribov +- Yuri Gribov -* Drew Risinger +- Drew Risinger -* Ben James +- Ben James -* Tomer Chachamu, Richard Goodman: simplifiable-if-expression +- Tomer Chachamu, Richard Goodman: simplifiable-if-expression -* Alan Chan +- Alan Chan -* Benjamin Drung: contributing Debian Developer +- Benjamin Drung: contributing Debian Developer -* Scott Worley +- Scott Worley -* Michael Hudson-Doyle +- Michael Hudson-Doyle -* Lucas Cimon +- Lucas Cimon -* Mike Miller +- Mike Miller -* Sergei Lebedev +- Sergei Lebedev -* Sasha Bagan +- Sasha Bagan -* Pablo Galindo Salgado - Fix false positive 'Non-iterable value' with async comprehensions. +- Pablo Galindo Salgado + * Fix false positive 'Non-iterable value' with async comprehensions. -* Matus Valo +- Matus Valo -* Sardorbek Imomaliev +- Sardorbek Imomaliev -* Justin Li (justinnhli) +- Justin Li (justinnhli) -* Nicolas Dickreuter +- Nicolas Dickreuter -* Pascal Corpet +- Pascal Corpet -* Svetoslav Neykov +- Svetoslav Neykov -* Federico Bond +- Federico Bond -* Fantix King (UChicago) +- Fantix King (UChicago) -* Yory (yory8) +- Yory (yory8) -* Thomas Hisch +- Thomas Hisch -* Clément Pit-Claudel +- Clément Pit-Claudel -* Goudcode +- Goudcode -* Paul Renvoise +- Paul Renvoise -* Bluesheeptoken +- Bluesheeptoken -* Michael Scott Cuthbert +- Michael Scott Cuthbert -* Nathan Marrow +- Nathan Marrow -* Taewon Kim +- Taewon Kim -* Daniil Kharkov +- Daniil Kharkov -* Tyler N. Thieding +- Tyler N. Thieding -* Zeb Nicholls - - Made W9011 compatible with 'of' syntax in return types +- Zeb Nicholls + * Made W9011 compatible with 'of' syntax in return types -* Martin Vielsmaier +- Martin Vielsmaier -* Agustin Toledo +- Agustin Toledo -* Nicholas Smith +- Nicholas Smith -* Peter Kolbus (Garmin) +- Peter Kolbus (Garmin) -* Oisin Moran +- Oisin Moran -* Andrzej Klajnert +- Andrzej Klajnert -* Andrés Pérez Hortal +- Andrés Pérez Hortal -* Niko Wenselowski +- Niko Wenselowski -* Danny Hermes +- Danny Hermes -* Eric Froemling +- Eric Froemling -* Robert Schweizer +- Robert Schweizer -* Hugo van Kemenade +- Hugo van Kemenade -* Mikhail Fesenko +- Mikhail Fesenko -* Trevor Bekolay - - Added --list-msgs-enabled command +- Trevor Bekolay + * Added --list-msgs-enabled command -* Rémi Cardona +- Rémi Cardona -* Daniel Draper +- Daniel Draper -* Gabriel R. Sezefredo - - Fixed "exception-escape" false positive with generators +- Gabriel R. Sezefredo + * Fixed "exception-escape" false positive with generators -* laike9m +- laike9m -* Janne Rönkkö +- Janne Rönkkö -* Hugues Bruant +- Hugues Bruant -* Tim Gates +- Tim Gates -* Enji Cooper +- Enji Cooper -* Bastien Vallet +- Bastien Vallet -* Pek Chhan +- Pek Chhan -* Craig Henriques +- Craig Henriques -* Matthijs Blom +- Matthijs Blom -* Andy Palmer +- Andy Palmer -* Wes Turner (Google): added new check 'inconsistent-quotes' +- Wes Turner (Google): added new check 'inconsistent-quotes' -* Athos Ribeiro +- Athos Ribeiro Fixed dict-keys-not-iterating false positive for inverse containment checks -* Anubhav +- Anubhav -* Ben Graham +- Ben Graham -* Anthony Tan +- Anthony Tan -* Benny Müller +- Benny Müller -* Bernie Gray +- Bernie Gray -* Slavfox +- Slavfox -* Matthew Beckers (mattlbeck) +- Matthew Beckers (mattlbeck) -* Yang Yang +- Yang Yang -* Andrew J. Simmons (anjsimmo) +- Andrew J. Simmons (anjsimmo) -* Damien Baty +- Damien Baty -* Daniel R. Neal (danrneal) +- Daniel R. Neal (danrneal) -* Jeremy Fleischman (jfly) +- Jeremy Fleischman (jfly) -* Shiv Venkatasubrahmanyam +- Shiv Venkatasubrahmanyam -* Jochen Preusche (iilei) +- Jochen Preusche (iilei) -* Ram Rachum (cool-RR) +- Ram Rachum (cool-RR) -* D. Alphus (Alphadelta14) +- D. Alphus (Alphadelta14) -* Pieter Engelbrecht +- Pieter Engelbrecht -* Ethan Leba +- Ethan Leba -* Matěj Grabovský +- Matěj Grabovský -* Yeting Li (yetingli) +- Yeting Li (yetingli) -* Frost Ming (frostming) +- Frost Ming (frostming) -* Luigi Bertaco Cristofolini (luigibertaco) +- Luigi Bertaco Cristofolini (luigibertaco) -* Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only +- Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only -* Ganden Schaffner +- Ganden Schaffner -* Josselin Feist +- Josselin Feist -* David Cain +- David Cain -* Pedro Algarvio (s0undt3ch) +- Pedro Algarvio (s0undt3ch) -* Luigi Bertaco Cristofolini (luigibertaco) +- Luigi Bertaco Cristofolini (luigibertaco) -* Or Bahari +- Or Bahari -* Joshua Cannon +- Joshua Cannon -* Giuseppe Valente +- Giuseppe Valente -* Takashi Hirashima +- Takashi Hirashima -* Joffrey Mander +- Joffrey Mander -* Julien Palard +- Julien Palard -* Raphael Gaschignard +- Raphael Gaschignard -* Sorin Sbarnea +- Sorin Sbarnea -* Gergely Kalmár +- Gergely Kalmár -* Batuhan Taskaya +- Batuhan Taskaya -* Frank Harrison (doublethefish) +- Frank Harrison (doublethefish) -* Gauthier Sebaux +- Gauthier Sebaux -* Logan Miller (komodo472) +- Logan Miller (komodo472) -* Matthew Suozzo +- Matthew Suozzo -* David Gilman +- David Gilman -* Ikraduya Edian - - Added new checks 'consider-using-generator' and 'use-a-generator'. +- Ikraduya Edian + * Added new checks 'consider-using-generator' and 'use-a-generator'. -* Tiago Honorato +- Tiago Honorato -* Lefteris Karapetsas +- Lefteris Karapetsas -* Louis Sautier +- Louis Sautier -* Quentin Young +- Quentin Young -* Alexander Kapshuna +- Alexander Kapshuna -* Mark Byrne +- Mark Byrne -* Konstantina Saketou +- Konstantina Saketou -* Andrew Howe +- Andrew Howe -* James Sinclair (irgeek) +- James Sinclair (irgeek) -* Aidan Haase, Elizabeth Bott +- Aidan Haase, Elizabeth Bott -* Sebastian Müller +- Sebastian Müller -* Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp +- Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp -* manderj +- manderj -* qwiddle +- qwiddle -* das-intensity +- das-intensity -* Jiajunsu (victor) +- Jiajunsu (victor) -* Andrew Haigh (nelfin) +- Andrew Haigh (nelfin) -* Aditya Gupta (adityagupta1089) - - Added ignore_signatures to duplicate checker +- Aditya Gupta (adityagupta1089) + * Added ignore_signatures to duplicate checker -* ruro +- ruro -* David Liu (david-yz-liu) +- David Liu (david-yz-liu) -* Bernard Nauwelaerts +- Bernard Nauwelaerts -* Fabian Damken +- Fabian Damken -* Markus Siebenhaar +- Markus Siebenhaar -* Lorena Buciu (lorena-b) +- Lorena Buciu (lorena-b) -* Sergei Lebedev (superbobry) +- Sergei Lebedev (superbobry) -* Maksym Humetskyi (mhumetskyi) - - Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled - - Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled - - Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled +- Maksym Humetskyi (mhumetskyi) + * Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled + * Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled + * Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled -* Daniel Dorani (doranid) +- Daniel Dorani (doranid) -* Will Shanks +- Will Shanks -* Mark Bell +- Mark Bell -* Marco Gorelli - - Documented Jupyter integration +- Marco Gorelli + * Documented Jupyter integration -* Rebecca Turner (9999years) +- Rebecca Turner (9999years) -* Yilei Yang +- Yilei Yang -* Marcin Kurczewski (rr-) +- Marcin Kurczewski (rr-) -* Tanvi Moharir - - Fix for invalid toml config +- Tanvi Moharir + * Fix for invalid toml config -* Eisuke Kawashima (e-kwsm) +- Eisuke Kawashima (e-kwsm) -* Michal Vasilek +- Michal Vasilek -* Kai Mueller (kasium) +- Kai Mueller (kasium) -* Sam Vermeiren (PaaEl) +- Sam Vermeiren (PaaEl) -* Phil A. (flying-sheep) +- Phil A. (flying-sheep) -* Melvin Hazeleger (melvio) +- Melvin Hazeleger (melvio) -* Hayden Richards (SupImDos) - - Fixed "no-self-use" for async methods - - Fixed "docparams" extension for async functions and methods +- Hayden Richards (SupImDos) + * Fixed "no-self-use" for async methods + * Fixed "docparams" extension for async functions and methods -* Jeroen Seegers (jeroenseegers) - - Fixed `toml` dependency issue +- Jeroen Seegers (jeroenseegers) + * Fixed `toml` dependency issue -* Tim Martin +- Tim Martin -* Jaehoon Hwang (jaehoonhwang) +- Jaehoon Hwang (jaehoonhwang) -* Samuel Forestier +- Samuel Forestier -* Nick Pesce +- Nick Pesce -* James DesLauriers +- James DesLauriers -* Youngsoo Sung +- Youngsoo Sung -* Samuel Freilich (sfreilich) +- Samuel Freilich (sfreilich) -* Mike Fiedler (miketheman) +- Mike Fiedler (miketheman) -* Takahide Nojima +- Takahide Nojima -* Tushar Sadhwani (tusharsadhwani) +- Tushar Sadhwani (tusharsadhwani) -* Ikraduya Edian +- Ikraduya Edian -* Antonio Quarta (sgheppy) +- Antonio Quarta (sgheppy) -* Harshil (harshil21) +- Harshil (harshil21) -* Jérome Perrin (perrinjerome) +- Jérome Perrin (perrinjerome) -* Felix von Drigalski (felixvd) +- Felix von Drigalski (felixvd) -* Jake Lishman (jakelishman) +- Jake Lishman (jakelishman) -* Philipp Albrecht (pylbrecht) +- Philipp Albrecht (pylbrecht) -* Allan Chandler (allanc65) - - Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params +- Allan Chandler (allanc65) + * Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params -* Eero Vuojolahti +- Eero Vuojolahti -* Kian-Meng, Ang +- Kian-Meng, Ang -* Nuzula H. Yudaka (Nuzhuka) +- Nuzula H. Yudaka (Nuzhuka) -* Carli Freudenberg (CarliJoy) - - Fixed issue 5281, added Unicode checker - - Improve non-ascii-name checker +- Carli Freudenberg (CarliJoy) + * Fixed issue 5281, added Unicode checker + * Improve non-ascii-name checker -* Daniel Brookman +- Daniel Brookman -* Téo Bouvard +- Téo Bouvard -* Konrad Weihmann +- Konrad Weihmann From 7981ec021b84c6f2b216202c1c028da4b0ee995b Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 13 Mar 2022 10:03:53 +0100 Subject: [PATCH 267/357] Enforce one name per line and no line break in CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 327 +++++++---------------------------------------- 1 file changed, 43 insertions(+), 284 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2ae3149181..01c49f1b7e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -11,7 +11,6 @@ Maintainers - Jacob Walls - Dimitri Prybysh * multiple-imports, not-iterable, not-a-mapping, various patches. - - Roy Williams (Lyft) * added check for implementing __eq__ without implementing __hash__, * Added Python 3 check for accessing Exception.message. @@ -20,7 +19,6 @@ Maintainers * Added Python 3 check for bad import statements. * Added Python 3 check for accessing deprecated methods on the 'string' module, various patches. - - Florian Bruhin - Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> - Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> @@ -38,63 +36,44 @@ We would not be here without folks that contributed patches, pull requests, issues and their time to pylint. We're incredibly grateful to all of these contributors: -- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant), - various patches - -- Martin Pool (Google): warnings for anomalous backslashes, symbolic names for - messages (like 'unused'), etc - +- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant) +- Martin Pool (Google): + * warnings for anomalous backslashes + * symbolic names for messages (like 'unused') + * etc. - Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support - -- Julien Cristau, Emile Anclin (Logilab): python 3 support - +- Julien Cristau (Logilab): python 3 support +- Emile Anclin (Logilab): python 3 support - Sandro Tosi: Debian packaging - -- Mads Kiilerich, Boris Feld, Bill Wendling, Sebastian Ulrich: - various patches - +- Mads Kiilerich +- Boris Feld +- Bill Wendling +- Sebastian Ulrich - Brian van den Broek: windows installation documentation - - Amaury Forgeot d'Arc: check names imported from a module exists in the module - - Benjamin Niemann: allow block level enabling/disabling of messages - - Nathaniel Manista: suspicious lambda checking - - David Shea: invalid sequence and slice index - - Carl Crowder: don't evaluate the value of arguments for 'dangerous-default-value' - -- Michal Nowikowski: wrong-spelling-in-comment, wrong-spelling-in-docstring, - parallel execution on multiple CPUs and other patches. - +- Michal Nowikowski: + * wrong-spelling-in-comment + * wrong-spelling-in-docstring + * parallel execution on multiple CPUs - David Lindquist: logging-format-interpolation warning. - -- Brett Cannon: Port source code to be Python 2/3 compatible, Python 3 - checker. - +- Brett Cannon: + * Port source code to be Python 2/3 compatible + * Python 3 checker - Vlad Temian: redundant-unittest-assert and the JSON reporter. - - Cosmin Poieană: unichr-builtin and improvements to bad-open-mode. - - Viorel Știrbu: intern-builtin warning. - - Dan Goldsmith: support for msg-template in HTML reporter. - - Chris Rebert: unidiomatic-typecheck. - - Steven Myint: duplicate-except. - - Radu Ciorba: not-context-manager and confusing-with-statement warnings. - - Bruno Daniel: check_docs extension. - - James Morgensen: ignored-modules option applies to import errors. - - Cezar Elnazli: deprecated-method - - Stéphane Wirtel: nonlocal-without-binding - - Laura Medioni (Logilab, on behalf of the CNES): * misplaced-comparison-constant * no-classmethod-decorator @@ -106,11 +85,8 @@ contributors: * ungrouped-imports, * wrong-import-position * redefined-variable-type - - Aru Sahni: Git ignoring, regex-based ignores - - Mike Frysinger - - Moisés López (Vauxoo): * Support for deprecated-modules in modules not installed, * Refactor wrong-import-order to integrate it with `isort` library @@ -118,75 +94,47 @@ contributors: * Refactor wrong-import-position to skip try-import and nested cases * Add consider-merging-isinstance, superfluous-else-return * Fix consider-using-ternary for 'True and True and True or True' case - -- Luis Escobar (Vauxoo), Moisés López (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty - +- Luis Escobar (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty +- Moisés López (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty - Yannick Brehon - -- Glenn Matthews: autogenerated documentation for optional extensions, - bug fixes and enhancements for docparams (née check_docs) extension - +- Glenn Matthews: + * autogenerated documentation for optional extensions, + * bug fixes and enhancements for docparams (née check_docs) extension - Elias Dorneles: minor adjust to config defaults and docs - - Yuri Bochkarev: Added epytext support to docparams extension. - -- Alexander Todorov: added new error conditions to 'bad-super-call', +- Alexander Todorov: + * added new error conditions to 'bad-super-call', * Added new check for incorrect len(SEQUENCE) usage, * Added new extension for comparison against empty string constants, * Added new extension which detects comparing integers to zero, * Added new useless-return checker, * Added new try-except-raise checker - -- Erik Eriksson - Added overlapping-except error check. - +- Erik Eriksson: Added overlapping-except error check. - Anthony Foglia (Google): Added simple string slots check. - - Derek Gustafson - - Petr Pulc: require whitespace around annotations - - John Paraskevopoulos: add 'differing-param-doc' and 'differing-type-doc' - - Martin von Gagern (Google): Added 'raising-format-tuple' warning. - -- Ahirnish Pareek, 'keyword-arg-before-var-arg' check - +- Ahirnish Pareek: 'keyword-arg-before-var-arg' check - Daniel Miller - - Martin Bašti * Added new check for shallow copy of os.environ * Added new check for useless `with threading.Lock():` statement - - Jacques Kvam - - Brian Shaginaw: prevent error on exception check for functions - - Ioana Tagirta: fix bad thread instantiation check - - Reverb Chu - - Tobias Hernstig - - Konstantin Manna - - Andreas Freimuth: fix indentation checking with tabs - - Renat Galimov - - Thomas Snowden: fix missing-docstring for inner functions - - Mitchell Young: minor adjustment to docparams - - Marianna Polatoglou: minor contribution for wildcard import check - - Ben Green - - Benjamin Freeman - - Fureigh - - Jace Browning: updated default report format with clickable paths - - Sushobhit (sushobhit27) * Added new check 'comparison-with-itself'. * Added new check 'useless-import-alias'. @@ -195,404 +143,215 @@ contributors: * Removed six package dependency. * Added new check 'chained-comparison'. * Added new check 'useless-object-inheritance'. - - Mariatta Wijaya * Added new check `logging-fstring-interpolation` * Documentation typo fixes - - Jason Owen - - Mark Roman Miller: fix inline defs in too-many-statements - - Adam Dangoor - - Gary Tyler McLeod - -- Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, - Maarten ter Huurne, Mirko Friedenhagen and all the Logilab's team (among others). - +- Wolfgang Grafen +- Axel Muller +- Fabio Zadrozny +- Pierre Rouleau, +- Maarten ter Huurne +- Mirko Friedenhagen - Matej Marusak - -- Nick Drozd, performance improvements to astroid - +- Nick Drozd: performance improvements to astroid - Kosarchuk Sergey - - Kurian Benoy - - Carey Metcalfe: demoted `try-except-raise` from error to warning - - Marcus Näslund (naslundx) - - Natalie Serebryakova - - Caio Carrara - - Roberto Leinardi: PyCharm plugin maintainer - - Aivar Annamaa - - Hornwitser: fix import graph - - Yuri Gribov - - Drew Risinger - - Ben James - -- Tomer Chachamu, Richard Goodman: simplifiable-if-expression - +- Tomer Chachamu: simplifiable-if-expression +- Richard Goodman: simplifiable-if-expression - Alan Chan - - Benjamin Drung: contributing Debian Developer - - Scott Worley - - Michael Hudson-Doyle - - Lucas Cimon - - Mike Miller - - Sergei Lebedev - - Sasha Bagan - - Pablo Galindo Salgado * Fix false positive 'Non-iterable value' with async comprehensions. - - Matus Valo - - Sardorbek Imomaliev - - Justin Li (justinnhli) - - Nicolas Dickreuter - - Pascal Corpet - - Svetoslav Neykov - - Federico Bond - - Fantix King (UChicago) - - Yory (yory8) - - Thomas Hisch - - Clément Pit-Claudel - - Goudcode - - Paul Renvoise - - Bluesheeptoken - - Michael Scott Cuthbert - - Nathan Marrow - - Taewon Kim - - Daniil Kharkov - - Tyler N. Thieding - - Zeb Nicholls * Made W9011 compatible with 'of' syntax in return types - - Martin Vielsmaier - - Agustin Toledo - - Nicholas Smith - - Peter Kolbus (Garmin) - - Oisin Moran - - Andrzej Klajnert - - Andrés Pérez Hortal - - Niko Wenselowski - - Danny Hermes - - Eric Froemling - - Robert Schweizer - - Hugo van Kemenade - - Mikhail Fesenko - - Trevor Bekolay * Added --list-msgs-enabled command - - Rémi Cardona - - Daniel Draper - -- Gabriel R. Sezefredo - * Fixed "exception-escape" false positive with generators - +- Gabriel R. Sezefredo: Fixed "exception-escape" false positive with generators - laike9m - - Janne Rönkkö - - Hugues Bruant - - Tim Gates - - Enji Cooper - - Bastien Vallet - - Pek Chhan - - Craig Henriques - - Matthijs Blom - - Andy Palmer - - Wes Turner (Google): added new check 'inconsistent-quotes' - -- Athos Ribeiro - Fixed dict-keys-not-iterating false positive for inverse containment checks - +- Athos Ribeiro: Fixed dict-keys-not-iterating false positive for inverse containment checks - Anubhav - - Ben Graham - - Anthony Tan - - Benny Müller - - Bernie Gray - - Slavfox - - Matthew Beckers (mattlbeck) - - Yang Yang - - Andrew J. Simmons (anjsimmo) - - Damien Baty - - Daniel R. Neal (danrneal) - - Jeremy Fleischman (jfly) - - Shiv Venkatasubrahmanyam - - Jochen Preusche (iilei) - - Ram Rachum (cool-RR) - - D. Alphus (Alphadelta14) - - Pieter Engelbrecht - - Ethan Leba - - Matěj Grabovský - - Yeting Li (yetingli) - - Frost Ming (frostming) - - Luigi Bertaco Cristofolini (luigibertaco) - - Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only - - Ganden Schaffner - - Josselin Feist - - David Cain - - Pedro Algarvio (s0undt3ch) - - Luigi Bertaco Cristofolini (luigibertaco) - - Or Bahari - - Joshua Cannon - - Giuseppe Valente - - Takashi Hirashima - - Joffrey Mander - - Julien Palard - - Raphael Gaschignard - - Sorin Sbarnea - - Gergely Kalmár - - Batuhan Taskaya - - Frank Harrison (doublethefish) - - Gauthier Sebaux - - Logan Miller (komodo472) - - Matthew Suozzo - - David Gilman - -- Ikraduya Edian - * Added new checks 'consider-using-generator' and 'use-a-generator'. - +- Ikraduya Edian: Added new checks 'consider-using-generator' and 'use-a-generator'. - Tiago Honorato - - Lefteris Karapetsas - - Louis Sautier - - Quentin Young - - Alexander Kapshuna - - Mark Byrne - - Konstantina Saketou - - Andrew Howe - - James Sinclair (irgeek) - -- Aidan Haase, Elizabeth Bott - +- Aidan Haase +- Elizabeth Bott - Sebastian Müller - - Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp - - manderj - - qwiddle - - das-intensity - - Jiajunsu (victor) - - Andrew Haigh (nelfin) - - Aditya Gupta (adityagupta1089) * Added ignore_signatures to duplicate checker - - ruro - - David Liu (david-yz-liu) - - Bernard Nauwelaerts - - Fabian Damken - - Markus Siebenhaar - - Lorena Buciu (lorena-b) - - Sergei Lebedev (superbobry) - - Maksym Humetskyi (mhumetskyi) * Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled * Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled * Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled - - Daniel Dorani (doranid) - - Will Shanks - - Mark Bell - -- Marco Gorelli - * Documented Jupyter integration - +- Marco Gorelli: Documented Jupyter integration - Rebecca Turner (9999years) - - Yilei Yang - - Marcin Kurczewski (rr-) - -- Tanvi Moharir - * Fix for invalid toml config - +- Tanvi Moharir: Fix for invalid toml config - Eisuke Kawashima (e-kwsm) - - Michal Vasilek - - Kai Mueller (kasium) - - Sam Vermeiren (PaaEl) - - Phil A. (flying-sheep) - - Melvin Hazeleger (melvio) - - Hayden Richards (SupImDos) * Fixed "no-self-use" for async methods * Fixed "docparams" extension for async functions and methods - - Jeroen Seegers (jeroenseegers) * Fixed `toml` dependency issue - - Tim Martin - - Jaehoon Hwang (jaehoonhwang) - - Samuel Forestier - - Nick Pesce - - James DesLauriers - - Youngsoo Sung - - Samuel Freilich (sfreilich) - - Mike Fiedler (miketheman) - - Takahide Nojima - - Tushar Sadhwani (tusharsadhwani) - - Ikraduya Edian - - Antonio Quarta (sgheppy) - - Harshil (harshil21) - - Jérome Perrin (perrinjerome) - - Felix von Drigalski (felixvd) - - Jake Lishman (jakelishman) - - Philipp Albrecht (pylbrecht) - - Allan Chandler (allanc65) * Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params - - Eero Vuojolahti - - Kian-Meng, Ang - - Nuzula H. Yudaka (Nuzhuka) - - Carli Freudenberg (CarliJoy) * Fixed issue 5281, added Unicode checker * Improve non-ascii-name checker - - Daniel Brookman - - Téo Bouvard - - Konrad Weihmann From 663a2be7abd407fc680f426155ba26becaaa278d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 13 Mar 2022 13:15:46 +0100 Subject: [PATCH 268/357] Upgrade ``pydocstringformatter`` to ``0.5.0`` (#5910) --- .pre-commit-config.yaml | 4 +- doc/exts/pylint_messages.py | 2 + pylint/checkers/__init__.py | 5 ++- pylint/checkers/base.py | 18 ++++++--- pylint/checkers/base_checker.py | 6 ++- pylint/checkers/classes/class_checker.py | 19 +++++++--- pylint/checkers/deprecated.py | 6 ++- pylint/checkers/design_analysis.py | 4 +- pylint/checkers/ellipsis_checker.py | 3 +- pylint/checkers/exceptions.py | 1 + pylint/checkers/format.py | 8 +++- pylint/checkers/imports.py | 4 +- pylint/checkers/misc.py | 4 +- pylint/checkers/newstyle.py | 3 +- pylint/checkers/raw_metrics.py | 4 +- .../implicit_booleaness_checker.py | 3 +- .../refactoring/refactoring_checker.py | 7 +++- pylint/checkers/similar.py | 26 ++++++++----- pylint/checkers/stdlib.py | 4 +- pylint/checkers/strings.py | 1 + pylint/checkers/typecheck.py | 5 ++- pylint/checkers/utils.py | 30 +++++++++------ pylint/checkers/variables.py | 37 +++++++++++-------- pylint/extensions/_check_docs_utils.py | 4 +- pylint/extensions/code_style.py | 1 + pylint/extensions/comparetozero.py | 1 + pylint/extensions/docparams.py | 6 +-- pylint/extensions/emptystring.py | 1 + pylint/extensions/eq_without_hash.py | 7 ++-- pylint/extensions/overlapping_exceptions.py | 1 + pylint/extensions/typing.py | 5 ++- pylint/graph.py | 11 +++--- pylint/lint/expand_modules.py | 4 +- pylint/lint/parallel.py | 3 +- pylint/lint/run.py | 4 +- pylint/pyreverse/dot_printer.py | 5 ++- pylint/pyreverse/inspector.py | 1 + pylint/pyreverse/mermaidjs_printer.py | 5 ++- pylint/pyreverse/plantuml_printer.py | 5 ++- pylint/pyreverse/printer.py | 5 ++- pylint/pyreverse/utils.py | 1 + pylint/pyreverse/vcg_printer.py | 6 ++- pylint/reporters/multi_reporter.py | 7 ++-- pylint/testutils/output_line.py | 10 +++-- pylint/testutils/pyreverse.py | 1 + pylint/utils/utils.py | 2 +- 46 files changed, 199 insertions(+), 101 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4ace61f43..039f220c6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -95,7 +95,9 @@ repos: args: [--prose-wrap=always, --print-width=88] exclude: tests(/.*)*/data - repo: https://github.com/DanielNoord/pydocstringformatter - rev: a9f94bf13b08fe33f784ed7f0a0fc39e2a8549e2 + rev: v0.5.0 hooks: - id: pydocstringformatter exclude: *fixtures + args: ["--max-summary-lines=2", "--split-summary-body", "-w"] + files: "pylint" diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index 1ca3bdb88a..7f910918f4 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -52,6 +52,7 @@ def _get_all_messages( ) -> Tuple[MessagesDict, OldMessagesDict]: """Get all messages registered to a linter and return a dictionary indexed by message type. + Also return a dictionary of old message and the new messages they can be mapped to. """ messages_dict: MessagesDict = { @@ -202,6 +203,7 @@ def _write_redirect_pages(old_messages: OldMessagesDict) -> None: # pylint: disable-next=unused-argument def build_messages_pages(app: Optional[Sphinx]) -> None: """Overwrite messages files by printing the documentation to a stream. + Documentation is written in ReST format. """ # Create linter, register all checkers and extensions and get all messages diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index ab23b6b592..160bad8af2 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -77,8 +77,9 @@ def table_lines_from_stats( stat_type: Literal["duplicated_lines", "message_types"], ) -> List[str]: """Get values listed in from and , - and return a formatted list of values, designed to be given to a - ureport.Table object + and return a formatted list of values. + + The return value is designed to be given to a ureport.Table object """ lines: List[str] = [] if stat_type == "duplicated_lines": diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 71dabd8efc..cd678b6cb7 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -102,8 +102,7 @@ class NamingStyle: """It may seem counterintuitive that single naming style has multiple "accepted" - forms of regular expressions, but we need to special-case stuff like dunder names - in method names. + forms of regular expressions, but we need to special-case stuff like dunder names in method names. """ ANY: Pattern[str] = re.compile(".*") @@ -234,6 +233,7 @@ class AnyStyle(NamingStyle): def _redefines_import(node): """Detect that the given node (AssignName) is inside an exception handler and redefines an import from the tryexcept body. + Returns True if the node redefines an import, False otherwise. """ current = node @@ -940,7 +940,9 @@ def _check_redefinition(self, redeftype, node): class BasicChecker(_BasicChecker): - """Checks for : + """Basic checker. + + Checks for : * doc strings * number of arguments, local variables, branches, returns and statements in functions, methods @@ -1360,7 +1362,9 @@ def is_iterable(internal_node): @utils.check_messages("unreachable", "lost-exception") def visit_return(self, node: nodes.Return) -> None: - """1 - check if the node has a right sibling (if so, that's some + """Return node visitor. + + 1 - check if the node has a right sibling (if so, that's some unreachable code) 2 - check if the node is inside the 'finally' clause of a 'try...finally' block @@ -1378,7 +1382,9 @@ def visit_continue(self, node: nodes.Continue) -> None: @utils.check_messages("unreachable", "lost-exception") def visit_break(self, node: nodes.Break) -> None: - """1 - check if the node has a right sibling (if so, that's some + """Break node visitor. + + 1 - check if the node has a right sibling (if so, that's some unreachable code) 2 - check if the node is inside the 'finally' clause of a 'try...finally' block @@ -1495,6 +1501,7 @@ def _check_unreachable(self, node): def _check_not_in_finally(self, node, node_name, breaker_classes=()): """Check that a node is not inside a 'finally' clause of a 'try...finally' statement. + If we find a parent which type is in breaker_classes before a 'try...finally' block we skip the whole check. """ @@ -2561,6 +2568,7 @@ def _check_literal_comparison(self, literal, node: nodes.Compare): def _check_logical_tautology(self, node: nodes.Compare): """Check if identifier is compared against itself. + :param node: Compare node :Example: val = 786 diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index 10e990f935..376e785450 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -67,8 +67,10 @@ def __repr__(self): return f"{status} '{self.name}' (responsible for '{msgs}')" def __str__(self): - """This might be incomplete because multiple class inheriting BaseChecker - can have the same name. Cf MessageHandlerMixIn.get_full_documentation() + """This might be incomplete because multiple classes inheriting BaseChecker + can have the same name. + + See: MessageHandlerMixIn.get_full_documentation() """ return self.get_full_documentation( msgs=self.msgs, options=self.options_and_values(), reports=self.reports diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index dd9a0b31ce..63078cf421 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -362,7 +362,9 @@ def _has_data_descriptor(cls, attr): def _called_in_methods(func, klass, methods): """Check if the func was called in any of the given methods, - belonging to the *klass*. Returns True if so, False otherwise. + belonging to the *klass*. + + Returns True if so, False otherwise. """ if not isinstance(func, nodes.FunctionDef): return False @@ -697,7 +699,9 @@ def accessed(self, scope): class ClassChecker(BaseChecker): - """Checks for : + """Checker for class nodes. + + Checks for : * methods without self as first argument * overridden methods signature * access only to existent members via self @@ -876,7 +880,8 @@ def _check_typing_final(self, node: nodes.ClassDef) -> None: @check_messages("unused-private-member", "attribute-defined-outside-init") def leave_classdef(self, node: nodes.ClassDef) -> None: - """Close a class node: + """Checker for Class nodes. + check that instance attributes are defined in __init__ and check access to existent members """ @@ -1463,7 +1468,9 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None: def visit_attribute(self, node: nodes.Attribute) -> None: """Check if the getattr is an access to a class member - if so, register it. Also check for access to protected + if so, register it + + Also check for access to protected class member from outside its class (but ignore __special__ methods) """ @@ -1617,7 +1624,9 @@ def _check_classmethod_declaration(self, node): def _check_protected_attribute_access(self, node: nodes.Attribute): """Given an attribute access node (set or get), check if attribute - access is legitimate. Call _check_first_attr with node before calling + access is legitimate. + + Call _check_first_attr with node before calling this method. Valid cases are: * self._attr in a method or cls._attr in a classmethod. Checked by _check_first_attr. diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index dc0713a89e..dd75547b95 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -22,6 +22,7 @@ class DeprecatedMixin(BaseChecker): """A mixin implementing logic for checking deprecated symbols. + A class implementing mixin must define "deprecated-method" Message. """ @@ -180,8 +181,9 @@ def check_deprecated_module(self, node, mod_path): self.add_message("deprecated-module", node=node, args=mod_path) def check_deprecated_method(self, node, inferred): - """Executes the checker for the given node. This method should - be called from the checker implementing this mixin. + """Executes the checker for the given node. + + This method should be called from the checker implementing this mixin. """ # Reject nodes which aren't of interest to us. diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index 37cd16c571..46bd443a2e 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -298,7 +298,9 @@ def _get_parents( class MisdesignChecker(BaseChecker): - """Checks for sign of poor/misdesign: + """Checker of potential misdesigns. + + Checks for sign of poor/misdesign: * number of methods, attributes, local variables... * size, complexity of functions, methods """ diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py index f29ebed042..1261485b82 100644 --- a/pylint/checkers/ellipsis_checker.py +++ b/pylint/checkers/ellipsis_checker.py @@ -28,7 +28,8 @@ class EllipsisChecker(BaseChecker): @check_messages("unnecessary-ellipsis") def visit_const(self, node: nodes.Const) -> None: """Check if the ellipsis constant is used unnecessarily. - Emit a warning when: + + Emits a warning when: - A line consisting of an ellipsis is preceded by a docstring. - A statement exists in the same scope as the ellipsis. For example: A function consisting of an ellipsis followed by a diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 9960b8d32e..58d273ca2e 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -58,6 +58,7 @@ def predicate(obj): def _annotated_unpack_infer(stmt, context=None): """Recursively generate nodes inferred by the given statement. + If the inferred value is a list or a tuple, recurse on the elements. Returns an iterator which yields tuples in the format ('original node', 'inferred node'). diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 799d97cbc5..716dce4cba 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -237,7 +237,9 @@ def line(self, idx): class FormatChecker(BaseTokenChecker): - """Checks for : + """Formatting checker. + + Checks for : * unauthorized constructions * strict indentation * line length @@ -739,7 +741,9 @@ def specific_splitlines(lines: str) -> List[str]: return res def check_lines(self, lines: str, lineno: int) -> None: - """Check lines have : + """Check given lines for potential messages. + + Check lines have : - a final newline - no trailing whitespace - less than a maximum number of characters diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 689573b211..f9422f9b32 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -300,7 +300,9 @@ def _make_graph( class ImportsChecker(DeprecatedMixin, BaseChecker): - """Checks for + """BaseChecker for import statements. + + Checks for * external modules dependencies * relative / wildcard imports * cyclic imports diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index baec58fbbf..6461975ae4 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -76,7 +76,9 @@ def process_module(self, node: nodes.Module) -> None: class EncodingChecker(BaseChecker): - """Checks for: + """BaseChecker for encoding issues. + + Checks for: * warning notes in the code like FIXME, XXX * encoding issues. """ diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index 00fa0748e3..eb8473d09f 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -45,7 +45,8 @@ class NewStyleConflictChecker(BaseChecker): """Checks for usage of new style capabilities on old style classes and - other new/old styles conflicts problems + other new/old styles conflicts problems. + * use of property, __slots__, super * "super" usage """ diff --git a/pylint/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index 84e6c23c47..dae296ec84 100644 --- a/pylint/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py @@ -60,7 +60,9 @@ def report_raw_stats( class RawMetricsChecker(BaseTokenChecker): - """Does not check anything but gives some raw metrics : + """Checker that provides raw metrics instead of checking anything. + + Provides: * total number of lines * total number of code lines * total number of docstring lines diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index b1fa672788..e9482f6b71 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -128,8 +128,7 @@ def instance_has_bool(class_def: nodes.ClassDef) -> bool: @utils.check_messages("use-implicit-booleaness-not-len") def visit_unaryop(self, node: nodes.UnaryOp) -> None: """`not len(S)` must become `not S` regardless if the parent block - is a test condition or something else (boolean expression) - e.g. `if not len(S):` + is a test condition or something else (boolean expression) e.g. `if not len(S):` """ if ( isinstance(node, nodes.UnaryOp) diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 07fcff802c..00e234cbd6 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1531,6 +1531,7 @@ def _check_use_list_or_dict_literal(self, node: nodes.Call) -> None: def _check_consider_using_join(self, aug_assign): """We start with the augmented assignment and work our way upwards. + Names of variables for nodes if match successful: result = '' # assign for number in ['1', '2', '3'] # for_loop @@ -1824,7 +1825,7 @@ def _has_return_in_siblings(node: nodes.NodeNG) -> bool: return False def _is_function_def_never_returning(self, node: nodes.FunctionDef) -> bool: - """Return True if the function never returns. False otherwise. + """Return True if the function never returns, False otherwise. Args: node (nodes.FunctionDef): function definition node to be analyzed. @@ -1846,7 +1847,9 @@ def _is_function_def_never_returning(self, node: nodes.FunctionDef) -> bool: def _check_return_at_the_end(self, node): """Check for presence of a *single* return statement at the end of a - function. "return" or "return None" are useless because None is the + function. + + "return" or "return None" are useless because None is the default return type if they are missing. NOTE: produces a message only if there is a single return statement diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 5287e31279..8e6eee92d5 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -109,9 +109,8 @@ class LineSpecifs(NamedTuple): class CplSuccessiveLinesLimits: - """This class holds a couple of SuccessiveLinesLimits objects, one for each file compared, - and a counter on the number of common lines between both stripped lines collections extracted - from both files + """Holds a SuccessiveLinesLimits object for each file compared and a + counter on the number of common lines between both stripped lines collections extracted from both files """ __slots__ = ("first_file", "second_file", "effective_cmn_lines_nb") @@ -230,7 +229,9 @@ def increment(self, value: Index) -> "LineSetStartCouple": def hash_lineset( lineset: "LineSet", min_common_lines: int = DEFAULT_MIN_SIMILARITY_LINE ) -> Tuple[HashToIndex_T, IndexToLines_T]: - """Return two dicts. The first associates the hash of successive stripped lines of a lineset + """Return two dicts. + + The first associates the hash of successive stripped lines of a lineset to the indices of the starting lines. The second dict, associates the index of the starting line in the lineset's stripped lines to the couple [start, end] lines number in the corresponding file. @@ -318,9 +319,12 @@ def filter_noncode_lines( stindex_2: Index, common_lines_nb: int, ) -> int: - """Return the effective number of common lines between lineset1 and lineset2 filtered from non code lines, that is to say the number of - common successive stripped lines except those that do not contain code (for example a ligne with only an - ending parathensis) + """Return the effective number of common lines between lineset1 + and lineset2 filtered from non code lines. + + That is to say the number of common successive stripped + lines except those that do not contain code (for example + a line with only an ending parathensis) :param ls_1: first lineset :param stindex_1: first lineset starting index @@ -665,6 +669,7 @@ def _get_functions( @functools.total_ordering class LineSet: """Holds and indexes all the lines of a single source file. + Allows for correspondence between real lines of the source file and stripped ones, which are the real ones from which undesired patterns have been removed. """ @@ -737,9 +742,10 @@ def report_similarities( # wrapper to get a pylint checker from the similar class class SimilarChecker(BaseChecker, Similar, MapReduceMixin): - """Checks for similarities and duplicated code. This computation may be - memory / CPU intensive, so you should disable it if you experiment some - problems. + """Checks for similarities and duplicated code. + + This computation may be memory / CPU intensive, so you + should disable it if you experiment some problems. """ __implements__ = (IRawChecker,) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 3884fc32e4..5b4670803e 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -646,9 +646,7 @@ def _check_redundant_assert(self, node, infer): ) def _check_datetime(self, node): - """Check that a datetime was inferred. - If so, emit boolean-datetime warning. - """ + """Check that a datetime was inferred, if so, emit boolean-datetime warning.""" try: inferred = next(node.infer()) except astroid.InferenceError: diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index e89e8db5f3..3b2ed51068 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -933,6 +933,7 @@ def register(linter: "PyLinter") -> None: def str_eval(token): """Mostly replicate `ast.literal_eval(token)` manually to avoid any performance hit. + This supports f-strings, contrary to `ast.literal_eval`. We have to support all string literal notations: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 78e92becb8..d3ab8ca8e4 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1289,6 +1289,7 @@ def _check_uninferable_call(self, node): def _check_argument_order(self, node, call_site, called, called_param_names): """Match the supplied argument names against the function parameters. + Warn if some argument names are not in the same order as they are in the function signature. """ @@ -1339,8 +1340,7 @@ def _check_isinstance_args(self, node): @check_messages(*(list(MSGS.keys()))) def visit_call(self, node: nodes.Call) -> None: """Check that called functions/methods are inferred to callable objects, - and that the arguments passed to the function match the parameters in - the inferred function's definition + and that passed arguments match the parameters in the inferred function. """ called = safe_infer(node.func) @@ -2001,6 +2001,7 @@ def visit_for(self, node: nodes.For) -> None: class IterableChecker(BaseChecker): """Checks for non-iterables used in an iterable context. + Contexts include: - for-statement - starargs in function call diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 026a78b5e7..4cc413e5c3 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -512,8 +512,9 @@ def __init__(self, index): def parse_format_string( format_string: str, ) -> Tuple[Set[str], int, Dict[str, str], List[str]]: - """Parses a format string, returning a tuple of (keys, num_args), where 'keys' - is the set of mapping keys in the format string, and 'num_args' is the number + """Parses a format string, returning a tuple (keys, num_args). + + Where 'keys' is the set of mapping keys in the format string, and 'num_args' is the number of arguments required by the format string. Raises IncompleteFormatString or UnsupportedFormatCharacter if a parse error occurs. """ @@ -593,8 +594,9 @@ def split_format_field_names(format_string) -> Tuple[str, Iterable[Tuple[bool, s def collect_string_fields(format_string) -> Iterable[Optional[str]]: """Given a format string, return an iterator - of all the valid format fields. It handles nested fields - as well. + of all the valid format fields. + + It handles nested fields as well. """ formatter = string.Formatter() try: @@ -627,8 +629,9 @@ def parse_format_method_string( format_string: str, ) -> Tuple[List[Tuple[str, List[Tuple[bool, str]]]], int, int]: """Parses a PEP 3101 format string, returning a tuple of - (keyword_arguments, implicit_pos_args_cnt, explicit_pos_args), - where keyword_arguments is the set of mapping keys in the format string, implicit_pos_args_cnt + (keyword_arguments, implicit_pos_args_cnt, explicit_pos_args). + + keyword_arguments is the set of mapping keys in the format string, implicit_pos_args_cnt is the number of arguments required by the format string and explicit_pos_args is the number of arguments passed with the position. """ @@ -1045,8 +1048,8 @@ def get_exception_handlers( def is_node_inside_try_except(node: nodes.Raise) -> bool: - """Check if the node is directly under a Try/Except statement. - (but not under an ExceptHandler!) + """Check if the node is directly under a Try/Except statement + (but not under an ExceptHandler!). Args: node (nodes.Raise): the node raising the exception. @@ -1366,7 +1369,9 @@ def is_registered_in_singledispatch_function(node: nodes.FunctionDef) -> bool: def get_node_last_lineno(node: nodes.NodeNG) -> int: - """Get the last lineno of the given node. For a simple statement this will just be node.lineno, + """Get the last lineno of the given node. + + For a simple statement this will just be node.lineno, but for a node that has child statements (e.g. a method) this will be the lineno of the last child statement recursively. """ @@ -1438,6 +1443,7 @@ def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool: def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool: """Check if first node is a subclass of second node. + :param child: Node to check for subclass. :param parent: Node to check for superclass. :returns: True if child is derived from parent. False otherwise. @@ -1658,6 +1664,7 @@ def is_typing_guard(node: nodes.If) -> bool: def is_node_in_guarded_import_block(node: nodes.NodeNG) -> bool: """Return True if node is part for guarded if block. + I.e. `sys.version_info` or `typing.TYPE_CHECKING` """ return isinstance(node.parent, nodes.If) and ( @@ -1731,8 +1738,9 @@ def get_node_first_ancestor_of_type_and_its_child( node: nodes.NodeNG, ancestor_type: Union[Type[T_Node], Tuple[Type[T_Node], ...]] ) -> Union[Tuple[None, None], Tuple[T_Node, nodes.NodeNG]]: """Modified version of get_node_first_ancestor_of_type to also return the - descendant visited directly before reaching the sought ancestor. Useful - for extracting whether a statement is guarded by a try, except, or finally + descendant visited directly before reaching the sought ancestor + + Useful for extracting whether a statement is guarded by a try, except, or finally when searching for a TryFinally ancestor. """ child = node diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index b61c5a68ae..d4663e37ba 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -310,7 +310,9 @@ def _infer_name_module(node, name): def _fix_dot_imports(not_consumed): """Try to fix imports with multiple dots, by returning a dictionary - with the import names expanded. The function unflattens root imports, + with the import names expanded. + + The function unflattens root imports, like 'xml' (when we have both 'xml.etree' and 'xml.sax'), to 'xml.etree' and 'xml.sax' respectively. """ @@ -351,8 +353,9 @@ def _fix_dot_imports(not_consumed): def _find_frame_imports(name, frame): - """Detect imports in the frame, with the required - *name*. Such imports can be considered assignments. + """Detect imports in the frame, with the required *name*. + + Such imports can be considered assignments. Returns True if an import for the given name was found. """ imports = frame.nodes_of_class((nodes.Import, nodes.ImportFrom)) @@ -603,6 +606,7 @@ def consumed(self): def consumed_uncertain(self) -> DefaultDict[str, List[nodes.NodeNG]]: """Retrieves nodes filtered out by get_next_to_consume() that may not have executed, such as statements in except blocks, or statements + in try blocks (when evaluating their corresponding except and finally blocks). Checkers that want to treat the statements as executed (e.g. for unused-variable) may need to add them back. @@ -615,6 +619,7 @@ def scope_type(self): def mark_as_consumed(self, name, consumed_nodes): """Mark the given nodes as consumed for the name. + If all of the nodes for the name were consumed, delete the name from the to_consume dictionary """ @@ -627,8 +632,9 @@ def mark_as_consumed(self, name, consumed_nodes): del self.to_consume[name] def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]: - """Return a list of the nodes that define `node` from this scope. If it is - uncertain whether a node will be consumed, such as for statements in + """Return a list of the nodes that define `node` from this scope. + + If it is uncertain whether a node will be consumed, such as for statements in except blocks, add it to self.consumed_uncertain instead of returning it. Return None to indicate a special case that needs to be handled by the caller. """ @@ -804,6 +810,7 @@ def _check_loop_finishes_via_except( node: nodes.NodeNG, other_node_try_except: nodes.TryExcept ) -> bool: """Check for a case described in https://github.com/PyCQA/pylint/issues/5683. + It consists of a specific control flow scenario where the only non-break exit from a loop consists of the very except handler we are examining, such that code in the `else` branch of the loop can depend on it @@ -895,9 +902,8 @@ def _recursive_search_for_continue_before_break( def _uncertain_nodes_in_try_blocks_when_evaluating_except_blocks( found_nodes: List[nodes.NodeNG], node_statement: nodes.Statement ) -> List[nodes.NodeNG]: - """Return any nodes in ``found_nodes`` that should be treated as uncertain - because they are in a try block and the ``node_statement`` being evaluated - is in one of its except handlers. + """Return any nodes in ``found_nodes`` that should be treated as uncertain because they + are in a try block and the ``node_statement`` being evaluated is in one of its except handlers. """ uncertain_nodes: List[nodes.NodeNG] = [] closest_except_handler = utils.get_node_first_ancestor_of_type( @@ -994,7 +1000,9 @@ def _uncertain_nodes_in_try_blocks_when_evaluating_finally_blocks( # pylint: disable=too-many-public-methods class VariablesChecker(BaseChecker): - """Checks for + """BaseChecker for variables. + + Checks for * unused variables / imports * undefined variables * redefinition of variable from builtins or from an outer scope @@ -1781,9 +1789,8 @@ def visit_importfrom(self, node: nodes.ImportFrom) -> None: "unbalanced-tuple-unpacking", "unpacking-non-sequence", "self-cls-assignment" ) def visit_assign(self, node: nodes.Assign) -> None: - """Check unbalanced tuple unpacking for assignments - and unpacking non-sequences as well as in case self/cls - get assigned. + """Check unbalanced tuple unpacking for assignments and unpacking + non-sequences as well as in case self/cls get assigned. """ self._check_self_cls_assign(node) if not isinstance(node.targets[0], (nodes.Tuple, nodes.List)): @@ -2485,8 +2492,7 @@ def _has_homonym_in_upper_function_scope( self, node: nodes.Name, index: int ) -> bool: """Return whether there is a node with the same name in the - to_consume dict of an upper scope and if that scope is a - function + to_consume dict of an upper scope and if that scope is a function :param node: node to check for :param index: index of the current consumer inside self._to_consume @@ -2615,8 +2621,7 @@ def _nodes_to_unpack(node: nodes.NodeNG) -> Optional[List[nodes.NodeNG]]: def _check_module_attrs(self, node, module, module_names): """Check that module_names (list of string) are accessible through the - given module - if the latest access name corresponds to a module, return it + given module, if the latest access name corresponds to a module, return it """ while module_names: name = module_names.pop(0) diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 70b312539e..2e295ca110 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -400,7 +400,9 @@ def match_param_docs(self): class EpytextDocstring(SphinxDocstring): - """Epytext is similar to Sphinx. See the docs: + """Epytext is similar to Sphinx. + + See the docs: http://epydoc.sourceforge.net/epytext.html http://epydoc.sourceforge.net/fields.html#fields diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index 5a3e3ba17d..d1bb04928a 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -252,6 +252,7 @@ def _check_prev_sibling_to_if_stmt( prev_sibling: Optional[nodes.NodeNG], name: Optional[str] ) -> TypeGuard[Union[nodes.Assign, nodes.AnnAssign]]: """Check if previous sibling is an assignment with the same name. + Ignore statements which span multiple lines. """ if prev_sibling is None or prev_sibling.tolineno - prev_sibling.fromlineno != 0: diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py index 592e15a5b9..0d0865a133 100644 --- a/pylint/extensions/comparetozero.py +++ b/pylint/extensions/comparetozero.py @@ -31,6 +31,7 @@ def _is_constant_zero(node): class CompareToZeroChecker(checkers.BaseChecker): """Checks for comparisons to zero. + Most of the time you should use the fact that integers with a value of 0 are false. An exception to this rule is when 0 is allowed in the program and has a different meaning than None! diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 35324ac041..d618ea4d4d 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -492,10 +492,8 @@ def check_arguments_in_docstring( warning_node: astroid.NodeNG, accept_no_param_doc: Optional[bool] = None, ): - """Check that all parameters in a function, method or class constructor - on the one hand and the parameters mentioned in the parameter - documentation (e.g. the Sphinx tags 'param' and 'type') on the other - hand are consistent with each other. + """Check that all parameters are consistent with the parameters mentioned + in the parameter documentation (e.g. the Sphinx tags 'param' and 'type'). * Undocumented parameters except 'self' are noticed. * Undocumented parameter types except for 'self' and the ``*`` diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py index 15bdd1e584..096b96ec90 100644 --- a/pylint/extensions/emptystring.py +++ b/pylint/extensions/emptystring.py @@ -26,6 +26,7 @@ class CompareToEmptyStringChecker(checkers.BaseChecker): """Checks for comparisons to empty string. + Most of the time you should use the fact that empty strings are false. An exception to this rule is when an empty string value is allowed in the program and has a different meaning than None! diff --git a/pylint/extensions/eq_without_hash.py b/pylint/extensions/eq_without_hash.py index b0dd6ce46f..aeadac9b33 100644 --- a/pylint/extensions/eq_without_hash.py +++ b/pylint/extensions/eq_without_hash.py @@ -1,9 +1,10 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -"""This is the remnant of the python3 checker. It was removed because -the transition from python 2 to python3 is behind us, but some checks -are still useful in python3 after all. +"""This is the remnant of the python3 checker. + +It was removed because the transition from python 2 to python3 is +behind us, but some checks are still useful in python3 after all. See https://github.com/PyCQA/pylint/issues/5025 """ diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index 11c79eb04e..c3bf9aab2e 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -19,6 +19,7 @@ class OverlappingExceptionsChecker(checkers.BaseChecker): """Checks for two or more exceptions in the same exception handler clause that are identical or parts of the same inheritance hierarchy + (i.e. overlapping). """ diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index b2e6d769d0..07d18fdc7f 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -305,8 +305,9 @@ def _check_for_typing_alias( @check_messages("consider-using-alias") def leave_module(self, node: nodes.Module) -> None: """After parsing of module is complete, add messages for - 'consider-using-alias' check. Make sure results are safe - to recommend / collision free. + 'consider-using-alias' check. + + Make sure results are safe to recommend / collision free. """ if self._py39_plus: for msg in self._deprecated_typing_alias_msgs: diff --git a/pylint/graph.py b/pylint/graph.py index f75ce4e641..c35cf8c5da 100644 --- a/pylint/graph.py +++ b/pylint/graph.py @@ -152,7 +152,8 @@ def emit(self, line): def emit_edge(self, name1, name2, **props): """Emit an edge from to . - edge properties: see https://www.graphviz.org/doc/info/attrs.html + + For edge properties: see https://www.graphviz.org/doc/info/attrs.html """ attrs = [f'{prop}="{value}"' for prop, value in props.items()] n_from, n_to = normalize_node_id(name1), normalize_node_id(name2) @@ -160,7 +161,8 @@ def emit_edge(self, name1, name2, **props): def emit_node(self, name, **props): """Emit a node with given properties. - node properties: see https://www.graphviz.org/doc/info/attrs.html + + For node properties: see https://www.graphviz.org/doc/info/attrs.html """ attrs = [f'{prop}="{value}"' for prop, value in props.items()] self.emit(f"{normalize_node_id(name)} [{', '.join(sorted(attrs))}];") @@ -172,9 +174,8 @@ def normalize_node_id(nid): def get_cycles(graph_dict, vertices=None): - """Given a dictionary representing an ordered graph (i.e. key are vertices - and values is a list of destination vertices representing edges), return a - list of detected cycles + """Return a list of detected cycles in a dictionary representing an ordered graph + (i.e. key are vertices and values is a list of destination vertices representing edges) """ if not graph_dict: return () diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 296e6b5778..236e49c992 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -18,7 +18,9 @@ def _is_package_cb(inner_path, parts): def get_python_path(filepath: str) -> str: """TODO This get the python path with the (bad) assumption that there is always - an __init__.py. This is not true since python 3.3 and is causing problem. + an __init__.py + + This is not true since python 3.3 and is causing problem. """ dirname = os.path.realpath(os.path.expanduser(filepath)) if not os.path.isdir(dirname): diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index f3e2d6cb45..99c3714931 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -133,7 +133,8 @@ def check_parallel( files: Iterable[FileItem], arguments: Union[None, str, Sequence[str]] = None, ) -> None: - """Use the given linter to lint the files with given amount of workers (jobs) + """Use the given linter to lint the files with given amount of workers (jobs). + This splits the work filestream-by-filestream. If you need to do work across multiple files, as in the similarity-checker, then inherit from MapReduceMixin and implement the map/reduce mixin functionality. diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 6cbdc5cc71..b719051844 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -398,7 +398,9 @@ def cb_add_plugins(self, name, value): self._plugins.extend(utils._splitstrip(value)) def cb_error_mode(self, *args, **kwargs): - """Error mode: + """Callback for --errors-only. + + Error mode: * disable all but error messages * disable the 'miscellaneous' checker which can be safely deactivated in debug diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index ee19a5b659..fccdcf5579 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -67,7 +67,10 @@ def emit_node( type_: NodeType, properties: Optional[NodeProperties] = None, ) -> None: - """Create a new node. Nodes can be classes, packages, participants etc.""" + """Create a new node. + + Nodes can be classes, packages, participants etc. + """ if properties is None: properties = NodeProperties(label=name) shape = SHAPES[type_] diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 61c8d1f7ea..808f34266c 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -15,6 +15,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE """Visitor doing some postprocessing on the astroid tree. + Try to resolve definitions (namespace) dictionary, relationship... """ import collections diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index 0140419931..a725d03f4c 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -38,7 +38,10 @@ def emit_node( type_: NodeType, properties: Optional[NodeProperties] = None, ) -> None: - """Create a new node. Nodes can be classes, packages, participants etc.""" + """Create a new node. + + Nodes can be classes, packages, participants etc. + """ if properties is None: properties = NodeProperties(label=name) stereotype = "~~Interface~~" if type_ is NodeType.INTERFACE else "" diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index 5693e626ff..2e643fe1fe 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -48,7 +48,10 @@ def emit_node( type_: NodeType, properties: Optional[NodeProperties] = None, ) -> None: - """Create a new node. Nodes can be classes, packages, participants etc.""" + """Create a new node. + + Nodes can be classes, packages, participants etc. + """ if properties is None: properties = NodeProperties(label=name) stereotype = " << interface >>" if type_ is NodeType.INTERFACE else "" diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index ca7f9b0a2a..6f4d62f364 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -85,7 +85,10 @@ def emit_node( type_: NodeType, properties: Optional[NodeProperties] = None, ) -> None: - """Create a new node. Nodes can be classes, packages, participants etc.""" + """Create a new node. + + Nodes can be classes, packages, participants etc. + """ @abstractmethod def emit_edge( diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 5cb4138e74..52bfb4fbdd 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -286,6 +286,7 @@ def infer_node(node: Union[nodes.AssignAttr, nodes.AssignName]) -> set: def check_graphviz_availability(): """Check if the ``dot`` command is available on the machine. + This is needed if image output is desired and ``dot`` is used to convert from *.dot or *.gv into the final output format. """ diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index f7e2a46652..52e99a300a 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -15,6 +15,7 @@ """Functions to generate files readable with Georg Sander's vcg (Visualization of Compiler Graphs). + You can download vcg at https://rw4.cs.uni-sb.de/~sander/html/gshome.html Note that vcg exists as a debian package. See vcg's documentation for explanation about the different values that @@ -212,7 +213,10 @@ def emit_node( type_: NodeType, properties: Optional[NodeProperties] = None, ) -> None: - """Create a new node. Nodes can be classes, packages, participants etc.""" + """Create a new node. + + Nodes can be classes, packages, participants etc. + """ if properties is None: properties = NodeProperties(label=name) elif properties.label is None: diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index a68c8c423d..c2c7382d29 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -49,9 +49,10 @@ def out(self): @out.setter def out(self, output: Optional[AnyFile] = None): - """MultiReporter doesn't have its own output. This method is only - provided for API parity with BaseReporter and should not be called - with non-None values for 'output'. + """MultiReporter doesn't have its own output. + + This method is only provided for API parity with BaseReporter + and should not be called with non-None values for 'output'. """ self.__out = None if output is not None: diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index e851ccfbe9..70dc01f383 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -23,7 +23,10 @@ class MessageTest(NamedTuple): col_offset: Optional[int] = None end_line: Optional[int] = None end_col_offset: Optional[int] = None - """Used to test messages produced by pylint. Class name cannot start with Test as pytest doesn't allow constructors in test classes.""" + """Used to test messages produced by pylint. + + Class name cannot start with Test as pytest doesn't allow constructors in test classes. + """ class MalformedOutputLineException(Exception): @@ -93,8 +96,9 @@ def from_msg(cls, msg: Message, check_endline: bool = True) -> "OutputLine": @staticmethod def _get_column(column: str) -> int: - """Handle column numbers except for python < 3.8. The ast parser in those versions doesn't - return them. + """Handle column numbers except for python < 3.8. + + The ast parser in those versions doesn't return them. """ if not PY38_PLUS: # We check the column only for the new better ast parser introduced in python 3.8 diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index ac72def41c..34d66d2177 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -8,6 +8,7 @@ # A NamedTuple is not possible as some tests need to modify attributes during the test. class PyreverseConfig: # pylint: disable=too-many-instance-attributes, too-many-arguments """Holds the configuration options for Pyreverse. + The default values correspond to the defaults of the options' parser. """ diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index c3d120458e..14f57a16c1 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -277,7 +277,7 @@ def get_global_option( def _splitstrip(string, sep=","): """Return a list of stripped string by splitting the string given as - argument on `sep` (',' by default). Empty string are discarded. + argument on `sep` (',' by default), empty strings are discarded. >>> _splitstrip('a, b, c , 4,,') ['a', 'b', 'c', '4'] From 8e9a44727545b09ccc0161d72b8b66bc92d95d88 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 13 Mar 2022 10:20:48 -0400 Subject: [PATCH 269/357] Add regression test for #5770 (#5846) --- ChangeLog | 5 +++++ doc/whatsnew/2.13.rst | 5 +++++ .../regression_newtype_fstring.py | 9 +++++++++ .../regression_newtype_fstring.rc | 2 ++ 4 files changed, 21 insertions(+) create mode 100644 tests/functional/ext/redefined_variable_type/regression_newtype_fstring.py create mode 100644 tests/functional/ext/redefined_variable_type/regression_newtype_fstring.rc diff --git a/ChangeLog b/ChangeLog index a78a1d0048..726ccc134e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -137,6 +137,11 @@ Release date: TBA Closes #5731 +* Fixed a crash involving a ``NewType`` named with an f-string. + + Closes #5770 + Ref PyCQA/astroid#1400 + * Improved ``bad-open-mode`` message when providing ``None`` to the ``mode`` argument of an ``open()`` call. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 9ddbc2ee7e..130a9ac69b 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -281,6 +281,11 @@ Other Changes Closes #5731 +* Fixed a crash involving a ``NewType`` named with an f-string. + + Closes #5770 + Ref PyCQA/astroid#1400 + * Improved ``bad-open-mode`` message when providing ``None`` to the ``mode`` argument of an ``open()`` call. diff --git a/tests/functional/ext/redefined_variable_type/regression_newtype_fstring.py b/tests/functional/ext/redefined_variable_type/regression_newtype_fstring.py new file mode 100644 index 0000000000..1e3cb28080 --- /dev/null +++ b/tests/functional/ext/redefined_variable_type/regression_newtype_fstring.py @@ -0,0 +1,9 @@ +"""Regression test for issue 5770: NewType created with f-string +See: https://github.com/PyCQA/pylint/issues/5770 +""" +from typing import NewType + +def make_new_type(suffix): + """Dynamically create a NewType with `suffix`""" + new_type = NewType(f'IntRange_{suffix}', suffix) + print(new_type) diff --git a/tests/functional/ext/redefined_variable_type/regression_newtype_fstring.rc b/tests/functional/ext/redefined_variable_type/regression_newtype_fstring.rc new file mode 100644 index 0000000000..8ee18a8f10 --- /dev/null +++ b/tests/functional/ext/redefined_variable_type/regression_newtype_fstring.rc @@ -0,0 +1,2 @@ +[MASTER] +load-plugins=pylint.extensions.redefined_variable_type, From dee07af04df93c4b5edeb2492eaa5c46ebcdb7b7 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 13 Mar 2022 17:55:12 +0100 Subject: [PATCH 270/357] Fix English punctuation and grammar (FAQs) --- doc/faq.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/doc/faq.rst b/doc/faq.rst index fe41466307..04e3b14935 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -59,7 +59,7 @@ The supported running environment since Pylint 2.12.1 is Python 3.6.2+. Pylint expects the name of a package or module as its argument. As a convenience, you can give it a file name if it's possible to guess a module name from -the file's path using the python path. Some examples : +the file's path using the python path. Some examples: "pylint mymodule.py" should always work since the current working directory is automatically added on top of the python path @@ -142,23 +142,22 @@ When ``--recursive=y`` option is used, modules and packages are also accepted as ================== 4.1 How to disable a particular message? ------------------------------------------------------------ +---------------------------------------- -For a single line : Add ``#pylint: disable=some-message,another-one`` at the -end of the desired line of code. Since Pylint 2.10 you can also use -``#pylint: disable-next=...`` on the line just above the problem. -``...`` in the following example is a short hand for the list of -messages you want to disable. +For just a single line, add ``#pylint: disable=some-message,another-one`` at the end of +the desired line of code. Since Pylint 2.10 you can also use ``#pylint: disable-next=...`` +on the line just above the problem. ``...`` in the following example is short for the +list of messages you want to disable. -For larger disable : You can add ``#pylint: disable=...`` at the block level to -disable for the block. It's possible to enable for the reminder of the block -with ``#pylint: enable=...`` A block is either a scope (say a function, a module), -or a multiline statement (try, finally, if statements, for loops). -`It's currently impossible to disable inside an else block`_ +For larger amounts of code, you can add ``#pylint: disable=...`` at the block level +to disable messages for the entire block. It's possible to re-enable a message for the +remainder of the block with ``#pylint: enable=...``. A block is either a scope (say a +function, a module) or a multiline statement (try, finally, if statements, for loops). +Note: It's currently impossible to `disable inside an else block`_. Read :ref:`message-control` for details and examples. -.. _`It's currently impossible to disable inside an else block`: https://github.com/PyCQA/pylint/issues/872 +.. _`disable inside an else block`: https://github.com/PyCQA/pylint/issues/872 4.2 Is there a way to disable a message for a particular module only? --------------------------------------------------------------------- From 2b81cbb6d53bec271eeb7af16b57f79f10a99146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 13 Mar 2022 22:59:37 +0100 Subject: [PATCH 271/357] Fix typing of safe_infer (#5902) --- pylint/checkers/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 4cc413e5c3..4e775e3d24 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1234,7 +1234,7 @@ def supports_delitem(value: nodes.NodeNG, _: nodes.NodeNG) -> bool: return _supports_protocol(value, _supports_delitem_protocol) -def _get_python_type_of_node(node): +def _get_python_type_of_node(node: nodes.NodeNG) -> Optional[str]: pytype = getattr(node, "pytype", None) if callable(pytype): return pytype() @@ -1242,13 +1242,15 @@ def _get_python_type_of_node(node): @lru_cache(maxsize=1024) -def safe_infer(node: nodes.NodeNG, context=None) -> Optional[nodes.NodeNG]: +def safe_infer( + node: nodes.NodeNG, context: Optional[InferenceContext] = None +) -> Union[nodes.NodeNG, Type[astroid.Uninferable], None]: """Return the inferred value for the given node. Return None if inference failed or if there is some ambiguity (more than one node has been inferred of different types). """ - inferred_types = set() + inferred_types: Set[Optional[str]] = set() try: infer_gen = node.infer(context=context) value = next(infer_gen) From 9b8841ad90e9534213af125aec52afbd15353e40 Mon Sep 17 00:00:00 2001 From: Arianna Date: Mon, 14 Mar 2022 16:17:00 -0500 Subject: [PATCH 272/357] Introduce new 'import-private-name' checker (#5610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- ChangeLog | 5 + doc/whatsnew/2.13.rst | 4 + pylint/checkers/__init__.py | 3 +- pylint/checkers/utils.py | 5 + pylint/extensions/private_import.py | 248 ++++++++++++++++++ tests/extensions/test_private_import.py | 69 +++++ .../ext/private_import/private_import.py | 121 +++++++++ .../ext/private_import/private_import.rc | 2 + .../ext/private_import/private_import.txt | 20 ++ 9 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 pylint/extensions/private_import.py create mode 100644 tests/extensions/test_private_import.py create mode 100644 tests/functional/ext/private_import/private_import.py create mode 100644 tests/functional/ext/private_import/private_import.rc create mode 100644 tests/functional/ext/private_import/private_import.txt diff --git a/ChangeLog b/ChangeLog index 726ccc134e..52cb627f2e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -54,6 +54,11 @@ Release date: TBA closes #5722 +* New extension ``import-private-name``: indicate imports of external private packages + and objects (prefixed with ``_``). It can be loaded using ``load-plugins=pylint.extensions.private_import``. + + Closes #5463 + * Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were defined outside the top level of a class. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 130a9ac69b..d277d0d82d 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -76,6 +76,10 @@ Removed checkers Extensions ========== +* New extension ``import-private-name``: indicate imports of external private packages + and objects (prefixed with ``_``). It can be loaded using ``load-plugins=pylint.extensions.private_import``. + + Closes #5463 * Pyreverse - add output in mermaid-js format and html which is an mermaid js diagram with html boilerplate diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 160bad8af2..61ce67ffef 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -45,7 +45,8 @@ 24: non-ascii-names 25: unicode 26: unsupported_version -27-50: not yet used: reserved for future internal checkers. +27: private-import +28-50: not yet used: reserved for future internal checkers. This file is not updated. Use script/get_unused_message_id_category.py to get the next free checker id. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 4e775e3d24..f53ae88456 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1664,6 +1664,11 @@ def is_typing_guard(node: nodes.If) -> bool: ) and node.test.as_string().endswith("TYPE_CHECKING") +def is_node_in_typing_guarded_import_block(node: nodes.NodeNG) -> bool: + """Return True if node is part for guarded `typing.TYPE_CHECKING` if block.""" + return isinstance(node.parent, nodes.If) and is_typing_guard(node.parent) + + def is_node_in_guarded_import_block(node: nodes.NodeNG) -> bool: """Return True if node is part for guarded if block. diff --git a/pylint/extensions/private_import.py b/pylint/extensions/private_import.py new file mode 100644 index 0000000000..88033fa1e5 --- /dev/null +++ b/pylint/extensions/private_import.py @@ -0,0 +1,248 @@ +"""Check for imports on private external modules and names.""" +from pathlib import Path +from typing import TYPE_CHECKING, Dict, List, Union + +from astroid import nodes + +from pylint.checkers import BaseChecker, utils +from pylint.interfaces import HIGH, IAstroidChecker + +if TYPE_CHECKING: + from pylint.lint.pylinter import PyLinter + + +class PrivateImportChecker(BaseChecker): + + __implements__ = (IAstroidChecker,) + name = "import-private-name" + msgs = { + "C2701": ( + "Imported private %s (%s)", + "import-private-name", + "Used when a private module or object prefixed with _ is imported. " + "PEP8 guidance on Naming Conventions states that public attributes with " + "leading underscores should be considered private.", + ), + } + + def __init__(self, linter: "PyLinter") -> None: + BaseChecker.__init__(self, linter) + + # A mapping of private names used as a type annotation to whether it is an acceptable import + self.all_used_type_annotations: Dict[str, bool] = {} + self.populated_annotations = False + + @utils.check_messages("import-private-name") + def visit_import(self, node: nodes.Import) -> None: + if utils.is_node_in_typing_guarded_import_block(node): + return + names = [name[0] for name in node.names] + private_names = self._get_private_imports(names) + private_names = self._get_type_annotation_names(node, private_names) + if private_names: + imported_identifier = "modules" if len(private_names) > 1 else "module" + private_name_string = ", ".join(private_names) + self.add_message( + "import-private-name", + node=node, + args=(imported_identifier, private_name_string), + confidence=HIGH, + ) + + @utils.check_messages("import-private-name") + def visit_importfrom(self, node: nodes.ImportFrom) -> None: + if utils.is_node_in_typing_guarded_import_block(node): + return + # Only check imported names if the module is external + if self.same_root_dir(node, node.modname): + return + + names = [n[0] for n in node.names] + + # Check the imported objects first. If they are all valid type annotations, the package can be private + private_names = self._get_type_annotation_names(node, names) + if not private_names: + return + + # There are invalid imported objects, so check the name of the package + private_module_imports = self._get_private_imports([node.modname]) + private_module_imports = self._get_type_annotation_names( + node, private_module_imports + ) + if private_module_imports: + self.add_message( + "import-private-name", + node=node, + args=("module", private_module_imports[0]), + confidence=HIGH, + ) + return # Do not emit messages on the objects if the package is private + + private_names = self._get_private_imports(private_names) + + if private_names: + imported_identifier = "objects" if len(private_names) > 1 else "object" + private_name_string = ", ".join(private_names) + self.add_message( + "import-private-name", + node=node, + args=(imported_identifier, private_name_string), + confidence=HIGH, + ) + + def _get_private_imports(self, names: List[str]) -> List[str]: + """Returns the private names from input names by a simple string check.""" + return [name for name in names if self._name_is_private(name)] + + @staticmethod + def _name_is_private(name: str) -> bool: + """Returns true if the name exists, starts with `_`, and if len(name) > 4 + it is not a dunder, i.e. it does not begin and end with two underscores. + """ + return ( + bool(name) + and name[0] == "_" + and (len(name) <= 4 or name[1] != "_" or name[-2:] != "__") + ) + + def _get_type_annotation_names( + self, node: nodes.Import, names: List[str] + ) -> List[str]: + """Removes from names any names that are used as type annotations with no other illegal usages.""" + if names and not self.populated_annotations: + self._populate_type_annotations(node.root(), self.all_used_type_annotations) + self.populated_annotations = True + + return [ + n + for n in names + if n not in self.all_used_type_annotations + or ( + n in self.all_used_type_annotations + and not self.all_used_type_annotations[n] + ) + ] + + def _populate_type_annotations( + self, node: nodes.LocalsDictNodeNG, all_used_type_annotations: Dict[str, bool] + ) -> None: + """Adds into the dict `all_used_type_annotations` the names of all names ever used as a type annotation + in the scope and nested scopes of node and whether these names are only used for type checking. + """ + for name in node.locals: + # If we find a private type annotation, make sure we do not mask illegal usages + private_name = None + # All the assignments using this variable that we might have to check for illegal usages later + name_assignments = [] + for usage_node in node.locals[name]: + if isinstance(usage_node, nodes.AssignName) and isinstance( + usage_node.parent, (nodes.AnnAssign, nodes.Assign) + ): + assign_parent = usage_node.parent + if isinstance(assign_parent, nodes.AnnAssign): + name_assignments.append(assign_parent) + private_name = self._populate_type_annotations_annotation( + usage_node.parent.annotation, all_used_type_annotations + ) + elif isinstance(assign_parent, nodes.Assign): + name_assignments.append(assign_parent) + + if isinstance(usage_node, nodes.FunctionDef): + self._populate_type_annotations_function( + usage_node, all_used_type_annotations + ) + if isinstance(usage_node, nodes.LocalsDictNodeNG): + self._populate_type_annotations( + usage_node, all_used_type_annotations + ) + if private_name is not None: + # Found a new private annotation, make sure we are not accessing it elsewhere + all_used_type_annotations[ + private_name + ] = self._assignments_call_private_name(name_assignments, private_name) + + def _populate_type_annotations_function( + self, node: nodes.FunctionDef, all_used_type_annotations: Dict[str, bool] + ) -> None: + """Adds into the dict `all_used_type_annotations` the names of all names used as a type annotation + in the arguments and return type of the function node. + """ + if node.args and node.args.annotations: + for annotation in node.args.annotations: + self._populate_type_annotations_annotation( + annotation, all_used_type_annotations + ) + if node.returns: + self._populate_type_annotations_annotation( + node.returns, all_used_type_annotations + ) + + def _populate_type_annotations_annotation( + self, + node: Union[nodes.Attribute, nodes.Subscript, nodes.Name], + all_used_type_annotations: Dict[str, bool], + ) -> Union[str, None]: + """Handles the possiblity of an annotation either being a Name, i.e. just type, + or a Subscript e.g. `Optional[type]` or an Attribute, e.g. `pylint.lint.linter`. + """ + if isinstance(node, nodes.Name) and node.name not in all_used_type_annotations: + all_used_type_annotations[node.name] = True + return node.name + if isinstance(node, nodes.Subscript): # e.g. Optional[List[str]] + # slice is the next nested type + self._populate_type_annotations_annotation( + node.slice, all_used_type_annotations + ) + # value is the current type name: could be a Name or Attribute + return self._populate_type_annotations_annotation( + node.value, all_used_type_annotations + ) + if isinstance(node, nodes.Attribute): + # An attribute is a type like `pylint.lint.pylinter`. node.expr is the next level up, could be another attribute + return self._populate_type_annotations_annotation( + node.expr, all_used_type_annotations + ) + return None + + @staticmethod + def _assignments_call_private_name( + assignments: List[Union[nodes.AnnAssign, nodes.Assign]], private_name: str + ) -> bool: + """Returns True if no assignments involve accessing `private_name`.""" + if all(not assignment.value for assignment in assignments): + # Variable annotated but unassigned is unallowed because there may be a possible illegal access elsewhere + return False + for assignment in assignments: + current_attribute = None + if isinstance(assignment.value, nodes.Call): + current_attribute = assignment.value.func + elif isinstance(assignment.value, nodes.Attribute): + current_attribute = assignment.value + elif isinstance(assignment.value, nodes.Name): + current_attribute = assignment.value.name + if not current_attribute: + continue + while isinstance(current_attribute, (nodes.Attribute, nodes.Call)): + if isinstance(current_attribute, nodes.Call): + current_attribute = current_attribute.func + current_attribute = current_attribute.expr + if ( + isinstance(current_attribute, nodes.Name) + and current_attribute.name == private_name + ): + return False + return True + + @staticmethod + def same_root_dir(node: nodes.Import, import_mod_name: str) -> bool: + """Does the node's file's path contain the base name of `import_mod_name`?""" + if not import_mod_name: # from . import ... + return True + + base_import_package = import_mod_name.split(".")[0] + + return base_import_package in Path(node.root().file).parent.parts + + +def register(linter: "PyLinter") -> None: + linter.register_checker(PrivateImportChecker(linter)) diff --git a/tests/extensions/test_private_import.py b/tests/extensions/test_private_import.py new file mode 100644 index 0000000000..10648a8f91 --- /dev/null +++ b/tests/extensions/test_private_import.py @@ -0,0 +1,69 @@ +"""Tests the local module directory comparison logic which requires mocking file directories""" + +from unittest.mock import patch + +import astroid + +from pylint.extensions import private_import +from pylint.interfaces import HIGH +from pylint.testutils import CheckerTestCase, MessageTest + + +class TestPrivateImport(CheckerTestCase): + """The mocked dirname is the directory of the file being linted, the node is code inside that file""" + + CHECKER_CLASS = private_import.PrivateImportChecker + + @patch("pathlib.Path.parent") + def test_internal_module(self, parent) -> None: + parent.parts = ("", "dir", "module") + import_from = astroid.extract_node("""from module import _file""") + + with self.assertNoMessages(): + self.checker.visit_importfrom(import_from) + + @patch("pathlib.Path.parent") + def test_external_module_nested(self, parent) -> None: + parent.parts = ("", "dir", "module", "module_files", "util") + + import_from = astroid.extract_node("""from module import _file""") + + with self.assertNoMessages(): + self.checker.visit_importfrom(import_from) + + @patch("pathlib.Path.parent") + def test_external_module_dot_import(self, parent) -> None: + parent.parts = ("", "dir", "outer", "inner", "module_files", "util") + + import_from = astroid.extract_node("""from outer.inner import _file""") + + with self.assertNoMessages(): + self.checker.visit_importfrom(import_from) + + @patch("pathlib.Path.parent") + def test_external_module_dot_import_outer_only(self, parent) -> None: + parent.parts = ("", "dir", "outer", "extensions") + + import_from = astroid.extract_node("""from outer.inner import _file""") + + with self.assertNoMessages(): + self.checker.visit_importfrom(import_from) + + @patch("pathlib.Path.parent") + def test_external_module(self, parent) -> None: + parent.parts = ("", "dir", "other") + + import_from = astroid.extract_node("""from module import _file""") + + msg = MessageTest( + msg_id="import-private-name", + node=import_from, + line=1, + col_offset=0, + end_line=1, + end_col_offset=24, + args=("object", "_file"), + confidence=HIGH, + ) + with self.assertAddsMessages(msg): + self.checker.visit_importfrom(import_from) diff --git a/tests/functional/ext/private_import/private_import.py b/tests/functional/ext/private_import/private_import.py new file mode 100644 index 0000000000..52ef405c90 --- /dev/null +++ b/tests/functional/ext/private_import/private_import.py @@ -0,0 +1,121 @@ +"""Tests for import-private-name.""" +# pylint: disable=unused-import, missing-docstring, reimported, import-error, wrong-import-order +# pylint: disable=no-name-in-module, multiple-imports, ungrouped-imports, misplaced-future +# pylint: disable=wrong-import-position + +# Basic cases +from _world import hello # [import-private-name] +from _world import _hello # [import-private-name] +from city import _house # [import-private-name] +from city import a, _b, c, _d # [import-private-name] +from _city import a, _b, c, _d # [import-private-name] +from city import a, b, c, _d # [import-private-name] +import house +import _house # [import-private-name] +import _house, _chair, _stair # [import-private-name] +import house, _chair, _stair # [import-private-name] + +# Ignore dunders +import __asd__ +import __future__ +from __future__ import print_function +from __future__ import __print_function__ + +# Ignore local modules +# The check for local modules compares directory names in the path of the file being linted to +# the name of the module we are importing from. The use of `__init__.py` to indicate Python modules +# is deprecated so this is a heuristic solution. +# If we were importing from `pylint`, it would be counted as a valid internal private import +# and not emit a message as long as this file has a parent directory called `pylint`, even though +# we are not importing from that directory. (We would be importing from `pylint/pylint`.) +from private_import import _private # pylint: disable=import-self +from private_import.other_file import _private +from . import _private +from astroid import _private # [import-private-name] +from sys import _private # [import-private-name] + +# Ignore typecheck +from typing import TYPE_CHECKING, List, Optional + +if TYPE_CHECKING: + import _TreeType + from types import _TreeType + from _types import TreeType + from _types import _TreeType + +# No error since imports are used as type annotations +from classes import _PrivateClassA, safe_get_A +from classes import _PrivateClassB +from classes import _PrivateClassC + +a_var: _PrivateClassA = safe_get_A() + +def b_func(class_b: _PrivateClassB): + print(class_b) + +def c_func() -> _PrivateClassC: + return None + +# Used as typing in slices +from classes import _SubScriptA +from classes import _SubScriptB + +a: Optional[_SubScriptA] +b: Optional[_SubScriptB[List]] + +import _TypeContainerA +import _TypeContainerB +import _TypeContainerC + +import SafeContainerA +a2: _TypeContainerA.A = SafeContainerA.safe_get_a() + +def b_func2(class_b2: _TypeContainerB.B): + print(class_b2) + +def c2_func() -> _TypeContainerC.C: + return None + +# This is allowed since all the imports are used for typing +from _TypeContainerExtra import TypeExtraA, TypeExtraB +from MakerContainerExtra import GetA, GetB +extraA: TypeExtraA = GetA() +extraB: TypeExtraB = GetB() + +# This is not allowed because there is an import not used for typing +from _TypeContainerExtra2 import TypeExtra2, NotTypeExtra # [import-private-name] +extra2: TypeExtra2 + +# Try many cases to ensure that type annotation usages of a private import +# do not mask other illegal usages of the import +import _private_module # [import-private-name] +my_var: _private_module.Thing = _private_module.Thing() + +import _private_module2 # [import-private-name] +my_var2: _private_module2.Thing2 +my_var2 = _private_module2.Thing2() + +import _private_module3 # [import-private-name] +my_var3: _private_module3.Thing3 +my_var3 = _private_module3.Thing3 +my_var3_2: _private_module3.Thing3 + +import _private_module4 # [import-private-name] +my_var4: _private_module4.Thing4 +my_var4 = _private_module4.get_callers().get_thing4() + +from _private_module5 import PrivateClass # [import-private-name] +my_var5: PrivateClass +my_var5 = PrivateClass() + +from _private_module6 import PrivateClass2 # [import-private-name] +my_var6: PrivateClass2 = PrivateClass2() + +from public_module import _PrivateClass3 # [import-private-name] +my_var7: _PrivateClass3 = _PrivateClass3() + +# Even though we do not see the private call, the type check does not keep us from emitting +# because we do not use that variable +import _private_module_unreachable # [import-private-name] +my_var8: _private_module_unreachable.Thing8 +_private_module_unreachable.Thing8() diff --git a/tests/functional/ext/private_import/private_import.rc b/tests/functional/ext/private_import/private_import.rc new file mode 100644 index 0000000000..c9bbc23f1f --- /dev/null +++ b/tests/functional/ext/private_import/private_import.rc @@ -0,0 +1,2 @@ +[MASTER] +load-plugins=pylint.extensions.private_import, diff --git a/tests/functional/ext/private_import/private_import.txt b/tests/functional/ext/private_import/private_import.txt new file mode 100644 index 0000000000..f618d58587 --- /dev/null +++ b/tests/functional/ext/private_import/private_import.txt @@ -0,0 +1,20 @@ +import-private-name:7:0:7:24::Imported private module (_world):HIGH +import-private-name:8:0:8:25::Imported private module (_world):HIGH +import-private-name:9:0:9:23::Imported private object (_house):HIGH +import-private-name:10:0:10:29::Imported private objects (_b, _d):HIGH +import-private-name:11:0:11:30::Imported private module (_city):HIGH +import-private-name:12:0:12:28::Imported private object (_d):HIGH +import-private-name:14:0:14:13::Imported private module (_house):HIGH +import-private-name:15:0:15:29::Imported private modules (_house, _chair, _stair):HIGH +import-private-name:16:0:16:28::Imported private modules (_chair, _stair):HIGH +import-private-name:34:0:34:28::Imported private object (_private):HIGH +import-private-name:35:0:35:24::Imported private object (_private):HIGH +import-private-name:86:0:86:57::Imported private module (_TypeContainerExtra2):HIGH +import-private-name:91:0:91:22::Imported private module (_private_module):HIGH +import-private-name:94:0:94:23::Imported private module (_private_module2):HIGH +import-private-name:98:0:98:23::Imported private module (_private_module3):HIGH +import-private-name:103:0:103:23::Imported private module (_private_module4):HIGH +import-private-name:107:0:107:41::Imported private module (_private_module5):HIGH +import-private-name:111:0:111:42::Imported private module (_private_module6):HIGH +import-private-name:114:0:114:40::Imported private object (_PrivateClass3):HIGH +import-private-name:119:0:119:34::Imported private module (_private_module_unreachable):HIGH From 6aba371d87966272c1bf2a2d88866f3ee566c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 14 Mar 2022 22:54:35 +0100 Subject: [PATCH 273/357] Simplify ``cached_property`` import guards (#5915) --- pylint/checkers/classes/class_checker.py | 5 ++--- pylint/checkers/design_analysis.py | 3 +-- .../checkers/refactoring/refactoring_checker.py | 15 ++------------- pylint/checkers/typecheck.py | 4 +--- pylint/checkers/variables.py | 4 +--- 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 63078cf421..3373b277b8 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -53,7 +53,7 @@ import collections import sys from itertools import chain, zip_longest -from typing import TYPE_CHECKING, Dict, List, Pattern, Set +from typing import Dict, List, Pattern, Set import astroid from astroid import bases, nodes @@ -85,10 +85,9 @@ from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option -if sys.version_info >= (3, 8) or TYPE_CHECKING: +if sys.version_info >= (3, 8): from functools import cached_property else: - # pylint: disable-next=ungrouped-imports from astroid.decorators import cachedproperty as cached_property INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"} diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index 46bd443a2e..d2ad84539e 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -43,10 +43,9 @@ from pylint.checkers.utils import check_messages from pylint.interfaces import IAstroidChecker -if sys.version_info >= (3, 8) or TYPE_CHECKING: +if sys.version_info >= (3, 8): from functools import cached_property else: - # pylint: disable-next=ungrouped-imports from astroid.decorators import cachedproperty as cached_property if TYPE_CHECKING: diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 00e234cbd6..0644271b21 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -7,16 +7,7 @@ import sys import tokenize from functools import reduce -from typing import ( - TYPE_CHECKING, - Dict, - Iterator, - List, - NamedTuple, - Optional, - Tuple, - Union, -) +from typing import Dict, Iterator, List, NamedTuple, Optional, Tuple, Union import astroid from astroid import nodes @@ -27,11 +18,9 @@ from pylint.checkers import utils from pylint.checkers.utils import node_frame_class -if sys.version_info >= (3, 8) or TYPE_CHECKING: - # pylint: disable-next=ungrouped-imports +if sys.version_info >= (3, 8): from functools import cached_property else: - # pylint: disable-next=ungrouped-imports from astroid.decorators import cachedproperty as cached_property KNOWN_INFINITE_ITERATORS = {"itertools.count"} diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index d3ab8ca8e4..8f5f35b6fe 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -109,11 +109,9 @@ from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils import get_global_option -if sys.version_info >= (3, 8) or TYPE_CHECKING: - # pylint: disable-next=ungrouped-imports +if sys.version_info >= (3, 8): from functools import cached_property else: - # pylint: disable-next=ungrouped-imports from astroid.decorators import cachedproperty as cached_property if TYPE_CHECKING: diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index d4663e37ba..d05f578c05 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -91,11 +91,9 @@ ) from pylint.utils import get_global_option -if sys.version_info >= (3, 8) or TYPE_CHECKING: - # pylint: disable-next=ungrouped-imports +if sys.version_info >= (3, 8): from functools import cached_property else: - # pylint: disable-next=ungrouped-imports from astroid.decorators import cachedproperty as cached_property if TYPE_CHECKING: From 04e89f0c1a182f6bb828eafabc03d3b3951631ea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 09:03:03 +0100 Subject: [PATCH 274/357] [pre-commit.ci] pre-commit autoupdate (#5917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.31.0 → v2.31.1](https://github.com/asottile/pyupgrade/compare/v2.31.0...v2.31.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 039f220c6c..44cfb0ef8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - --remove-duplicate-keys - --remove-unused-variables - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + rev: v2.31.1 hooks: - id: pyupgrade args: [--py36-plus] From 12b33ee244f89e6d8880a5b4b45119d19fec9c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 15 Mar 2022 09:14:58 +0100 Subject: [PATCH 275/357] Update ``pydocstringformatter`` to ``0.5.3`` (#5918) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 44cfb0ef8d..6df441d822 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -95,9 +95,9 @@ repos: args: [--prose-wrap=always, --print-width=88] exclude: tests(/.*)*/data - repo: https://github.com/DanielNoord/pydocstringformatter - rev: v0.5.0 + rev: v0.5.3 hooks: - id: pydocstringformatter exclude: *fixtures - args: ["--max-summary-lines=2", "--split-summary-body", "-w"] + args: ["--split-summary-body", "--max-summary-lines=2"] files: "pylint" From 849bdda236ec8024d9230ef4d300feb29b679fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 12 Mar 2022 20:00:12 +0100 Subject: [PATCH 276/357] Remove uses of ``NodeNG.doc`` in favour of ``doc_node`` --- pylint/checkers/base.py | 4 ++-- pylint/checkers/ellipsis_checker.py | 2 +- pylint/checkers/spelling.py | 5 ++--- pylint/extensions/_check_docs_utils.py | 12 +++++++----- pylint/extensions/docparams.py | 12 ++++++------ pylint/extensions/docstyle.py | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index cd678b6cb7..faacee9984 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -2309,7 +2309,7 @@ def _check_docstring( confidence=interfaces.HIGH, ): """Check if the node has a non-empty docstring.""" - docstring = node.doc + docstring = node.doc_node.value if node.doc_node else None if docstring is None: docstring = _infer_dunder_doc_attribute(node) @@ -2375,7 +2375,7 @@ class PassChecker(_BasicChecker): def visit_pass(self, node: nodes.Pass) -> None: if len(node.parent.child_sequence(node)) > 1 or ( isinstance(node.parent, (nodes.ClassDef, nodes.FunctionDef)) - and (node.parent.doc is not None) + and node.parent.doc_node ): self.add_message("unnecessary-pass", node=node) diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py index 1261485b82..76e8a77ce0 100644 --- a/pylint/checkers/ellipsis_checker.py +++ b/pylint/checkers/ellipsis_checker.py @@ -42,7 +42,7 @@ def visit_const(self, node: nodes.Const) -> None: len(node.parent.parent.child_sequence(node.parent)) > 1 or ( isinstance(node.parent.parent, (nodes.ClassDef, nodes.FunctionDef)) - and (node.parent.parent.doc is not None) + and node.parent.parent.doc_node ) ) ): diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index 8c004444e6..e63196155a 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -460,14 +460,13 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: def _check_docstring(self, node): """Check the node has any spelling errors.""" - docstring = node.doc - if not docstring: + if not node.doc_node: return start_line = node.lineno + 1 # Go through lines of docstring - for idx, line in enumerate(docstring.splitlines()): + for idx, line in enumerate(node.doc_node.value.splitlines()): self._check_spelling("wrong-spelling-in-docstring", line, start_line + idx) diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 2e295ca110..ffda42cd37 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -25,7 +25,7 @@ """Utility methods for docstring checking.""" import re -from typing import List, Set, Tuple +from typing import List, Optional, Set, Tuple import astroid from astroid import nodes @@ -176,7 +176,9 @@ def possible_exc_types(node: nodes.NodeNG) -> Set[nodes.ClassDef]: return set() -def docstringify(docstring: str, default_type: str = "default") -> "Docstring": +def docstringify( + docstring: Optional[nodes.Const], default_type: str = "default" +) -> "Docstring": best_match = (0, DOCSTRING_TYPES.get(default_type, Docstring)(docstring)) for docstring_type in ( SphinxDocstring, @@ -208,9 +210,9 @@ class Docstring: # These methods are designed to be overridden # pylint: disable=no-self-use - def __init__(self, doc): - doc = doc or "" - self.doc = doc.expandtabs() + def __init__(self, doc: Optional[nodes.Const]) -> None: + docstring = doc.value if doc else "" + self.doc = docstring.expandtabs() def __repr__(self) -> str: return f"<{self.__class__.__name__}:'''{self.doc}'''>" diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index d618ea4d4d..8d60b11881 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -219,7 +219,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: :param node: Node for a function or method definition in the AST :type node: :class:`astroid.scoped_nodes.Function` """ - node_doc = utils.docstringify(node.doc, self.config.default_docstring_type) + node_doc = utils.docstringify(node.doc_node, self.config.default_docstring_type) # skip functions that match the 'no-docstring-rgx' config option no_docstring_rgx = get_global_option(self, "no-docstring-rgx") @@ -244,7 +244,7 @@ def check_functiondef_params(self, node, node_doc): class_node = checker_utils.node_frame_class(node) if class_node is not None: class_doc = utils.docstringify( - class_node.doc, self.config.default_docstring_type + class_node.doc_node, self.config.default_docstring_type ) self.check_single_constructor_params(class_doc, node_doc, class_node) @@ -298,14 +298,14 @@ def visit_raise(self, node: nodes.Raise) -> None: if not expected_excs: return - if not func_node.doc: + if not func_node.doc_node: # If this is a property setter, # the property should have the docstring instead. property_ = utils.get_setters_property(func_node) if property_: func_node = property_ - doc = utils.docstringify(func_node.doc, self.config.default_docstring_type) + doc = utils.docstringify(func_node.doc_node, self.config.default_docstring_type) if not doc.matching_sections(): if doc.doc: missing = {exc.name for exc in expected_excs} @@ -340,7 +340,7 @@ def visit_return(self, node: nodes.Return) -> None: if not isinstance(func_node, astroid.FunctionDef): return - doc = utils.docstringify(func_node.doc, self.config.default_docstring_type) + doc = utils.docstringify(func_node.doc_node, self.config.default_docstring_type) is_property = checker_utils.decorated_with_property(func_node) @@ -361,7 +361,7 @@ def visit_yield(self, node: nodes.Yield) -> None: if not isinstance(func_node, astroid.FunctionDef): return - doc = utils.docstringify(func_node.doc, self.config.default_docstring_type) + doc = utils.docstringify(func_node.doc_node, self.config.default_docstring_type) if doc.supports_yields: doc_has_yields = doc.has_yields() diff --git a/pylint/extensions/docstyle.py b/pylint/extensions/docstyle.py index 7dc26db5d4..e520d0650c 100644 --- a/pylint/extensions/docstyle.py +++ b/pylint/extensions/docstyle.py @@ -58,7 +58,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: visit_asyncfunctiondef = visit_functiondef def _check_docstring(self, node_type, node): - docstring = node.doc + docstring = node.doc_node.value if node.doc_node else None if docstring and docstring[0] == "\n": self.add_message( "docstring-first-line-empty", From fcc17e97f35263775fbd27b3a8f8cf05e3f27613 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 13:05:15 +0000 Subject: [PATCH 277/357] Update typing-extensions requirement from ~=4.0 to ~=4.1 Updates the requirements on [typing-extensions](https://github.com/python/typing) to permit the latest version. - [Release notes](https://github.com/python/typing/releases) - [Changelog](https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG) - [Commits](https://github.com/python/typing/compare/4.0.0...4.1.1) --- updated-dependencies: - dependency-name: typing-extensions dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements_test_min.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 10385cd5b6..f577fbddcd 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e .[testutil] # astroid dependency is also defined in setup.cfg astroid==2.11.0 # Pinned to a specific version for tests -typing-extensions~=4.0 +typing-extensions~=4.1 pytest~=7.0 pytest-benchmark~=3.4 From 975550582494f9e1c0eb43336943c716120bc4e6 Mon Sep 17 00:00:00 2001 From: yushao2 <36848472+yushao2@users.noreply.github.com> Date: Tue, 15 Mar 2022 18:22:33 +0800 Subject: [PATCH 278/357] fix(4756): fix false positive `unused-private-member` for private methods (#5345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pierre Sassoulas Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- ChangeLog | 4 ++ doc/whatsnew/2.13.rst | 4 ++ pylint/checkers/classes/class_checker.py | 44 +++++++++++-------- .../u/unused/unused_private_member.py | 25 +++++++++++ .../u/unused/unused_private_member.txt | 40 ++++++++--------- 5 files changed, 78 insertions(+), 39 deletions(-) diff --git a/ChangeLog b/ChangeLog index 52cb627f2e..bcebfa4478 100644 --- a/ChangeLog +++ b/ChangeLog @@ -582,6 +582,10 @@ Release date: 2021-11-24 Closes #4412 #5287 +* Fix ``unused-private-member`` false positive when accessing private methods through ``property``. + + Closes #4756 + * Fix ``install graphiz`` message which isn't needed for puml output format. * ``MessageTest`` of the unittest ``testutil`` now requires the ``confidence`` attribute diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index d277d0d82d..629798f421 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -153,6 +153,10 @@ Other Changes Closes #5722 +* Fix ``unused-private-member`` false positive when accessing private methods through ``property``. + + Closes #4756 + * Fixed crash from ``arguments-differ`` and ``arguments-renamed`` when methods were defined outside the top level of a class. diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 3373b277b8..ab954f2e83 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -900,29 +900,35 @@ def _check_unused_private_functions(self, node: nodes.ClassDef) -> None: n.name for n in parent_scope.nodes_of_class(nodes.Name) ): continue - for attribute in node.nodes_of_class(nodes.Attribute): - if ( - attribute.attrname != function_def.name - or attribute.scope() == function_def # We ignore recursive calls - ): - continue - if isinstance(attribute.expr, nodes.Name) and attribute.expr.name in ( - "self", - "cls", - node.name, - ): - # self.__attrname - # cls.__attrname - # node_name.__attrname + for child in node.nodes_of_class((nodes.Name, nodes.Attribute)): + # Check for cases where the functions are used as a variable instead of as a method call + if isinstance(child, nodes.Name) and child.name == function_def.name: break - if isinstance(attribute.expr, nodes.Call): - # type(self).__attrname - inferred = safe_infer(attribute.expr) + if isinstance(child, nodes.Attribute): + # Ignore recursive calls if ( - isinstance(inferred, nodes.ClassDef) - and inferred.name == node.name + child.attrname != function_def.name + or child.scope() == function_def ): + continue + + # Check self.__attrname, cls.__attrname, node_name.__attrname + if isinstance(child.expr, nodes.Name) and child.expr.name in { + "self", + "cls", + node.name, + }: + break + + # Check type(self).__attrname + if isinstance(child.expr, nodes.Call): + inferred = safe_infer(child.expr) + if ( + isinstance(inferred, nodes.ClassDef) + and inferred.name == node.name + ): + break else: name_stack = [] curr = parent_scope diff --git a/tests/functional/u/unused/unused_private_member.py b/tests/functional/u/unused/unused_private_member.py index 1b81d738b8..d69d7eb11a 100644 --- a/tests/functional/u/unused/unused_private_member.py +++ b/tests/functional/u/unused/unused_private_member.py @@ -1,4 +1,6 @@ # pylint: disable=missing-docstring, invalid-name, too-few-public-methods, no-self-use, line-too-long, unused-argument, protected-access +from functools import partialmethod + class AnotherClass(): def __test(self): # [unused-private-member] @@ -311,6 +313,29 @@ def method(self): print(self.__class__.__ham) +# https://github.com/PyCQA/pylint/issues/4756 +# Check for false positives emitted when private functions are not referenced in the class body +# with standard calls but passed as arguments to other functions. +class FalsePositive4756a: + def __bar(self, x): + print(x) + fizz = partialmethod(__bar, 'fizz') +foo = FalsePositive4756a() +foo.fizz() + +class FalsePositive4756b: + def __get_prop(self): + pass + + def __set_prop(self, value): + pass + + def __del_prop(self): + pass + + prop = property(__get_prop, __set_prop, __del_prop) + + class TypeSelfCallInMethod: """Regression test for issue 5569""" @classmethod diff --git a/tests/functional/u/unused/unused_private_member.txt b/tests/functional/u/unused/unused_private_member.txt index 71c95c9764..e6b6ab83c3 100644 --- a/tests/functional/u/unused/unused_private_member.txt +++ b/tests/functional/u/unused/unused_private_member.txt @@ -1,20 +1,20 @@ -unused-private-member:4:4:4:14:AnotherClass.__test:Unused private member `AnotherClass.__test(self)`:UNDEFINED -unused-private-member:8:4:8:15:HasUnusedInClass:Unused private member `HasUnusedInClass.__my_secret`:UNDEFINED -unused-private-member:12:4:12:37:HasUnusedInClass.__private_class_method_unused:Unused private member `HasUnusedInClass.__private_class_method_unused(cls)`:UNDEFINED -unused-private-member:20:4:20:38:HasUnusedInClass.__private_static_method_unused:Unused private member `HasUnusedInClass.__private_static_method_unused()`:UNDEFINED -unused-private-member:28:8:28:30:HasUnusedInClass.__init__:Unused private member `HasUnusedInClass.__instance_secret`:UNDEFINED -unused-private-member:34:4:34:14:HasUnusedInClass.__test:Unused private member `HasUnusedInClass.__test(self, x, y, z)`:UNDEFINED -unused-private-member:55:4:55:24:HasUnusedInClass.__test_recursive:Unused private member `HasUnusedInClass.__test_recursive(self)`:UNDEFINED -unused-private-member:133:8:133:21:FalsePositive4657.__init__:Unused private member `FalsePositive4657.__attr_c`:UNDEFINED -undefined-variable:138:15:138:18:FalsePositive4657.attr_c:Undefined variable 'cls':UNDEFINED -unused-private-member:157:8:157:26:FalsePositive4668.__new__:Unused private member `FalsePositive4668.__unused`:UNDEFINED -unused-private-member:181:8:181:27:FalsePositive4673.do_thing.__true_positive:Unused private member `FalsePositive4673.do_thing.__true_positive(in_thing)`:UNDEFINED -unused-private-member:201:8:201:21:FalsePositive4673.complicated_example.__inner_4:Unused private member `FalsePositive4673.complicated_example.__inner_4()`:UNDEFINED -unused-private-member:212:8:212:23:Crash4755Context.__init__:Unused private member `Crash4755Context.__messages`:UNDEFINED -unused-private-member:229:4:229:24:FalsePositive4681:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED -unused-private-member:239:12:239:50:FalsePositive4681.__init__:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED -unused-private-member:243:12:243:50:FalsePositive4681.__init__:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED -unused-private-member:274:4:274:31:FalsePositive4849.__unused_private_method:Unused private member `FalsePositive4849.__unused_private_method()`:UNDEFINED -unused-private-member:291:4:291:23:Pony.__init_defaults:Unused private member `Pony.__init_defaults(self)`:UNDEFINED -unused-private-member:296:4:296:23:Pony.__get_fur_color:Unused private member `Pony.__get_fur_color(self)`:UNDEFINED -unused-private-member:318:8:318:15:TypeSelfCallInMethod.b:Unused private member `TypeSelfCallInMethod.__a`:UNDEFINED +unused-private-member:6:4:6:14:AnotherClass.__test:Unused private member `AnotherClass.__test(self)`:UNDEFINED +unused-private-member:10:4:10:15:HasUnusedInClass:Unused private member `HasUnusedInClass.__my_secret`:UNDEFINED +unused-private-member:14:4:14:37:HasUnusedInClass.__private_class_method_unused:Unused private member `HasUnusedInClass.__private_class_method_unused(cls)`:UNDEFINED +unused-private-member:22:4:22:38:HasUnusedInClass.__private_static_method_unused:Unused private member `HasUnusedInClass.__private_static_method_unused()`:UNDEFINED +unused-private-member:30:8:30:30:HasUnusedInClass.__init__:Unused private member `HasUnusedInClass.__instance_secret`:UNDEFINED +unused-private-member:36:4:36:14:HasUnusedInClass.__test:Unused private member `HasUnusedInClass.__test(self, x, y, z)`:UNDEFINED +unused-private-member:57:4:57:24:HasUnusedInClass.__test_recursive:Unused private member `HasUnusedInClass.__test_recursive(self)`:UNDEFINED +unused-private-member:135:8:135:21:FalsePositive4657.__init__:Unused private member `FalsePositive4657.__attr_c`:UNDEFINED +undefined-variable:140:15:140:18:FalsePositive4657.attr_c:Undefined variable 'cls':UNDEFINED +unused-private-member:159:8:159:26:FalsePositive4668.__new__:Unused private member `FalsePositive4668.__unused`:UNDEFINED +unused-private-member:183:8:183:27:FalsePositive4673.do_thing.__true_positive:Unused private member `FalsePositive4673.do_thing.__true_positive(in_thing)`:UNDEFINED +unused-private-member:203:8:203:21:FalsePositive4673.complicated_example.__inner_4:Unused private member `FalsePositive4673.complicated_example.__inner_4()`:UNDEFINED +unused-private-member:214:8:214:23:Crash4755Context.__init__:Unused private member `Crash4755Context.__messages`:UNDEFINED +unused-private-member:231:4:231:24:FalsePositive4681:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED +unused-private-member:241:12:241:50:FalsePositive4681.__init__:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED +unused-private-member:245:12:245:50:FalsePositive4681.__init__:Unused private member `FalsePositive4681.__should_cause_error`:UNDEFINED +unused-private-member:276:4:276:31:FalsePositive4849.__unused_private_method:Unused private member `FalsePositive4849.__unused_private_method()`:UNDEFINED +unused-private-member:293:4:293:23:Pony.__init_defaults:Unused private member `Pony.__init_defaults(self)`:UNDEFINED +unused-private-member:298:4:298:23:Pony.__get_fur_color:Unused private member `Pony.__get_fur_color(self)`:UNDEFINED +unused-private-member:343:8:343:15:TypeSelfCallInMethod.b:Unused private member `TypeSelfCallInMethod.__a`:UNDEFINED From 0481a7356140bb9506833d93149e1d219a3ec79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 15 Mar 2022 21:30:36 +0100 Subject: [PATCH 279/357] Handle ``ComprehensionScope`` and their behaviour better (#5923) --- pylint/checkers/classes/special_methods_checker.py | 5 +++++ pylint/checkers/typecheck.py | 10 +++++----- pylint/checkers/utils.py | 5 +++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py index 7eb61440df..d3d2b0935e 100644 --- a/pylint/checkers/classes/special_methods_checker.py +++ b/pylint/checkers/classes/special_methods_checker.py @@ -289,6 +289,11 @@ def _is_iterator(node): if isinstance(node, astroid.bases.Generator): # Generators can be iterated. return True + # pylint: disable-next=fixme + # TODO: Should be covered by https://github.com/PyCQA/astroid/pull/1475 + if isinstance(node, nodes.ComprehensionScope): + # Comprehensions can be iterated. + return True if isinstance(node, astroid.Instance): try: diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 8f5f35b6fe..7cb132830e 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -273,11 +273,11 @@ def _missing_member_hint(owner, attrname, distance_threshold, max_choices): names = [repr(name) for name in names] if len(names) == 1: - names = ", ".join(names) + names_hint = ", ".join(names) else: - names = f"one of {', '.join(names[:-1])} or {names[-1]}" + names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}" - return f"; maybe {names}?" + return f"; maybe {names_hint}?" MSGS = { @@ -2047,10 +2047,10 @@ def _is_asyncio_coroutine(node): return False def _check_iterable(self, node, check_async=False): - if is_inside_abstract_class(node) or is_comprehension(node): + if is_inside_abstract_class(node): return inferred = safe_infer(node) - if not inferred: + if not inferred or is_comprehension(inferred): return if not is_iterable(inferred, check_async=check_async): self.add_message("not-an-iterable", args=node.as_string(), node=node) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index f53ae88456..f8b7b74e30 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1189,6 +1189,11 @@ def _supports_protocol( if protocol_callback(value): return True + # pylint: disable-next=fixme + # TODO: Should be covered by https://github.com/PyCQA/astroid/pull/1475 + if isinstance(value, nodes.ComprehensionScope): + return True + if ( isinstance(value, astroid.bases.Proxy) and isinstance(value._proxied, astroid.BaseInstance) From 65f65e9864dbbb130678f2c45646152d8248c27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9o=20Bouvard?= Date: Wed, 16 Mar 2022 08:20:11 +0100 Subject: [PATCH 280/357] Remove primer test on black-primer (#5924) black-primer was removed from black repository, so primer test on this directory has been removed to prevent the CI from failing. See psf/black#2924 Co-authored-by: Pierre Sassoulas --- tests/primer/packages_to_lint_batch_one.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/primer/packages_to_lint_batch_one.json b/tests/primer/packages_to_lint_batch_one.json index 95768b82e7..1c90556766 100644 --- a/tests/primer/packages_to_lint_batch_one.json +++ b/tests/primer/packages_to_lint_batch_one.json @@ -1,7 +1,7 @@ { "black": { "branch": "main", - "directories": ["src/black/", "src/blackd/", "src/black_primer/", "src/blib2to3/"], + "directories": ["src/black/", "src/blackd/", "src/blib2to3/"], "url": "https://github.com/psf/black.git" }, "django": { From ccd32d32c7362f15d43ee83b3ac82b405eb049bf Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 16 Mar 2022 14:52:57 +0300 Subject: [PATCH 281/357] Optimize handling of long lines for checkers like 'missing-final-newline' (#5925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix parsing of long lines when ``missing-final-newline`` is enabled * Adapt fa31b6b6 to be backward-compatible Fixes #5724 Also address comments from the PR PyCQA/pylint#5786 Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- CONTRIBUTORS.txt | 1 + ChangeLog | 4 ++++ doc/whatsnew/2.13.rst | 4 ++++ pylint/utils/pragma_parser.py | 23 ++++++++++++----------- tests/checkers/unittest_refactoring.py | 15 +++++++++++++++ tests/regrtest_data/issue_5724.py | 1 + 6 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 tests/regrtest_data/issue_5724.py diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 01c49f1b7e..989f9297d0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -355,3 +355,4 @@ contributors: - Daniel Brookman - Téo Bouvard - Konrad Weihmann +- Sergey B Kirpichev diff --git a/ChangeLog b/ChangeLog index bcebfa4478..99ad7a2374 100644 --- a/ChangeLog +++ b/ChangeLog @@ -201,6 +201,10 @@ Release date: TBA Closes #5569 +* Optimize parsing of long lines when ``missing-final-newline`` is enabled. + + Closes #5724 + * Fix false positives for ``used-before-assignment`` from using named expressions in a ternary operator test and using that expression as a call argument. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 629798f421..d0696f1dbe 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -261,6 +261,10 @@ Other Changes Closes #5771 Ref PyCQA/astroid#1382 +* Optimize parsing of long lines when ``missing-final-newline`` is enabled. + + Closes #5724 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index 0bf25de7ce..e09cba7c9e 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -9,17 +9,18 @@ # so that an option can be continued with the reasons # why it is active or disabled. OPTION_RGX = r""" - \s* # Any number of whitespace - \#? # One or zero hash - .* # Anything (as much as possible) - (\s* # Beginning of first matched group and any number of whitespaces - \# # Beginning of comment - .*? # Anything (as little as possible) - \bpylint: # pylint word and column - \s* # Any number of whitespaces - ([^;#\n]+)) # Anything except semicolon or hash or newline (it is the second matched group) - # and end of the first matched group - [;#]{0,1}""" # From 0 to 1 repetition of semicolon or hash + (?:^\s*\#.*|\s*| # Comment line, or whitespaces, + \s*\#.*(?=\#.*?\bpylint:)) # or a beginning of an inline comment + # followed by "pylint:" pragma + (\# # Beginning of comment + .*? # Anything (as little as possible) + \bpylint: # pylint word and column + \s* # Any number of whitespaces + ([^;#\n]+)) # Anything except semicolon or hash or + # newline (it is the second matched group) + # and end of the first matched group + [;#]{0,1} # From 0 to 1 repetition of semicolon or hash +""" OPTION_PO = re.compile(OPTION_RGX, re.VERBOSE) diff --git a/tests/checkers/unittest_refactoring.py b/tests/checkers/unittest_refactoring.py index a2694200b7..760eb2fe85 100644 --- a/tests/checkers/unittest_refactoring.py +++ b/tests/checkers/unittest_refactoring.py @@ -32,3 +32,18 @@ def test_process_tokens() -> None: with pytest.raises(SystemExit) as cm: Run([os.path.join(REGR_DATA, "very_long_line.py")], reporter=TextReporter()) assert cm.value.code == 0 + + +@pytest.mark.skipif(not hasattr(signal, "setitimer"), reason="Assumes POSIX signals") +def test_issue_5724() -> None: + """Regression test for parsing of pylint disable pragma's.""" + with timeout(25.0): + with pytest.raises(SystemExit) as cm: + Run( + [ + os.path.join(REGR_DATA, "issue_5724.py"), + "--enable=missing-final-newline", + ], + reporter=TextReporter(), + ) + assert cm.value.code == 0 diff --git a/tests/regrtest_data/issue_5724.py b/tests/regrtest_data/issue_5724.py new file mode 100644 index 0000000000..dad184c432 --- /dev/null +++ b/tests/regrtest_data/issue_5724.py @@ -0,0 +1 @@ +a = "a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #a #" From 9baa5b228a3c111a42edef026615f3d9584c63df Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 18 Mar 2022 06:16:23 +0300 Subject: [PATCH 282/357] Use pytest-timeout for tests --- requirements_test_min.txt | 1 + tests/checkers/unittest_refactoring.py | 44 ++++++++------------------ tests/test_regr.py | 30 +++--------------- 3 files changed, 19 insertions(+), 56 deletions(-) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index f577fbddcd..83f1a88101 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -4,3 +4,4 @@ astroid==2.11.0 # Pinned to a specific version for tests typing-extensions~=4.1 pytest~=7.0 pytest-benchmark~=3.4 +pytest-timeout~=2.1 diff --git a/tests/checkers/unittest_refactoring.py b/tests/checkers/unittest_refactoring.py index 760eb2fe85..86ed28a4da 100644 --- a/tests/checkers/unittest_refactoring.py +++ b/tests/checkers/unittest_refactoring.py @@ -2,8 +2,6 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import os -import signal -from contextlib import contextmanager import pytest @@ -14,36 +12,22 @@ REGR_DATA = os.path.join(PARENT_DIR, "regrtest_data") -@contextmanager -def timeout(timeout_s: float): - def _handle(_signum, _frame): - pytest.fail("Test took too long") - - signal.signal(signal.SIGALRM, _handle) - signal.setitimer(signal.ITIMER_REAL, timeout_s) - yield - signal.setitimer(signal.ITIMER_REAL, 0) - signal.signal(signal.SIGALRM, signal.SIG_DFL) - - -@pytest.mark.skipif(not hasattr(signal, "setitimer"), reason="Assumes POSIX signals") +@pytest.mark.timeout(8) def test_process_tokens() -> None: - with timeout(8.0): - with pytest.raises(SystemExit) as cm: - Run([os.path.join(REGR_DATA, "very_long_line.py")], reporter=TextReporter()) - assert cm.value.code == 0 + with pytest.raises(SystemExit) as cm: + Run([os.path.join(REGR_DATA, "very_long_line.py")], reporter=TextReporter()) + assert cm.value.code == 0 -@pytest.mark.skipif(not hasattr(signal, "setitimer"), reason="Assumes POSIX signals") +@pytest.mark.timeout(25) def test_issue_5724() -> None: """Regression test for parsing of pylint disable pragma's.""" - with timeout(25.0): - with pytest.raises(SystemExit) as cm: - Run( - [ - os.path.join(REGR_DATA, "issue_5724.py"), - "--enable=missing-final-newline", - ], - reporter=TextReporter(), - ) - assert cm.value.code == 0 + with pytest.raises(SystemExit) as cm: + Run( + [ + os.path.join(REGR_DATA, "issue_5724.py"), + "--enable=missing-final-newline", + ], + reporter=TextReporter(), + ) + assert cm.value.code == 0 diff --git a/tests/test_regr.py b/tests/test_regr.py index 3ece9c9873..2980057c6e 100644 --- a/tests/test_regr.py +++ b/tests/test_regr.py @@ -23,9 +23,7 @@ # pylint: disable=redefined-outer-name import os -import signal import sys -from contextlib import contextmanager from os.path import abspath, dirname, join from typing import Callable, Iterator, List, cast @@ -154,27 +152,7 @@ def test_pylint_config_attr() -> None: assert inferred[0].name == "Values" -@contextmanager -def timeout(timeout_s: float): - def _handle(_signum, _frame): - pytest.fail("timed out") - - signal.signal(signal.SIGALRM, _handle) - signal.setitimer(signal.ITIMER_REAL, timeout_s) - yield - signal.setitimer(signal.ITIMER_REAL, 0) - signal.signal(signal.SIGALRM, signal.SIG_DFL) - - -@pytest.mark.skipif(not hasattr(signal, "setitimer"), reason="Assumes POSIX signals") -@pytest.mark.parametrize( - "file_names,timeout_s", - [ - ([join(REGR_DATA, "hang", "pkg4972.string")], 30.0), - ], -) -def test_hang( - finalize_linter: PyLinter, file_names: List[str], timeout_s: float -) -> None: - with timeout(timeout_s): - finalize_linter.check(file_names) +@pytest.mark.timeout(30) +@pytest.mark.parametrize("file_names", ([join(REGR_DATA, "hang", "pkg4972.string")],)) +def test_hang(finalize_linter: PyLinter, file_names: List[str]) -> None: + finalize_linter.check(file_names) From 2a33b6742ec5cc0bc718ef6cc691ed16684b8cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9o=20Bouvard?= Date: Sat, 19 Mar 2022 11:35:53 +0100 Subject: [PATCH 283/357] Fix pyreverse type hints for methods returning None (#5916) --- ChangeLog | 2 ++ doc/whatsnew/2.13.rst | 2 ++ pylint/pyreverse/utils.py | 13 ++++++----- tests/data/clientmodule_test.py | 3 +++ tests/pyreverse/data/classes_No_Name.dot | 2 +- tests/pyreverse/data/classes_No_Name.html | 1 + tests/pyreverse/data/classes_No_Name.mmd | 1 + tests/pyreverse/data/classes_No_Name.puml | 1 + tests/pyreverse/data/classes_No_Name.vcg | 2 +- tests/pyreverse/data/classes_colorized.dot | 2 +- tests/pyreverse/data/classes_colorized.puml | 1 + tests/pyreverse/test_utils.py | 24 ++++++++++++++++++++- 12 files changed, 43 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 99ad7a2374..49a41526ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,8 @@ Release date: TBA * Fix pyreverse diagrams type hinting for classmethods and staticmethods. +* Fix pyreverse diagrams type hinting for methods returning None. + * Fix matching ``--notes`` options that end in a non-word character. Closes #5840 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index d0696f1dbe..687274a652 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -506,3 +506,5 @@ Other Changes * Fixed false positive for ``global-variable-not-assigned`` when the ``del`` statement is used Closes #5333 + +* Fix type hints in class diagrams generated by pyreverse for class methods and methods returning None. diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 52bfb4fbdd..7fde44225b 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -224,13 +224,12 @@ def visit(self, node): return None -def get_annotation_label(ann: Union[nodes.Name, nodes.Subscript]) -> str: - label = "" - if isinstance(ann, nodes.Subscript): - label = ann.as_string() - elif isinstance(ann, nodes.Name): - label = ann.name - return label +def get_annotation_label(ann: Union[nodes.Name, nodes.NodeNG]) -> str: + if isinstance(ann, nodes.Name) and ann.name is not None: + return ann.name + if isinstance(ann, nodes.NodeNG): + return ann.as_string() + return "" def get_annotation( diff --git a/tests/data/clientmodule_test.py b/tests/data/clientmodule_test.py index 257aadec1d..35c39684b5 100644 --- a/tests/data/clientmodule_test.py +++ b/tests/data/clientmodule_test.py @@ -36,3 +36,6 @@ def from_value(cls, value: int): @staticmethod def transform_value(value: int) -> int: return value * 2 + + def increment_value(self) -> None: + self.set_value(self.get_value() + 1) diff --git a/tests/pyreverse/data/classes_No_Name.dot b/tests/pyreverse/data/classes_No_Name.dot index e5a304ce37..1f3f705e78 100644 --- a/tests/pyreverse/data/classes_No_Name.dot +++ b/tests/pyreverse/data/classes_No_Name.dot @@ -8,7 +8,7 @@ charset="utf-8" "data.suppliermodule_test.DoSomething" [color="black", fontcolor="black", label="{DoSomething|my_int : Optional[int]\lmy_int_2 : Optional[int]\lmy_string : str\l|do_it(new_int: int): int\l}", shape="record", style="solid"]; "data.suppliermodule_test.Interface" [color="black", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; "data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label="{PropertyPatterns|prop1\lprop2\l|}", shape="record", style="solid"]; -"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|from_value(value: int)\ltransform_value(value: int): int\l}", shape="record", style="solid"]; +"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|from_value(value: int)\lincrement_value(): None\ltransform_value(value: int): int\l}", shape="record", style="solid"]; "data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"]; "data.clientmodule_test.Ancestor" -> "data.suppliermodule_test.Interface" [arrowhead="empty", arrowtail="node", style="dashed"]; "data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Ancestor" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; diff --git a/tests/pyreverse/data/classes_No_Name.html b/tests/pyreverse/data/classes_No_Name.html index 420a869d4f..b4ada04b85 100644 --- a/tests/pyreverse/data/classes_No_Name.html +++ b/tests/pyreverse/data/classes_No_Name.html @@ -36,6 +36,7 @@ relation2 top : str from_value(value: int) + increment_value() -> None transform_value(value: int) -> int } Specialization --|> Ancestor diff --git a/tests/pyreverse/data/classes_No_Name.mmd b/tests/pyreverse/data/classes_No_Name.mmd index 50da7f39cf..efdaacae19 100644 --- a/tests/pyreverse/data/classes_No_Name.mmd +++ b/tests/pyreverse/data/classes_No_Name.mmd @@ -31,6 +31,7 @@ classDiagram relation2 top : str from_value(value: int) + increment_value() -> None transform_value(value: int) -> int } Specialization --|> Ancestor diff --git a/tests/pyreverse/data/classes_No_Name.puml b/tests/pyreverse/data/classes_No_Name.puml index a0f8350d0d..37767b321c 100644 --- a/tests/pyreverse/data/classes_No_Name.puml +++ b/tests/pyreverse/data/classes_No_Name.puml @@ -32,6 +32,7 @@ class "Specialization" as data.clientmodule_test.Specialization { relation2 top : str from_value(value: int) + increment_value() -> None transform_value(value: int) -> int } data.clientmodule_test.Specialization --|> data.clientmodule_test.Ancestor diff --git a/tests/pyreverse/data/classes_No_Name.vcg b/tests/pyreverse/data/classes_No_Name.vcg index 6497f26b4e..4c792db698 100644 --- a/tests/pyreverse/data/classes_No_Name.vcg +++ b/tests/pyreverse/data/classes_No_Name.vcg @@ -25,7 +25,7 @@ graph:{ node: {title:"data.property_pattern.PropertyPatterns" label:"\fbPropertyPatterns\fn\n\f__________________\n\f08prop1\n\f08prop2\n\f__________________" shape:box } - node: {title:"data.clientmodule_test.Specialization" label:"\fbSpecialization\fn\n\f_________________\n\f08TYPE : str\n\f08relation\n\f08relation2\n\f08top : str\n\f_________________\n\f10from_value()\n\f10transform_value()" + node: {title:"data.clientmodule_test.Specialization" label:"\fbSpecialization\fn\n\f_________________\n\f08TYPE : str\n\f08relation\n\f08relation2\n\f08top : str\n\f_________________\n\f10from_value()\n\f10increment_value()\n\f10transform_value()" shape:box } edge: {sourcename:"data.clientmodule_test.Specialization" targetname:"data.clientmodule_test.Ancestor" arrowstyle:solid diff --git a/tests/pyreverse/data/classes_colorized.dot b/tests/pyreverse/data/classes_colorized.dot index 3b68c79336..72f30658db 100644 --- a/tests/pyreverse/data/classes_colorized.dot +++ b/tests/pyreverse/data/classes_colorized.dot @@ -8,7 +8,7 @@ charset="utf-8" "data.suppliermodule_test.DoSomething" [color="aliceblue", fontcolor="black", label="{DoSomething|my_int : Optional[int]\lmy_int_2 : Optional[int]\lmy_string : str\l|do_it(new_int: int): int\l}", shape="record", style="filled"]; "data.suppliermodule_test.Interface" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; "data.property_pattern.PropertyPatterns" [color="aliceblue", fontcolor="black", label="{PropertyPatterns|prop1\lprop2\l|}", shape="record", style="filled"]; -"data.clientmodule_test.Specialization" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|from_value(value: int)\ltransform_value(value: int): int\l}", shape="record", style="filled"]; +"data.clientmodule_test.Specialization" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\lrelation2\ltop : str\l|from_value(value: int)\lincrement_value(): None\ltransform_value(value: int): int\l}", shape="record", style="filled"]; "data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"]; "data.clientmodule_test.Ancestor" -> "data.suppliermodule_test.Interface" [arrowhead="empty", arrowtail="node", style="dashed"]; "data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Ancestor" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; diff --git a/tests/pyreverse/data/classes_colorized.puml b/tests/pyreverse/data/classes_colorized.puml index 8a37f090f4..1226f7b4e6 100644 --- a/tests/pyreverse/data/classes_colorized.puml +++ b/tests/pyreverse/data/classes_colorized.puml @@ -32,6 +32,7 @@ class "Specialization" as data.clientmodule_test.Specialization #aliceblue { relation2 top : str from_value(value: int) + increment_value() -> None transform_value(value: int) -> int } data.clientmodule_test.Specialization --|> data.clientmodule_test.Ancestor diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index f571b18473..322b5bea24 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -17,7 +17,12 @@ import pytest from astroid import nodes -from pylint.pyreverse.utils import get_annotation, get_visibility, infer_node +from pylint.pyreverse.utils import ( + get_annotation, + get_annotation_label, + get_visibility, + infer_node, +) @pytest.mark.parametrize( @@ -81,6 +86,23 @@ class A: assert got == label, f"got {got} instead of {label} for value {node}" +@pytest.mark.parametrize( + "node_text, expected_label", + [ + ("def f() -> None: pass", "None"), + ("def f() -> int: pass", "int"), + ("def f(a) -> Optional[int]: return 1 if a else None", "Optional[int]"), + ("def f() -> 'MyType': pass", "'MyType'"), + ], +) +def test_get_annotation_label_of_return_type( + node_text: str, expected_label: str +) -> None: + func = astroid.extract_node(node_text) + assert isinstance(func, nodes.FunctionDef) + assert get_annotation_label(func.returns) == expected_label + + @patch("pylint.pyreverse.utils.get_annotation") @patch("astroid.node_classes.NodeNG.infer", side_effect=astroid.InferenceError) def test_infer_node_1(mock_infer: Any, mock_get_annotation: Any) -> None: From a03c92ab51d0fe8488352e88465ab5351314ec36 Mon Sep 17 00:00:00 2001 From: Joseph Young <80432516+jpy-git@users.noreply.github.com> Date: Sun, 20 Mar 2022 07:01:01 +0000 Subject: [PATCH 284/357] Update "How to Write a Checker" docs (#5939) Make init arg optional to satisfy mypy. Co-authored-by: Pierre Sassoulas --- doc/how_tos/custom_checkers.rst | 70 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/doc/how_tos/custom_checkers.rst b/doc/how_tos/custom_checkers.rst index 30c70d63a6..bfa623a9d4 100644 --- a/doc/how_tos/custom_checkers.rst +++ b/doc/how_tos/custom_checkers.rst @@ -37,30 +37,36 @@ Firstly we will need to fill in some required boilerplate: import astroid from astroid import nodes + from typing import TYPE_CHECKING, Optional from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker - from pylint.lint import PyLinter + + if TYPE_CHECKING: + from pylint.lint import PyLinter + class UniqueReturnChecker(BaseChecker): __implements__ = IAstroidChecker - name = 'unique-returns' + name = "unique-returns" priority = -1 msgs = { - 'W0001': ( - 'Returns a non-unique constant.', - 'non-unique-returns', - 'All constants returned in a function should be unique.' + "W0001": ( + "Returns a non-unique constant.", + "non-unique-returns", + "All constants returned in a function should be unique.", ), } options = ( ( - 'ignore-ints', + "ignore-ints", { - 'default': False, 'type': 'yn', 'metavar' : '', - 'help': 'Allow returning non-unique integers', - } + "default": False, + "type": "yn", + "metavar": "", + "help": "Allow returning non-unique integers", + }, ), ) @@ -79,20 +85,19 @@ So far we have defined the following required components of our checker: going to emit. It has the following format:: msgs = { - 'message-id': ( - 'displayed-message', 'message-symbol', 'message-help' + "message-id": ( + "displayed-message", "message-symbol", "message-help" ) } + * The ``message-id`` should be a 4-digit number, prefixed with a **message category**. There are multiple message categories, these being ``C``, ``W``, ``E``, ``F``, ``R``, standing for ``Convention``, ``Warning``, ``Error``, ``Fatal`` and ``Refactoring``. - The rest of the 5 digits should not conflict with existing checkers - and they should be consistent across the checker. - For instance, - the first two digits should not be different across the checker. + The 4 digits should not conflict with existing checkers + and the first 2 digits should consistent across the checker. * The ``displayed-message`` is used for displaying the message to the user, once it is emitted. @@ -107,9 +112,10 @@ The options list defines any user configurable options. It has the following format:: options = ( - 'option-symbol': {'argparse-like-kwarg': 'value'}, + ("option-symbol", {"argparse-like-kwarg": "value"}), ) + * The ``option-symbol`` is a unique name for the option. This is used on the command line and in config files. The hyphen is replaced by an underscore when used in the checker, @@ -119,8 +125,8 @@ Next we'll track when we enter and leave a function. .. code-block:: python - def __init__(self, linter: PyLinter =None) -> None: - super(UniqueReturnChecker, self).__init__(linter) + def __init__(self, linter: Optional["PyLinter"] = None) -> None: + super().__init__(linter) self._function_stack = [] def visit_functiondef(self, node: nodes.FunctionDef) -> None: @@ -145,7 +151,7 @@ which is called with an :class:`.astroid.nodes.Return` node. .. _astroid_extract_node: .. TODO We can shorten/remove this bit once astroid has API docs. -We'll need to be able to figure out what attributes a +We'll need to be able to figure out what attributes an :class:`.astroid.nodes.Return` node has available. We can use :func:`astroid.extract_node` for this:: @@ -163,7 +169,7 @@ We could also construct a more complete example:: ... if True: ... return 5 #@ ... return 5 #@ - """) + ... """) >>> node_a.value >>> node_a.value.value @@ -183,13 +189,11 @@ Now we know how to use the astroid node, we can implement our check. def visit_return(self, node: nodes.Return) -> None: if not isinstance(node.value, nodes.Const): return - for other_return in self._function_stack[-1]: - if (node.value.value == other_return.value.value and - not (self.config.ignore_ints and node.value.pytype() == int)): - self.add_message( - 'non-unique-returns', node=node, - ) + if node.value.value == other_return.value.value and not ( + self.config.ignore_ints and node.value.pytype() == int + ): + self.add_message("non-unique-returns", node=node) self._function_stack[-1].append(node) @@ -201,17 +205,8 @@ Add the ``register`` function to the top level of the file. .. code-block:: python - from typing import TYPE_CHECKING - - import astroid - - if TYPE_CHECKING: - from pylint.lint import PyLinter - - def register(linter: "PyLinter") -> None: """This required method auto registers the checker during initialization. - :param linter: The linter to register the checker to. """ linter.register_checker(UniqueReturnChecker(linter)) @@ -269,6 +264,7 @@ We can use the example code that we used for debugging as our test cases. import my_plugin import pylint.testutils + class TestUniqueReturnChecker(pylint.testutils.CheckerTestCase): CHECKER_CLASS = my_plugin.UniqueReturnChecker @@ -284,7 +280,7 @@ We can use the example code that we used for debugging as our test cases. self.checker.visit_return(return_node_a) with self.assertAddsMessages( pylint.testutils.MessageTest( - msg_id='non-unique-returns', + msg_id="non-unique-returns", node=return_node_b, ), ): From d0e9fb1b24370a8a58c53b680340860aaeff7c29 Mon Sep 17 00:00:00 2001 From: Joe Young <80432516+jpy-git@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:46:21 +0000 Subject: [PATCH 285/357] Add missing __magic__ methods to `_SPECIAL_METHODS_PARAMS` (#5941) --- CONTRIBUTORS.txt | 1 + ChangeLog | 2 ++ doc/whatsnew/2.13.rst | 2 ++ pylint/checkers/utils.py | 2 ++ .../u/unexpected_special_method_signature.py | 6 ++++++ .../u/unexpected_special_method_signature.txt | 18 ++++++++++-------- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 989f9297d0..3232f61cf8 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -356,3 +356,4 @@ contributors: - Téo Bouvard - Konrad Weihmann - Sergey B Kirpichev +- Joseph Young (jpy-git) diff --git a/ChangeLog b/ChangeLog index 49a41526ca..24e7bdbde9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,8 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.13.rst' +* Add missing dunder methods to ``unexpected-special-method-signature`` check. + * No longer emit ``no-member`` in for loops that reference ``self`` if the binary operation that started the for loop uses a ``self`` that is encapsulated in tuples or lists. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 687274a652..c48703c3ff 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -110,6 +110,8 @@ Extensions Other Changes ============= +* Add missing dunder methods to ``unexpected-special-method-signature`` check. + * No longer emit ``no-member`` in for loops that reference ``self`` if the binary operation that started the for loop uses a ``self`` that is encapsulated in tuples or lists. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index f8b7b74e30..6966bf16f9 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -160,6 +160,8 @@ "__aiter__", "__anext__", "__fspath__", + "__subclasses__", + "__init_subclass__", ), 1: ( "__format__", diff --git a/tests/functional/u/unexpected_special_method_signature.py b/tests/functional/u/unexpected_special_method_signature.py index aba6811b92..e4d14bc489 100644 --- a/tests/functional/u/unexpected_special_method_signature.py +++ b/tests/functional/u/unexpected_special_method_signature.py @@ -30,6 +30,12 @@ def __iter__(): # [no-method-argument] def __getattr__(self, nanana): # [unexpected-special-method-signature] pass + def __subclasses__(self, blabla): # [unexpected-special-method-signature] + pass + + @classmethod + def __init_subclass__(cls, blabla): # [unexpected-special-method-signature] + pass class FirstBadContextManager(object): def __enter__(self): diff --git a/tests/functional/u/unexpected_special_method_signature.txt b/tests/functional/u/unexpected_special_method_signature.txt index dd9297f16d..072e78b83f 100644 --- a/tests/functional/u/unexpected_special_method_signature.txt +++ b/tests/functional/u/unexpected_special_method_signature.txt @@ -6,11 +6,13 @@ unexpected-special-method-signature:20:4:20:17:Invalid.__round__:The special met unexpected-special-method-signature:23:4:23:20:Invalid.__deepcopy__:The special method '__deepcopy__' expects 1 param(s), 2 were given:UNDEFINED no-method-argument:26:4:26:16:Invalid.__iter__:Method has no argument:UNDEFINED unexpected-special-method-signature:30:4:30:19:Invalid.__getattr__:The special method '__getattr__' expects 1 param(s), 2 were given:UNDEFINED -unexpected-special-method-signature:37:4:37:16:FirstBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:43:4:43:16:SecondBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED -unexpected-special-method-signature:51:4:51:16:ThirdBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED -unexpected-special-method-signature:57:4:57:17:Async.__aiter__:The special method '__aiter__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:59:4:59:17:Async.__anext__:The special method '__anext__' expects 0 param(s), 2 were given:UNDEFINED -unexpected-special-method-signature:61:4:61:17:Async.__await__:The special method '__await__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:63:4:63:18:Async.__aenter__:The special method '__aenter__' expects 0 param(s), 1 was given:UNDEFINED -unexpected-special-method-signature:65:4:65:17:Async.__aexit__:The special method '__aexit__' expects 3 param(s), 0 was given:UNDEFINED +unexpected-special-method-signature:33:4:33:22:Invalid.__subclasses__:The special method '__subclasses__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:37:4:37:25:Invalid.__init_subclass__:The special method '__init_subclass__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:43:4:43:16:FirstBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:49:4:49:16:SecondBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED +unexpected-special-method-signature:57:4:57:16:ThirdBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given:UNDEFINED +unexpected-special-method-signature:63:4:63:17:Async.__aiter__:The special method '__aiter__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:65:4:65:17:Async.__anext__:The special method '__anext__' expects 0 param(s), 2 were given:UNDEFINED +unexpected-special-method-signature:67:4:67:17:Async.__await__:The special method '__await__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:69:4:69:18:Async.__aenter__:The special method '__aenter__' expects 0 param(s), 1 was given:UNDEFINED +unexpected-special-method-signature:71:4:71:17:Async.__aexit__:The special method '__aexit__' expects 3 param(s), 0 was given:UNDEFINED From 2cb553658ebb97fb4326ddcb6bbd904efa6e520a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 21 Mar 2022 04:49:45 -0400 Subject: [PATCH 286/357] Fix #4590: `used-before-assignment` false positive for class definition in function scope (#5937) --- ChangeLog | 5 ++++ doc/whatsnew/2.13.rst | 5 ++++ pylint/checkers/variables.py | 28 ++++++++++++++----- ..._assignment_class_nested_under_function.py | 13 +++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 tests/functional/u/used/used_before_assignment_class_nested_under_function.py diff --git a/ChangeLog b/ChangeLog index 24e7bdbde9..c1917c330c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -376,6 +376,11 @@ Release date: TBA Closes #5679 +* Fix false positive for ``used-before-assignment`` from a class definition + nested under a function subclassing a class defined outside the function. + + Closes #4590 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index c48703c3ff..7b2f181653 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -267,6 +267,11 @@ Other Changes Closes #5724 +* Fix false positive for ``used-before-assignment`` from a class definition + nested under a function subclassing a class defined outside the function. + + Closes #4590 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index d05f578c05..3580e660e9 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -236,27 +236,41 @@ def _get_unpacking_extra_info(node, inferred): def _detect_global_scope(node, frame, defframe): - """Detect that the given frames shares a global - scope. + """Detect that the given frames share a global scope. - Two frames shares a global scope when neither + Two frames share a global scope when neither of them are hidden under a function scope, as well - as any of parent scope of them, until the root scope. + as any parent scope of them, until the root scope. In this case, depending from something defined later on - will not work, because it is still undefined. + will only work if guarded by a nested function definition. Example: class A: # B has the same global scope as `C`, leading to a NameError. + # Return True to indicate a shared scope. class B(C): ... class C: ... + Whereas this does not lead to a NameError: + class A: + def guard(): + # Return False to indicate no scope sharing. + class B(C): ... + class C: ... """ def_scope = scope = None if frame and frame.parent: scope = frame.parent.scope() if defframe and defframe.parent: def_scope = defframe.parent.scope() + if ( + isinstance(frame, nodes.ClassDef) + and scope is not def_scope + and scope is utils.get_node_first_ancestor_of_type(node, nodes.FunctionDef) + ): + # If the current node's scope is a class nested under a function, + # and the def_scope is something else, then they aren't shared. + return False if isinstance(frame, nodes.FunctionDef): # If the parent of the current node is a # function, then it can be under its scope @@ -290,12 +304,12 @@ class C: ... if break_scopes and len(set(break_scopes)) != 1: # Store different scopes than expected. # If the stored scopes are, in fact, the very same, then it means - # that the two frames (frame and defframe) shares the same scope, + # that the two frames (frame and defframe) share the same scope, # and we could apply our lineno analysis over them. # For instance, this works when they are inside a function, the node # that uses a definition and the definition itself. return False - # At this point, we are certain that frame and defframe shares a scope + # At this point, we are certain that frame and defframe share a scope # and the definition of the first depends on the second. return frame.lineno < defframe.lineno diff --git a/tests/functional/u/used/used_before_assignment_class_nested_under_function.py b/tests/functional/u/used/used_before_assignment_class_nested_under_function.py new file mode 100644 index 0000000000..3627ae1e19 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_class_nested_under_function.py @@ -0,0 +1,13 @@ +"""https://github.com/PyCQA/pylint/issues/4590""" +# pylint: disable=too-few-public-methods + + +def conditional_class_factory(): + """Define a nested class""" + class ConditionalClass(ModuleClass): + """Subclasses a name from the module scope""" + return ConditionalClass + + +class ModuleClass: + """Module-level class""" From 2239270bfe56558b86381bfd86e08fbe58685374 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 15:17:02 +0100 Subject: [PATCH 287/357] Bump mypy from 0.940 to 0.941 (#5945) Bumps [mypy](https://github.com/python/mypy) from 0.940 to 0.941. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.940...v0.941) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test_pre_commit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 31bca7e84c..0f5bf008f8 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,4 +4,4 @@ black==22.1.0 flake8==4.0.1 flake8-typing-imports==1.12.0 isort==5.10.1 -mypy==0.940 +mypy==0.941 From ffd23d47c5df1eb1feccfab868a692fb57f1664c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 15:58:36 +0100 Subject: [PATCH 288/357] Bump actions/cache from 2.1.7 to 3 (#5944) * Bump actions/cache from 2.1.7 to 3 Bumps [actions/cache](https://github.com/actions/cache) from 2.1.7 to 3. - [Release notes](https://github.com/actions/cache/releases) - [Commits](https://github.com/actions/cache/compare/v2.1.7...v3) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: Pierre Sassoulas --- .github/workflows/ci.yaml | 26 +++++++++++++------------- .github/workflows/primer-test.yaml | 8 ++++---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0ead6e5d76..5bf14f0601 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,7 +39,7 @@ jobs: }}" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: >- @@ -61,7 +61,7 @@ jobs: hashFiles('.pre-commit-config.yaml') }}" - name: Restore pre-commit environment id: cache-precommit - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: ${{ env.PRE_COMMIT_CACHE }} key: >- @@ -89,7 +89,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -102,7 +102,7 @@ jobs: exit 1 - name: Restore pre-commit environment id: cache-precommit - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: ${{ env.PRE_COMMIT_CACHE }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} @@ -132,7 +132,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -175,7 +175,7 @@ jobs: }}" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: >- @@ -210,7 +210,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -251,7 +251,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -295,7 +295,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -356,7 +356,7 @@ jobs: }}" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: >- @@ -395,7 +395,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -438,7 +438,7 @@ jobs: }}" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: >- @@ -473,7 +473,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index 566fc3ecea..e1c321b38e 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -48,7 +48,7 @@ jobs: }}" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: >- @@ -82,7 +82,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -117,7 +117,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: @@ -152,7 +152,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v2.1.7 + uses: actions/cache@v3.0.0 with: path: venv key: From fd731589ac56e44434046345e2b39bcc18edbf93 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 21 Mar 2022 17:57:38 +0100 Subject: [PATCH 289/357] Migrate current copyrite configuration to new format --- .../.contributors_aliases.json | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) rename .copyrite_aliases => script/.contributors_aliases.json (72%) diff --git a/.copyrite_aliases b/script/.contributors_aliases.json similarity index 72% rename from .copyrite_aliases rename to script/.contributors_aliases.json index 0645ec2fa1..2eb3a84f70 100644 --- a/.copyrite_aliases +++ b/script/.contributors_aliases.json @@ -1,19 +1,17 @@ -[ - { +{ + "pcmanticore@gmail.com": { "mails": ["cpopa@cloudbasesolutions.com", "pcmanticore@gmail.com"], - "authoritative_mail": "pcmanticore@gmail.com", "name": "Claudiu Popa" }, - { + "pierre.sassoulas@gmail.com": { "mails": [ "pierre.sassoulas@gmail.com", "pierre.sassoulas@cea.fr", "pierre.sassoulas@wisebim.fr" ], - "authoritative_mail": "pierre.sassoulas@gmail.com", "name": "Pierre Sassoulas" }, - { + "contact@logilab.fr": { "mails": [ "alexandre.fayolle@logilab.fr", "emile.anclin@logilab.fr", @@ -33,15 +31,13 @@ "julien.jehannet@logilab.fr", "lmedioni@logilab.fr" ], - "authoritative_mail": "contact@logilab.fr", "name": "LOGILAB S.A. (Paris, FRANCE)" }, - { + "moylop260@vauxoo.com": { "mails": ["moylop260@vauxoo.com"], - "name": "Moises Lopez", - "authoritative_mail": "moylop260@vauxoo.com" + "name": "Moises Lopez" }, - { + "no-reply@google.com": { "mails": [ "nathaniel@google.com", "mbp@google.com", @@ -52,71 +48,60 @@ ], "name": "Google, Inc." }, - { + "ashley@awhetter.co.uk": { "mails": [ "ashley@awhetter.co.uk", "awhetter.2011@my.bristol.ac.uk", "asw@dneg.com", "AWhetter@users.noreply.github.com" ], - "name": "Ashley Whetter", - "authoritative_mail": "ashley@awhetter.co.uk" + "name": "Ashley Whetter" }, - { + "ville.skytta@iki.fi": { "mails": ["ville.skytta@iki.fi", "ville.skytta@upcloud.com"], - "authoritative_mail": "ville.skytta@iki.fi", "name": "Ville Skyttä" }, - { + "ejfine@gmail.com": { "mails": [ "ubuntu@ip-172-31-89-59.ec2.internal", "eli88fine@gmail.com", "ejfine@gmail.com" ], - "authoritative_mail": "ejfine@gmail.com", "name": "Eli Fine" }, - { + "andi.finkler@gmail.com": { "mails": ["andi.finkler@gmail.com", "3929834+DudeNr33@users.noreply.github.com"], - "authoritative_mail": "andi.finkler@gmail.com", "name": "Andreas Finkler" }, - { + "matusvalo@users.noreply.github.com": { "mails": ["matusvalo@users.noreply.github.com", "matusvalo@gmail.com"], - "authoritative_mail": "matusvalo@users.noreply.github.com", "name": "Matus Valo" }, - { + "nickpesce22@gmail.com": { "mails": ["nickpesce22@gmail.com", "npesce@terpmail.umd.edu"], - "authoritative_mail": "nickpesce22@gmail.com", "name": "Nick Pesce" }, - { + "31762852+mbyrnepr2@users.noreply.github.com": { "mails": ["31762852+mbyrnepr2@users.noreply.github.com", "mbyrnepr2@gmail.com"], - "authoritative_mail": "31762852+mbyrnepr2@users.noreply.github.com", "name": "Mark Byrne" }, - { + "36848472+yushao2@users.noreply.github.com": { "mails": ["36848472+yushao2@users.noreply.github.com", "p.yushao2@gmail.com"], - "authoritative_mail": "36848472+yushao2@users.noreply.github.com", "name": "Yu Shao, Pang" }, - { + "bot@noreply.github.com": { "mails": [ "66853113+pre-commit-ci[bot]@users.noreply.github.com", "49699333+dependabot[bot]@users.noreply.github.com" ], - "authoritative_mail": "bot@noreply.github.com", "name": "bot" }, - { + "tushar.sadhwani000@gmail.com": { "mails": ["tushar.sadhwani000@gmail.com", "tushar@deepsource.io"], - "authoritative_mail": "tushar.sadhwani000@gmail.com", "name": "Tushar Sadhwani" }, - { + "or.ba402@gmail.com": { "mails": ["or.ba402@gmail.com", "orbahari@mail.tau.ac.il"], - "name": "Or Bahari", - "authoritative_mail": "or.ba402@gmail.com" + "name": "Or Bahari" } -] +} From 5a4a7201f8b98b11bdfd557b7b3b1bf4305835ad Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 21 Mar 2022 17:58:50 +0100 Subject: [PATCH 290/357] Sort the copyrite aliases alphabetically --- script/.contributors_aliases.json | 106 +++++++++++++++--------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index 2eb3a84f70..c015b6f534 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -1,15 +1,31 @@ { - "pcmanticore@gmail.com": { - "mails": ["cpopa@cloudbasesolutions.com", "pcmanticore@gmail.com"], - "name": "Claudiu Popa" + "31762852+mbyrnepr2@users.noreply.github.com": { + "mails": ["31762852+mbyrnepr2@users.noreply.github.com", "mbyrnepr2@gmail.com"], + "name": "Mark Byrne" }, - "pierre.sassoulas@gmail.com": { + "36848472+yushao2@users.noreply.github.com": { + "mails": ["36848472+yushao2@users.noreply.github.com", "p.yushao2@gmail.com"], + "name": "Yu Shao, Pang" + }, + "andi.finkler@gmail.com": { + "mails": ["andi.finkler@gmail.com", "3929834+DudeNr33@users.noreply.github.com"], + "name": "Andreas Finkler" + }, + "ashley@awhetter.co.uk": { "mails": [ - "pierre.sassoulas@gmail.com", - "pierre.sassoulas@cea.fr", - "pierre.sassoulas@wisebim.fr" + "ashley@awhetter.co.uk", + "awhetter.2011@my.bristol.ac.uk", + "asw@dneg.com", + "AWhetter@users.noreply.github.com" ], - "name": "Pierre Sassoulas" + "name": "Ashley Whetter" + }, + "bot@noreply.github.com": { + "mails": [ + "66853113+pre-commit-ci[bot]@users.noreply.github.com", + "49699333+dependabot[bot]@users.noreply.github.com" + ], + "name": "bot" }, "contact@logilab.fr": { "mails": [ @@ -33,34 +49,6 @@ ], "name": "LOGILAB S.A. (Paris, FRANCE)" }, - "moylop260@vauxoo.com": { - "mails": ["moylop260@vauxoo.com"], - "name": "Moises Lopez" - }, - "no-reply@google.com": { - "mails": [ - "nathaniel@google.com", - "mbp@google.com", - "tmarek@google.com", - "shlomme@gmail.com", - "balparda@google.com", - "dlindquist@google.com" - ], - "name": "Google, Inc." - }, - "ashley@awhetter.co.uk": { - "mails": [ - "ashley@awhetter.co.uk", - "awhetter.2011@my.bristol.ac.uk", - "asw@dneg.com", - "AWhetter@users.noreply.github.com" - ], - "name": "Ashley Whetter" - }, - "ville.skytta@iki.fi": { - "mails": ["ville.skytta@iki.fi", "ville.skytta@upcloud.com"], - "name": "Ville Skyttä" - }, "ejfine@gmail.com": { "mails": [ "ubuntu@ip-172-31-89-59.ec2.internal", @@ -69,39 +57,51 @@ ], "name": "Eli Fine" }, - "andi.finkler@gmail.com": { - "mails": ["andi.finkler@gmail.com", "3929834+DudeNr33@users.noreply.github.com"], - "name": "Andreas Finkler" - }, "matusvalo@users.noreply.github.com": { "mails": ["matusvalo@users.noreply.github.com", "matusvalo@gmail.com"], "name": "Matus Valo" }, + "moylop260@vauxoo.com": { + "mails": ["moylop260@vauxoo.com"], + "name": "Moises Lopez" + }, "nickpesce22@gmail.com": { "mails": ["nickpesce22@gmail.com", "npesce@terpmail.umd.edu"], "name": "Nick Pesce" }, - "31762852+mbyrnepr2@users.noreply.github.com": { - "mails": ["31762852+mbyrnepr2@users.noreply.github.com", "mbyrnepr2@gmail.com"], - "name": "Mark Byrne" + "no-reply@google.com": { + "mails": [ + "nathaniel@google.com", + "mbp@google.com", + "tmarek@google.com", + "shlomme@gmail.com", + "balparda@google.com", + "dlindquist@google.com" + ], + "name": "Google, Inc." }, - "36848472+yushao2@users.noreply.github.com": { - "mails": ["36848472+yushao2@users.noreply.github.com", "p.yushao2@gmail.com"], - "name": "Yu Shao, Pang" + "or.ba402@gmail.com": { + "mails": ["or.ba402@gmail.com", "orbahari@mail.tau.ac.il"], + "name": "Or Bahari" }, - "bot@noreply.github.com": { + "pcmanticore@gmail.com": { + "mails": ["cpopa@cloudbasesolutions.com", "pcmanticore@gmail.com"], + "name": "Claudiu Popa" + }, + "pierre.sassoulas@gmail.com": { "mails": [ - "66853113+pre-commit-ci[bot]@users.noreply.github.com", - "49699333+dependabot[bot]@users.noreply.github.com" + "pierre.sassoulas@gmail.com", + "pierre.sassoulas@cea.fr", + "pierre.sassoulas@wisebim.fr" ], - "name": "bot" + "name": "Pierre Sassoulas" }, "tushar.sadhwani000@gmail.com": { "mails": ["tushar.sadhwani000@gmail.com", "tushar@deepsource.io"], "name": "Tushar Sadhwani" }, - "or.ba402@gmail.com": { - "mails": ["or.ba402@gmail.com", "orbahari@mail.tau.ac.il"], - "name": "Or Bahari" + "ville.skytta@iki.fi": { + "mails": ["ville.skytta@iki.fi", "ville.skytta@upcloud.com"], + "name": "Ville Skyttä" } } From 6de7100e4bed8641f898cef160e1874ec788ab96 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 09:04:35 +0100 Subject: [PATCH 291/357] [pre-commit.ci] pre-commit autoupdate (#5949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.940 → v0.941](https://github.com/pre-commit/mirrors-mypy/compare/v0.940...v0.941) - [github.com/pre-commit/mirrors-prettier: v2.5.1 → v2.6.0](https://github.com/pre-commit/mirrors-prettier/compare/v2.5.1...v2.6.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6df441d822..d6b2226db0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: types: [text] # necessary to include ChangeLog file files: ^(ChangeLog|doc/(.*/)*.*\.rst) - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.940 + rev: v0.941 hooks: - id: mypy name: mypy @@ -89,7 +89,7 @@ repos: additional_dependencies: ["platformdirs==2.2.0", "types-pkg_resources==0.1.3"] exclude: tests/functional/|tests/input|tests(/.*)*/data|tests/regrtest_data/|tests/data/|tests(/.*)+/conftest.py|doc/|bin/ - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.5.1 + rev: v2.6.0 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] From 9c90db16a860d5e33b4916feb232a0a6fe9f420d Mon Sep 17 00:00:00 2001 From: Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> Date: Tue, 22 Mar 2022 20:00:26 +0100 Subject: [PATCH 292/357] ``pyreverse``: better error messages for unsupported file formats (#5951) * Pyreverse: better error messages for unsupported file formats * Apply suggestions from code review. --- ChangeLog | 4 ++ doc/whatsnew/2.13.rst | 4 ++ pylint/pyreverse/dot_printer.py | 3 +- pylint/pyreverse/main.py | 33 +++++++++---- pylint/pyreverse/utils.py | 31 ++++++++++-- tests/pyreverse/test_main.py | 85 +++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index c1917c330c..b82e345bb0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,10 @@ Release date: TBA Ref PyCQA/astroid#1360 Closes #4826 +* Output better error message if unsupported file formats are used with ``pyreverse``. + + Closes #5950 + * Fix pyreverse diagrams type hinting for classmethods and staticmethods. * Fix pyreverse diagrams type hinting for methods returning None. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 7b2f181653..a47516e528 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -515,3 +515,7 @@ Other Changes Closes #5333 * Fix type hints in class diagrams generated by pyreverse for class methods and methods returning None. + +* Output better error message if unsupported file formats are used with ``pyreverse``. + + Closes #5950 diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index fccdcf5579..21f41d765c 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -19,7 +19,7 @@ from astroid import nodes from pylint.pyreverse.printer import EdgeType, Layout, NodeProperties, NodeType, Printer -from pylint.pyreverse.utils import check_graphviz_availability, get_annotation_label +from pylint.pyreverse.utils import get_annotation_label ALLOWED_CHARSETS: FrozenSet[str] = frozenset(("utf-8", "iso-8859-1", "latin1")) SHAPES: Dict[NodeType, str] = { @@ -152,7 +152,6 @@ def generate(self, outputfile: str) -> None: with open(dot_sourcepath, "w", encoding="utf8") as outfile: outfile.writelines(self.lines) if target not in graphviz_extensions: - check_graphviz_availability() use_shell = sys.platform == "win32" subprocess.call( ["dot", "-T", target, dot_sourcepath, "-o", outputfile], diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index e955388cb0..aefffb315d 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -32,7 +32,20 @@ from pylint.pyreverse import writer from pylint.pyreverse.diadefslib import DiadefsHandler from pylint.pyreverse.inspector import Linker, project_from_files -from pylint.pyreverse.utils import check_graphviz_availability, insert_default_options +from pylint.pyreverse.utils import ( + check_graphviz_availability, + check_if_graphviz_supports_format, + insert_default_options, +) + +DIRECTLY_SUPPORTED_FORMATS = ( + "dot", + "vcg", + "puml", + "plantuml", + "mmd", + "html", +) OPTIONS = ( ( @@ -139,7 +152,10 @@ action="store", default="dot", metavar="", - help="create a *. output file if format available.", + help=( + f"create a *. output file if format is available. Available formats are: {', '.join(DIRECTLY_SUPPORTED_FORMATS)}. " + f"Any other format will be tried to create by means of the 'dot' command line tool, which requires a graphviz installation." + ), ), ), ( @@ -205,15 +221,12 @@ def __init__(self, args: Iterable[str]): super().__init__(usage=__doc__) insert_default_options() args = self.load_command_line_configuration(args) - if self.config.output_format not in ( - "dot", - "vcg", - "puml", - "plantuml", - "mmd", - "html", - ): + if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS: check_graphviz_availability() + print( + f"Format {self.config.output_format} is not supported natively. Pyreverse will try to generate it using Graphviz..." + ) + check_if_graphviz_supports_format(self.config.output_format) sys.exit(self.run(args)) diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 7fde44225b..f7eff0ee0b 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -23,6 +23,7 @@ import os import re import shutil +import subprocess import sys from typing import Optional, Union @@ -290,9 +291,33 @@ def check_graphviz_availability(): from *.dot or *.gv into the final output format. """ if shutil.which("dot") is None: + print("'Graphviz' needs to be installed for your chosen output format.") + sys.exit(32) + + +def check_if_graphviz_supports_format(output_format: str) -> None: + """Check if the ``dot`` command supports the requested output format. + + This is needed if image output is desired and ``dot`` is used to convert + from *.gv into the final output format. + """ + dot_output = subprocess.run( + ["dot", "-T?"], capture_output=True, check=False, encoding="utf-8" + ) + match = re.match( + pattern=r".*Use one of: (?P(\S*\s?)+)", + string=dot_output.stderr.strip(), + ) + if not match: + print( + "Unable to determine Graphviz supported output formats. " + "Pyreverse will continue, but subsequent error messages " + "regarding the output format may come from Graphviz directly." + ) + return + supported_formats = match.group("formats") + if output_format not in supported_formats.split(): print( - "The requested output format is currently not available.\n" - "Please install 'Graphviz' to have other output formats " - "than 'dot' or 'vcg'." + f"Format {output_format} is not supported by Graphviz. It supports: {supported_formats}" ) sys.exit(32) diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index 01f3ccc099..2cc422c4ac 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -2,15 +2,39 @@ import os import sys from typing import Iterator +from unittest import mock import pytest from pylint.lint import fix_import_path +from pylint.pyreverse import main TEST_DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "data")) PROJECT_ROOT_DIR = os.path.abspath(os.path.join(TEST_DATA_DIR, "..")) +@pytest.fixture(name="mock_subprocess") +def mock_utils_subprocess(): + with mock.patch("pylint.pyreverse.utils.subprocess") as mock_subprocess: + yield mock_subprocess + + +@pytest.fixture +def mock_graphviz(mock_subprocess): + mock_subprocess.run.return_value = mock.Mock( + stderr=( + 'Format: "XYZ" not recognized. Use one of: ' + "bmp canon cgimage cmap cmapx cmapx_np dot dot_json eps exr fig gd " + "gd2 gif gv icns ico imap imap_np ismap jp2 jpe jpeg jpg json json0 " + "mp pct pdf pic pict plain plain-ext png pov ps ps2 psd sgi svg svgz " + "tga tif tiff tk vdx vml vmlz vrml wbmp webp xdot xdot1.2 xdot1.4 xdot_json" + ) + ) + with mock.patch("pylint.pyreverse.utils.shutil") as mock_shutil: + mock_shutil.which.return_value = "/usr/bin/dot" + yield + + @pytest.fixture(params=[PROJECT_ROOT_DIR, TEST_DATA_DIR]) def setup_path(request) -> Iterator: current_sys_path = list(sys.path) @@ -29,3 +53,64 @@ def test_project_root_in_sys_path(): """ with fix_import_path([TEST_DATA_DIR]): assert sys.path == [PROJECT_ROOT_DIR] + + +@mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) +@mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) +@mock.patch("pylint.pyreverse.main.writer") +@pytest.mark.usefixtures("mock_graphviz") +def test_graphviz_supported_image_format(mock_writer, capsys): + """Test that Graphviz is used if the image format is supported.""" + with pytest.raises(SystemExit) as wrapped_sysexit: + # we have to catch the SystemExit so the test execution does not stop + main.Run(["-o", "png", TEST_DATA_DIR]) + # Check that the right info message is shown to the user + assert ( + "Format png is not supported natively. Pyreverse will try to generate it using Graphviz..." + in capsys.readouterr().out + ) + # Check that pyreverse actually made the call to create the diagram and we exit cleanly + mock_writer.DiagramWriter().write.assert_called_once() + assert wrapped_sysexit.value.code == 0 + + +@mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) +@mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) +@mock.patch("pylint.pyreverse.main.writer") +@pytest.mark.usefixtures("mock_graphviz") +def test_graphviz_cant_determine_supported_formats( + mock_writer, mock_subprocess, capsys +): + """Test that Graphviz is used if the image format is supported.""" + mock_subprocess.run.return_value.stderr = "..." + with pytest.raises(SystemExit) as wrapped_sysexit: + # we have to catch the SystemExit so the test execution does not stop + main.Run(["-o", "png", TEST_DATA_DIR]) + # Check that the right info message is shown to the user + assert ( + "Unable to determine Graphviz supported output formats." + in capsys.readouterr().out + ) + # Check that pyreverse actually made the call to create the diagram and we exit cleanly + mock_writer.DiagramWriter().write.assert_called_once() + assert wrapped_sysexit.value.code == 0 + + +@mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) +@mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) +@mock.patch("pylint.pyreverse.main.writer", new=mock.MagicMock()) +@pytest.mark.usefixtures("mock_graphviz") +def test_graphviz_unsupported_image_format(capsys): + """Test that Graphviz is used if the image format is supported.""" + with pytest.raises(SystemExit) as wrapped_sysexit: + # we have to catch the SystemExit so the test execution does not stop + main.Run(["-o", "somethingElse", TEST_DATA_DIR]) + # Check that the right info messages are shown to the user + stdout = capsys.readouterr().out + assert ( + "Format somethingElse is not supported natively. Pyreverse will try to generate it using Graphviz..." + in stdout + ) + assert "Format somethingElse is not supported by Graphviz. It supports:" in stdout + # Check that we exited with the expected error code + assert wrapped_sysexit.value.code == 32 From 536087e448f9204ddbaea10a55ca5eab7d64de0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 23 Mar 2022 19:35:33 +0100 Subject: [PATCH 293/357] Set up system of code examples and details for message documentation (#5934) --- .pre-commit-config.yaml | 2 +- doc/conf.py | 2 +- doc/data/messages/e/empty-docstring/bad.py | 2 + doc/data/messages/e/empty-docstring/good.py | 2 + .../y/yield-inside-async-function/bad.py | 2 + .../y/yield-inside-async-function/details.rst | 1 + .../y/yield-inside-async-function/good.py | 7 ++ .../y/yield-inside-async-function/related.rst | 1 + doc/exts/pylint_messages.py | 73 ++++++++++++++++++- 9 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 doc/data/messages/e/empty-docstring/bad.py create mode 100644 doc/data/messages/e/empty-docstring/good.py create mode 100644 doc/data/messages/y/yield-inside-async-function/bad.py create mode 100644 doc/data/messages/y/yield-inside-async-function/details.rst create mode 100644 doc/data/messages/y/yield-inside-async-function/good.py create mode 100644 doc/data/messages/y/yield-inside-async-function/related.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d6b2226db0..f19027503c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: rev: v1.4 hooks: - id: autoflake - exclude: &fixtures tests/functional/|tests/input|tests/regrtest_data/|tests/data/ + exclude: &fixtures tests/functional/|tests/input|tests/regrtest_data/|tests/data/|doc/data/messages args: - --in-place - --remove-all-unused-imports diff --git a/doc/conf.py b/doc/conf.py index 1c44c89c67..0f15ffd91c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -73,7 +73,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ["_build"] +exclude_patterns = ["_build", "data/**"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None diff --git a/doc/data/messages/e/empty-docstring/bad.py b/doc/data/messages/e/empty-docstring/bad.py new file mode 100644 index 0000000000..83d3601251 --- /dev/null +++ b/doc/data/messages/e/empty-docstring/bad.py @@ -0,0 +1,2 @@ +def foo(): + pass # [emtpy-docstring] diff --git a/doc/data/messages/e/empty-docstring/good.py b/doc/data/messages/e/empty-docstring/good.py new file mode 100644 index 0000000000..b587ca4c6c --- /dev/null +++ b/doc/data/messages/e/empty-docstring/good.py @@ -0,0 +1,2 @@ +def foo(): + """A dummy description.""" diff --git a/doc/data/messages/y/yield-inside-async-function/bad.py b/doc/data/messages/y/yield-inside-async-function/bad.py new file mode 100644 index 0000000000..6e1d6bd28b --- /dev/null +++ b/doc/data/messages/y/yield-inside-async-function/bad.py @@ -0,0 +1,2 @@ +async def foo(): + yield from [1, 2, 3] # [yield-inside-async-function] diff --git a/doc/data/messages/y/yield-inside-async-function/details.rst b/doc/data/messages/y/yield-inside-async-function/details.rst new file mode 100644 index 0000000000..7d05701aeb --- /dev/null +++ b/doc/data/messages/y/yield-inside-async-function/details.rst @@ -0,0 +1 @@ +The message can't be emitted when using Python < 3.5. diff --git a/doc/data/messages/y/yield-inside-async-function/good.py b/doc/data/messages/y/yield-inside-async-function/good.py new file mode 100644 index 0000000000..1af96506b5 --- /dev/null +++ b/doc/data/messages/y/yield-inside-async-function/good.py @@ -0,0 +1,7 @@ +async def foo(): + def _inner_foo(): + yield from [1, 2, 3] + + +async def foo(): + yield 42 diff --git a/doc/data/messages/y/yield-inside-async-function/related.rst b/doc/data/messages/y/yield-inside-async-function/related.rst new file mode 100644 index 0000000000..98cb4e3a92 --- /dev/null +++ b/doc/data/messages/y/yield-inside-async-function/related.rst @@ -0,0 +1 @@ +- `PEP 525 `_ diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index 7f910918f4..0f9ac35de9 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -23,6 +23,8 @@ PYLINT_MESSAGES_PATH = PYLINT_BASE_PATH / "doc" / "messages" """Path to the messages documentation folder.""" +PYLINT_MESSAGES_DATA_PATH = PYLINT_BASE_PATH / "doc" / "data" / "messages" +"""Path to the folder with data for the messages documentation.""" MSG_TYPES_DOC = {k: v if v != "info" else "information" for k, v in MSG_TYPES.items()} @@ -32,6 +34,10 @@ class MessageData(NamedTuple): id: str name: str definition: MessageDefinition + good_code: str + bad_code: str + details: str + related_links: str MessagesDict = Dict[str, List[MessageData]] @@ -47,6 +53,54 @@ def _register_all_checkers_and_extensions(linter: PyLinter) -> None: initialize_extensions(linter) +def _get_message_data(data_path: Path) -> Tuple[str, str, str, str]: + """Get the message data from the specified path.""" + good_code, bad_code, details, related = "", "", "", "" + + if not data_path.exists(): + return good_code, bad_code, details, related + + if (data_path / "good.py").exists(): + with open(data_path / "good.py", encoding="utf-8") as file: + file_content = file.readlines() + indented_file_content = "".join(" " + i for i in file_content) + good_code = f""" +**Correct code:** + +.. code-block:: python + +{indented_file_content}""" + + if (data_path / "bad.py").exists(): + with open(data_path / "bad.py", encoding="utf-8") as file: + file_content = file.readlines() + indented_file_content = "".join(" " + i for i in file_content) + bad_code = f""" +**Problematic code:** + +.. code-block:: python + +{indented_file_content}""" + + if (data_path / "details.rst").exists(): + with open(data_path / "details.rst", encoding="utf-8") as file: + file_content_string = file.read() + details = f""" +**Additional details:** + +{file_content_string}""" + + if (data_path / "related.rst").exists(): + with open(data_path / "related.rst", encoding="utf-8") as file: + file_content_string = file.read() + related = f""" +**Related links:** + +{file_content_string}""" + + return good_code, bad_code, details, related + + def _get_all_messages( linter: PyLinter, ) -> Tuple[MessagesDict, OldMessagesDict]: @@ -72,8 +126,20 @@ def _get_all_messages( "information": defaultdict(list), } for message in linter.msgs_store.messages: + message_data_path = ( + PYLINT_MESSAGES_DATA_PATH / message.symbol[0] / message.symbol + ) + good_code, bad_code, details, related = _get_message_data(message_data_path) + message_data = MessageData( - message.checker_name, message.msgid, message.symbol, message + message.checker_name, + message.msgid, + message.symbol, + message, + good_code, + bad_code, + details, + related, ) messages_dict[MSG_TYPES_DOC[message.msgid[0]]].append(message_data) @@ -108,6 +174,11 @@ def _write_message_page(messages_dict: MessagesDict) -> None: *{message.definition.description}* +{message.good_code} +{message.bad_code} +{message.details} +{message.related_links} + Created by ``{message.checker}`` checker """ ) From bdeb7e5f0cdafab92efc3fe52b4d1e94ee3344e5 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 21 Mar 2022 17:48:20 +0100 Subject: [PATCH 294/357] Add a script to update the CONTRIBUTORS.txt --- requirements_test.txt | 1 + script/__init__.py | 3 +++ script/bump_changelog.py | 4 ++++ script/copyright.txt | 3 +++ script/create_contributor_list.py | 21 +++++++++++++++++++++ script/fix_documentation.py | 4 ++++ script/get_unused_message_id_category.py | 1 + setup.cfg | 3 +++ tbump.toml | 8 ++++++++ 9 files changed, 48 insertions(+) create mode 100644 script/copyright.txt create mode 100644 script/create_contributor_list.py diff --git a/requirements_test.txt b/requirements_test.txt index bcc6311fc2..2e35f584d2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -4,6 +4,7 @@ coveralls~=3.3 coverage~=6.2 pre-commit~=2.17;python_full_version>="3.6.2" tbump~=6.6.0 +contributors-txt>=0.7.3 pyenchant~=3.2 pytest-cov~=3.0 pytest-profiling~=1.7 diff --git a/script/__init__.py b/script/__init__.py index e69de29bb2..e8a8ff79fd 100644 --- a/script/__init__.py +++ b/script/__init__.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/script/bump_changelog.py b/script/bump_changelog.py index 6e25719d4c..f1edcdbb0f 100644 --- a/script/bump_changelog.py +++ b/script/bump_changelog.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + # ORIGINAL here: https://github.com/PyCQA/astroid/blob/main/script/bump_changelog.py # DO NOT MODIFY DIRECTLY diff --git a/script/copyright.txt b/script/copyright.txt new file mode 100644 index 0000000000..e8a8ff79fd --- /dev/null +++ b/script/copyright.txt @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/script/create_contributor_list.py b/script/create_contributor_list.py new file mode 100644 index 0000000000..9b336a01b1 --- /dev/null +++ b/script/create_contributor_list.py @@ -0,0 +1,21 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +from pathlib import Path + +from contributors_txt import create_contributors_txt + +BASE_DIRECTORY = Path(__file__).parent.parent +ALIASES_FILE = BASE_DIRECTORY / "script/.contributors_aliases.json" +DEFAULT_CONTRIBUTOR_PATH = BASE_DIRECTORY / "CONTRIBUTORS.txt" + + +def main(): + create_contributors_txt( + aliases_file=ALIASES_FILE, output=DEFAULT_CONTRIBUTOR_PATH, verbose=True + ) + + +if __name__ == "__main__": + main() diff --git a/script/fix_documentation.py b/script/fix_documentation.py index 0fc4e347e2..24dd0097e2 100644 --- a/script/fix_documentation.py +++ b/script/fix_documentation.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Small script to fix various issues with the documentation. Used by pre-commit.""" import argparse import re diff --git a/script/get_unused_message_id_category.py b/script/get_unused_message_id_category.py index 95d7ac3e30..7bf1a1343c 100644 --- a/script/get_unused_message_id_category.py +++ b/script/get_unused_message_id_category.py @@ -1,6 +1,7 @@ """Small script to get a new unused message id category.""" # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import List diff --git a/setup.cfg b/setup.cfg index 31de3b711a..42b349e380 100644 --- a/setup.cfg +++ b/setup.cfg @@ -102,6 +102,9 @@ ignore_missing_imports = True [mypy-tests.*] ignore_missing_imports = True +[mypy-contributors_txt] +ignore_missing_imports = True + [mypy-coverage] ignore_missing_imports = True diff --git a/tbump.toml b/tbump.toml index 0429cbd3a7..6744a745ed 100644 --- a/tbump.toml +++ b/tbump.toml @@ -32,6 +32,14 @@ cmd = "python3 script/bump_changelog.py {new_version}" name = "Upgrade and check doc" cmd = "tox -e docs||echo 'Hack so this command does not fail'" +[[before_commit]] +name = "Normalize the contributors-txt configuration" +cmd = "contributors-txt-normalize-configuration -a script/.contributors_aliases.json -o script/.contributors_aliases.json" + +[[before_commit]] +name = "Upgrade the contributors list" +cmd = "python3 script/create_contributor_list.py" + [[before_commit]] name = "Upgrade copyrights" cmd = "pip3 install copyrite;copyrite --contribution-threshold 1 --change-threshold 3 --backend-type git --aliases=.copyrite_aliases . --jobs=8" From 47d37870565dd49de74f7e97bd8655b6673d189e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 21 Mar 2022 18:25:31 +0100 Subject: [PATCH 295/357] Add more aliases to the contributors.txt configuration --- script/.contributors_aliases.json | 534 +++++++++++++++++++++++++++--- 1 file changed, 495 insertions(+), 39 deletions(-) diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index c015b6f534..9b77661efd 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -1,24 +1,177 @@ { + "13665637+DanielNoord@users.noreply.github.com": { + "mails": ["13665637+DanielNoord@users.noreply.github.com"], + "name": "Daniël van Noord", + "team": "Maintainers" + }, + "15907922+kasium@users.noreply.github.com": { + "mails": ["15907922+kasium@users.noreply.github.com"], + "name": "Kai Mueller" + }, + "16359131+jiajunsu@users.noreply.github.com": { + "mails": ["16359131+jiajunsu@users.noreply.github.com"], + "name": "Victor Jiajunsu" + }, + "17108752+mattlbeck@users.noreply.github.com": { + "mails": ["17108752+mattlbeck@users.noreply.github.com"], + "name": "Matthew Beckers" + }, + "30130371+cdce8p@users.noreply.github.com": { + "mails": ["30130371+cdce8p@users.noreply.github.com"], + "name": "Marc Mueller", + "team": "Maintainers" + }, + "30827238+thernstig@users.noreply.github.com": { + "mails": ["30827238+thernstig@users.noreply.github.com"], + "name": "Tobias Hernstig" + }, + "31448155+melvio@users.noreply.github.com": { + "mails": ["31448155+melvio@users.noreply.github.com"], + "name": "Melvin Hazeleger" + }, "31762852+mbyrnepr2@users.noreply.github.com": { "mails": ["31762852+mbyrnepr2@users.noreply.github.com", "mbyrnepr2@gmail.com"], "name": "Mark Byrne" }, + "31987769+sushobhit27@users.noreply.github.com": { + "mails": [ + "sushobhitsolanki@gmail.com", + "31987769+sushobhit27@users.noreply.github.com" + ], + "name": "Sushobhit" + }, + "35621759+anubh-v@users.noreply.github.com": { + "mails": ["35621759+anubh-v@users.noreply.github.com", "anubhav@u.nus.edu"], + "name": "Anubhav" + }, "36848472+yushao2@users.noreply.github.com": { "mails": ["36848472+yushao2@users.noreply.github.com", "p.yushao2@gmail.com"], - "name": "Yu Shao, Pang" + "name": "Yu Shao, Pang", + "team": "Maintainers" + }, + "37377066+harshil21@users.noreply.github.com": { + "mails": ["37377066+harshil21@users.noreply.github.com"], + "name": "Harshil" }, - "andi.finkler@gmail.com": { + "3929834+DudeNr33@users.noreply.github.com": { "mails": ["andi.finkler@gmail.com", "3929834+DudeNr33@users.noreply.github.com"], - "name": "Andreas Finkler" + "name": "Andreas Finkler", + "team": "Maintainers" + }, + "39745367+yory8@users.noreply.github.com": { + "mails": ["39745367+yory8@users.noreply.github.com"], + "name": "Yory" + }, + "44787650+haasea@users.noreply.github.com": { + "mails": ["44787650+haasea@users.noreply.github.com"], + "name": "Aidan Haase" + }, + "46202743+lorena-b@users.noreply.github.com": { + "mails": ["46202743+lorena-b@users.noreply.github.com"], + "name": "Lorena Buciu" + }, + "53625739+dbrookman@users.noreply.github.com": { + "mails": ["53625739+dbrookman@users.noreply.github.com"], + "name": "Daniel Brookman" + }, + "61059243+tiagohonorato@users.noreply.github.com": { + "mails": [ + "tiagohonorato1@gmail.com", + "61059243+tiagohonorato@users.noreply.github.com" + ], + "name": "Tiago Honorato" + }, + "62866982+SupImDos@users.noreply.github.com": { + "mails": ["62866982+SupImDos@users.noreply.github.com"], + "name": "Hayden Richards" + }, + "80432516+jpy-git@users.noreply.github.com": { + "mails": ["80432516+jpy-git@users.noreply.github.com"], + "name": "Joseph Young" + }, + "88253337+PaaEl@users.noreply.github.com": { + "mails": ["88253337+PaaEl@users.noreply.github.com"], + "name": "Sam Vermeiren" + }, + "95424144+allanc65@users.noreply.github.com": { + "mails": ["95424144+allanc65@users.noreply.github.com"], + "name": "Allan Chandler" + }, + "Adrien.DiMascio@logilab.fr": { + "mails": ["Adrien.DiMascio@logilab.fr"], + "name": "Adrien Di Mascio" + }, + "Github@pheanex.de": { + "mails": ["Github@pheanex.de"], + "name": "Konstantin" + }, + "Mariatta@users.noreply.github.com": { + "mails": ["Mariatta@users.noreply.github.com", "mariatta@python.org"], + "name": "Mariatta Wijaya" + }, + "MartinBasti@users.noreply.github.com": { + "mails": ["MartinBasti@users.noreply.github.com"], + "name": "Martin Bašti" + }, + "Pablogsal@gmail.com": { + "mails": ["Pablogsal@gmail.com"], + "name": "Pablo Galindo Salgado" + }, + "PaulRenvoise@users.noreply.github.com": { + "mails": ["renvoisepaul@gmail.com", "PaulRenvoise@users.noreply.github.com"], + "name": "Paul Renvoisé" + }, + "ahirnish@gmail.com": { + "mails": ["ahirnish@gmail.com"], + "name": "Ahirnish Pareek" + }, + "alexandre.fayolle@logilab.fr": { + "mails": ["alexandre.fayolle@logilab.fr", "afayolle.ml@free.fr"], + "name": "Alexandre Fayolle" + }, + "anjsimmo@gmail.com": { + "mails": ["anjsimmo@gmail.com", "a.simmons@deakin.edu.au"], + "name": "Andrew J. Simmons" + }, + "areveny@protonmail.com": { + "mails": [ + "areveny@protonmail.com", + "self@areveny.com", + "92831762+areveny@users.noreply.github.com" + ], + "name": "Arianna Yang", + "team": "Maintainers" + }, + "arusahni@gmail.com": { + "mails": ["arusahni@gmail.com", "aru@thehumangeo.com"], + "name": "Aru Sahni" }, "ashley@awhetter.co.uk": { "mails": [ + "ashleyw@activestate.com", "ashley@awhetter.co.uk", "awhetter.2011@my.bristol.ac.uk", "asw@dneg.com", "AWhetter@users.noreply.github.com" ], - "name": "Ashley Whetter" + "name": "Ashley Whetter", + "team": "Maintainers" + }, + "balint.mihai@gmail.com": { + "mails": ["balint.mihai@gmail.com", "mihai@cs.upt.ro"], + "name": "Mihai Balint" + }, + "bastien.vallet@gmail.com": { + "mails": ["bastien.vallet@gmail.com"], + "name": "Bastien Vallet" + }, + "benny.mueller91@gmail.com": { + "mails": ["benny.mueller91@gmail.com"], + "name": "Benny Mueller" + }, + "bitbucket@carlcrowder.com": { + "mails": ["bitbucket@carlcrowder.com"], + "name": "Carl Crowder" }, "bot@noreply.github.com": { "mails": [ @@ -27,27 +180,76 @@ ], "name": "bot" }, - "contact@logilab.fr": { - "mails": [ - "alexandre.fayolle@logilab.fr", - "emile.anclin@logilab.fr", - "david.douard@logilab.fr", - "laura.medioni@logilab.fr", - "anthony.truchet@logilab.fr", - "alain.leufroy@logilab.fr", - "julien.cristau@logilab.fr", - "Adrien.DiMascio@logilab.fr", - "emile@crater.logilab.fr", - "sylvain.thenault@logilab.fr", - "pierre-yves.david@logilab.fr", - "nicolas.chauvat@logilab.fr", - "afayolle.ml@free.fr", - "aurelien.campeas@logilab.fr", - "charles.hebert@logilab.fr", - "julien.jehannet@logilab.fr", - "lmedioni@logilab.fr" - ], - "name": "LOGILAB S.A. (Paris, FRANCE)" + "bruno.daniel@blue-yonder.com": { + "mails": ["Bruno.Daniel@blue-yonder.com", "bruno.daniel@blue-yonder.com"], + "name": "Bruno Daniel" + }, + "bryce.paul.guinta@gmail.com": { + "mails": ["bryce.paul.guinta@gmail.com", "bryce.guinta@protonmail.com"], + "name": "Bryce Guinta", + "team": "Maintainers" + }, + "buck@yelp.com": { + "mails": ["buck@yelp.com", "buck.2019@gmail.com"], + "name": "Buck (Yelp)" + }, + "calen.pennington@gmail.com": { + "mails": ["cale@edx.org", "calen.pennington@gmail.com"], + "name": "Calen Pennington" + }, + "carli.freudenberg@energymeteo.de": { + "mails": ["carli.freudenberg@energymeteo.de"], + "name": "Carli Freudenberg" + }, + "ceridwenv@gmail.com": { + "mails": ["ceridwenv@gmail.com"], + "name": "Ceridwen", + "team": "Maintainers" + }, + "cezar.elnazli2@gmail.com": { + "mails": ["celnazli@bitdefender.com", "cezar.elnazli2@gmail.com"], + "name": "Cezar Elnazli" + }, + "cmin@ropython.org": { + "mails": ["cmin@ropython.org"], + "name": "Cosmin Poieană" + }, + "contact@ionelmc.ro": { + "mails": ["contact@ionelmc.ro"], + "name": "Ionel Maries Cristian" + }, + "dan.r.neal@gmail.com": { + "mails": ["dan.r.neal@gmail.com"], + "name": "Daniel R. Neal" + }, + "david.douard@sdfa3.org": { + "mails": ["david.douard@sdfa3.org", "david.douard@logilab.fr"], + "name": "David Douard" + }, + "david.pursehouse@gmail.com": { + "mails": ["david.pursehouse@gmail.com", "david.pursehouse@sonymobile.com"], + "name": "David Pursehouse" + }, + "ddandd@gmail.com": { + "mails": ["ddandd@gmail.com"], + "name": "Daniel Dorani" + }, + "dharding@gmail.com": { + "mails": ["dharding@gmail.com", "dharding@living180.net"], + "name": "Daniel Harding" + }, + "djgoldsmith@googlemail.com": { + "mails": ["djgoldsmith@googlemail.com"], + "name": "Dan Goldsmith" + }, + "dmand@yandex.ru": { + "mails": ["dmand@yandex.ru"], + "name": "Dimitri Prybysh", + "team": "Maintainers" + }, + "drewrisinger@users.noreply.github.com": { + "mails": ["drewrisinger@users.noreply.github.com"], + "name": "Drew Risinger" }, "ejfine@gmail.com": { "mails": [ @@ -57,28 +259,196 @@ ], "name": "Eli Fine" }, + "email@holger-peters.de": { + "mails": ["email@holger-peters.de", "holger.peters@blue-yonder.com"], + "name": "Holger Peters" + }, + "emile.anclin@logilab.fr": { + "mails": ["emile.anclin@logilab.fr", ""], + "name": "Emile Anclin" + }, + "emile@crater.logilab.fr": { + "mails": ["emile@crater.logilab.fr"], + "name": "Émile Crater" + }, + "ethanleba5@gmail.com": { + "mails": ["ethanleba5@gmail.com"], + "name": "Ethan Leba" + }, + "flying-sheep@web.de": { + "mails": ["flying-sheep@web.de", "palbrecht@mailbox.org"], + "name": "Philipp Albrecht" + }, + "fortemarco.irl@gmail.com": { + "mails": ["fortemarco.irl@gmail.com", "marcoyolos"], + "name": "Marco Forte" + }, + "frank@doublethefish.com": { + "mails": ["frank@doublethefish.com", "doublethefish@gmail.com"], + "name": "Frank Harrison" + }, + "frost.nzcr4@jagmort.com": { + "mails": ["frost.nzcr4@jagmort.com"], + "name": "Alexander Pervakov" + }, + "frostming@tencent.com": { + "mails": ["frostming@tencent.com", "mianghong@gmail.com"], + "name": "Frost Ming" + }, + "g@briel.dev": { + "mails": ["g@briel.dev", "gabriel@sezefredo.com.br"], + "name": "Gabriel R. Sezefredo" + }, + "gergely.kalmar@logikal.jp": { + "mails": ["gergely.kalmar@logikal.jp"], + "name": "Gergely Kalmár" + }, + "github@euresti.com": { + "mails": ["david@dropbox.com", "github@euresti.com"], + "name": "David Euresti" + }, + "github@hornwitser.no": { + "mails": ["github@hornwitser.no"], + "name": "Hornwitser" + }, + "glenn@e-dad.net": { + "mails": ["glenn@e-dad.net", "glmatthe@cisco.com"], + "name": "Glenn Matthews" + }, + "guillaume.peillex@gmail.com": { + "mails": ["guillaume.peillex@gmail.com", "guillaume.peillex@gmail.col"], + "name": "Hippo91", + "team": "Maintainers" + }, + "hg@stevenmyint.com": { + "mails": ["hg@stevenmyint.com", "git@stevenmyint.com"], + "name": "Steven Myint" + }, + "hugovk@users.noreply.github.com": { + "mails": ["hugovk@users.noreply.github.com"], + "name": "Hugo van Kemenade" + }, + "hugues.bruant@affirm.com": { + "mails": ["hugues.bruant@affirm.com"], + "name": "Hugues Bruant" + }, + "iilei@users.noreply.github.com": { + "mails": ["iilei@users.noreply.github.com"], + "name": "Jochen Preusche" + }, + "jacob@bogdanov.dev": { + "mails": ["jacob@bogdanov.dev", "jbogdanov@128technology.com"], + "name": "Jacob Bogdanov" + }, + "jacobtylerwalls@gmail.com": { + "mails": ["jacobtylerwalls@gmail.com"], + "name": "Jacob Walls", + "team": "Maintainers" + }, + "joffrey.mander+pro@gmail.com": { + "mails": ["joffrey.mander+pro@gmail.com"], + "name": "Joffrey Mander" + }, + "joshdcannon@gmail.com": { + "mails": ["joshdcannon@gmail.com", "joshua.cannon@ni.com"], + "name": "Joshua Cannon" + }, + "josselin@trailofbits.com": { + "mails": ["josselin@trailofbits.com"], + "name": "Josselin Feist" + }, + "justinnhli@gmail.com": { + "mails": ["justinnhli@gmail.com", "justinnhli@users.noreply.github.com"], + "name": "Justin Li" + }, + "kapsh@kap.sh": { + "mails": ["kapsh@kap.sh"], + "name": "Alexander Kapshuna" + }, + "kavinsingh@hotmail.com": { + "mails": ["kavin.singh@mail.utoronto.ca", "kavinsingh@hotmail.com"], + "name": "Kavins Singh" + }, + "keichi.t@me.com": { + "mails": ["hello@keichi.dev", "keichi.t@me.com"], + "name": "Keichi Takahashi" + }, + "laike9m@users.noreply.github.com": { + "mails": ["laike9m@users.noreply.github.com"], + "name": "laike9m" + }, + "laura.medioni@logilab.fr": { + "mails": ["laura.medioni@logilab.fr", "lmedioni@logilab.fr"], + "name": "Laura Médioni" + }, + "liyt@ios.ac.cn": { + "mails": ["liyt@ios.ac.cn"], + "name": "Yeting Li" + }, + "lothiraldan@gmail.com": { + "mails": ["lothiraldan@gmail.com"], + "name": "Boris Feld" + }, + "lucristofolini@gmail.com": { + "mails": ["luigi.cristofolini@q-ctrl.com", "lucristofolini@gmail.com"], + "name": "Luigi Bertaco Cristofolini" + }, + "m.fesenko@corp.vk.com": { + "mails": ["m.fesenko@corp.vk.com", "proggga@gmail.com"], + "name": "Mikhail Fesenko" + }, + "mariocj89@gmail.com": { + "mails": ["mcorcherojim@bloomberg.net", "mariocj89@gmail.com"], + "name": "Mario Corchero" + }, + "mark00bell@googlemail.com": { + "mails": ["mark00bell@googlemail.com", "MarkCBell@users.noreply.github.com"], + "name": "Mark Bell" + }, + "martin@vielsmaier.net": { + "mails": ["martin@vielsmaier.net", "martin.vielsmaier@gmail.com"], + "name": "Martin Vielsmaier" + }, "matusvalo@users.noreply.github.com": { "mails": ["matusvalo@users.noreply.github.com", "matusvalo@gmail.com"], "name": "Matus Valo" }, + "me@the-compiler.org": { + "mails": [ + "me@the-compiler.org", + "git@the-compiler.org", + "bitbucket.org@the-compiler.org" + ], + "name": "Florian Bruhin", + "team": "Maintainers" + }, + "mitchelly@gmail.com": { + "mails": ["mitchelly@gmail.com"], + "name": "Mitchell Young" + }, + "molobrakos@users.noreply.github.com": { + "mails": ["molobrakos@users.noreply.github.com", "erik.eriksson@yahoo.com"], + "name": "Erik Eriksson" + }, "moylop260@vauxoo.com": { "mails": ["moylop260@vauxoo.com"], - "name": "Moises Lopez" + "name": "Moisés López" + }, + "mtmiller@users.noreply.github.com": { + "mails": ["725mrm@gmail.com", "mtmiller@users.noreply.github.com"], + "name": "Mark Roman Miller" + }, + "nelfin@gmail.com": { + "mails": ["nelfin@gmail.com", "hello@nelf.in"], + "name": "Andrew Haigh" }, "nickpesce22@gmail.com": { "mails": ["nickpesce22@gmail.com", "npesce@terpmail.umd.edu"], "name": "Nick Pesce" }, - "no-reply@google.com": { - "mails": [ - "nathaniel@google.com", - "mbp@google.com", - "tmarek@google.com", - "shlomme@gmail.com", - "balparda@google.com", - "dlindquist@google.com" - ], - "name": "Google, Inc." + "nozzy123nozzy@gmail.com": { + "mails": ["nozzy123nozzy@gmail.com"], + "name": "Takahide Nojima" }, "or.ba402@gmail.com": { "mails": ["or.ba402@gmail.com", "orbahari@mail.tau.ac.il"], @@ -86,7 +456,12 @@ }, "pcmanticore@gmail.com": { "mails": ["cpopa@cloudbasesolutions.com", "pcmanticore@gmail.com"], - "name": "Claudiu Popa" + "name": "Claudiu Popa", + "team": "Ex-maintainers" + }, + "peter.kolbus@gmail.com": { + "mails": ["peter.kolbus@gmail.com", "peter.kolbus@garmin.com"], + "name": "Peter Kolbus" }, "pierre.sassoulas@gmail.com": { "mails": [ @@ -94,14 +469,95 @@ "pierre.sassoulas@cea.fr", "pierre.sassoulas@wisebim.fr" ], - "name": "Pierre Sassoulas" + "name": "Pierre Sassoulas", + "team": "Maintainers" + }, + "raphael@makeleaps.com": { + "mails": ["raphael@rtpg.co", "raphael@makeleaps.com"], + "name": "Raphael Gaschignard" + }, + "reverbc@users.noreply.github.com": { + "mails": ["reverbc@users.noreply.github.com"], + "name": "Reverb Chu" + }, + "rhys.fureigh@gsa.gov": { + "mails": ["rhys.fureigh@gsa.gov", "fureigh@users.noreply.github.com"], + "name": "Fureigh" + }, + "rogalski.91@gmail.com": { + "mails": ["rogalski.91@gmail.com", "lukasz.rogalski@intel.com"], + "name": "Łukasz Rogalski", + "team": "Maintainers" + }, + "roy.williams.iii@gmail.com": { + "mails": ["roy.williams.iii@gmail.com", "rwilliams@lyft.com"], + "name": "Roy Williams", + "team": "Maintainers" + }, + "ruro.ruro@ya.ru": { + "mails": ["ruro.ruro@ya.ru"], + "name": "Ruro" + }, + "sergeykosarchuk@gmail.com": { + "mails": ["sergeykosarchuk@gmail.com"], + "name": "Kosarchuk Sergey" + }, + "shlomme@gmail.com": { + "mails": ["shlomme@gmail.com", "tmarek@google.com"], + "name": "Torsten Marek", + "team": "Ex-maintainers" + }, + "slavfoxman@gmail.com": { + "mails": ["slavfoxman@gmail.com"], + "name": "Slavfox" + }, + "sneakypete81@gmail.com": { + "mails": ["sneakypete81@gmail.com"], + "name": "Sneaky Pete" + }, + "stefan@sofa-rockers.org": { + "mails": ["stefan.scherfke@energymeteo.de", "stefan@sofa-rockers.org"], + "name": "Stefan Scherfke" + }, + "stephane@wirtel.be": { + "mails": ["stephane@wirtel.be"], + "name": "Stéphane Wirtel" + }, + "svet@hyperscience.com": { + "mails": ["svet@hyperscience.com"], + "name": "Svetoslav Neykov" + }, + "tanant@users.noreply.github.com": { + "mails": ["tanant@users.noreply.github.com"], + "name": "Anthony" + }, + "thenault@gmail.com": { + "mails": ["thenault@gmail.com", "sylvain.thenault@logilab.fr"], + "name": "Sylvain Thénault", + "team": "Ex-maintainers" }, "tushar.sadhwani000@gmail.com": { - "mails": ["tushar.sadhwani000@gmail.com", "tushar@deepsource.io"], + "mails": [ + "tushar.sadhwani000@gmail.com", + "tushar@deepsource.io", + "86737547+tushar-deepsource@users.noreply.github.com" + ], "name": "Tushar Sadhwani" }, + "vapier@gmail.com": { + "mails": ["vapier@gmail.com", "vapier@gentoo.org"], + "name": "Mike Frysinger" + }, "ville.skytta@iki.fi": { "mails": ["ville.skytta@iki.fi", "ville.skytta@upcloud.com"], "name": "Ville Skyttä" + }, + "viorels@gmail.com": { + "mails": ["viorels@gmail.com"], + "name": "Viorel Știrbu" + }, + "vladtemian@gmail.com": { + "mails": ["vladtemian@gmail.com"], + "name": "Vlad Temian" } } From 6120c20a25b51e23cb08bedb37847196b8b3b84f Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 21 Mar 2022 20:56:56 +0100 Subject: [PATCH 296/357] Add the email when easily available I.E. the used name in git is the same that was already existing in the contributors.txt. --- CONTRIBUTORS.txt | 629 +++++++++++++++++++++++++++++++---------------- 1 file changed, 416 insertions(+), 213 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3232f61cf8..e1c772d45e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -1,6 +1,19 @@ +# This file is autocompleted by 'contributors-txt', +# using the configuration in 'script/.contributors_aliases.json'. +# Do not add new persons manually and only add information without +# using '-' as the line first character. +# Please verify that your change are stable if you modify manually. + +Ex-maintainers +-------------- +- Claudiu Popa +- Sylvain Thénault : main author / maintainer +- Torsten Marek + + Maintainers ----------- -- Claudiu Popa + - Pierre Sassoulas - Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> - Marc Mueller <30130371+cdce8p@users.noreply.github.com> @@ -24,10 +37,6 @@ Maintainers - Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> - Arianna Yang -Ex-maintainers --------------- -- Sylvain Thénault : main author / maintainer -- Torsten Marek Contributors ------------ @@ -36,45 +45,45 @@ We would not be here without folks that contributed patches, pull requests, issues and their time to pylint. We're incredibly grateful to all of these contributors: -- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant) -- Martin Pool (Google): +- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant) +- Martin Pool (Google): * warnings for anomalous backslashes * symbolic names for messages (like 'unused') * etc. -- Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support -- Julien Cristau (Logilab): python 3 support -- Emile Anclin (Logilab): python 3 support -- Sandro Tosi: Debian packaging -- Mads Kiilerich -- Boris Feld +- Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support +- Julien Cristau (Logilab): python 3 support +- Emile Anclin (Logilab): python 3 support +- Sandro Tosi : Debian packaging +- Mads Kiilerich +- Boris Feld - Bill Wendling - Sebastian Ulrich - Brian van den Broek: windows installation documentation - Amaury Forgeot d'Arc: check names imported from a module exists in the module - Benjamin Niemann: allow block level enabling/disabling of messages -- Nathaniel Manista: suspicious lambda checking -- David Shea: invalid sequence and slice index -- Carl Crowder: don't evaluate the value of arguments for 'dangerous-default-value' -- Michal Nowikowski: +- Nathaniel Manista : suspicious lambda checking +- David Shea : invalid sequence and slice index +- Carl Crowder : don't evaluate the value of arguments for 'dangerous-default-value' +- Michal Nowikowski : * wrong-spelling-in-comment * wrong-spelling-in-docstring * parallel execution on multiple CPUs -- David Lindquist: logging-format-interpolation warning. -- Brett Cannon: +- David Lindquist : logging-format-interpolation warning. +- Brett Cannon : * Port source code to be Python 2/3 compatible * Python 3 checker -- Vlad Temian: redundant-unittest-assert and the JSON reporter. -- Cosmin Poieană: unichr-builtin and improvements to bad-open-mode. -- Viorel Știrbu: intern-builtin warning. -- Dan Goldsmith: support for msg-template in HTML reporter. -- Chris Rebert: unidiomatic-typecheck. -- Steven Myint: duplicate-except. -- Radu Ciorba: not-context-manager and confusing-with-statement warnings. -- Bruno Daniel: check_docs extension. -- James Morgensen: ignored-modules option applies to import errors. -- Cezar Elnazli: deprecated-method -- Stéphane Wirtel: nonlocal-without-binding -- Laura Medioni (Logilab, on behalf of the CNES): +- Vlad Temian : redundant-unittest-assert and the JSON reporter. +- Cosmin Poieană : unichr-builtin and improvements to bad-open-mode. +- Viorel Știrbu : intern-builtin warning. +- Dan Goldsmith : support for msg-template in HTML reporter. +- Chris Rebert : unidiomatic-typecheck. +- Steven Myint : duplicate-except. +- Radu Ciorba : not-context-manager and confusing-with-statement warnings. +- Bruno Daniel : check_docs extension. +- James Morgensen : ignored-modules option applies to import errors. +- Cezar Elnazli : deprecated-method +- Stéphane Wirtel : nonlocal-without-binding +- Laura Médioni (Logilab, on behalf of the CNES): * misplaced-comparison-constant * no-classmethod-decorator * no-staticmethod-decorator @@ -85,56 +94,56 @@ contributors: * ungrouped-imports, * wrong-import-position * redefined-variable-type -- Aru Sahni: Git ignoring, regex-based ignores -- Mike Frysinger -- Moisés López (Vauxoo): +- Aru Sahni : Git ignoring, regex-based ignores +- Mike Frysinger +- Moisés López (Vauxoo): * Support for deprecated-modules in modules not installed, * Refactor wrong-import-order to integrate it with `isort` library * Add check too-complex with mccabe for cyclomatic complexity * Refactor wrong-import-position to skip try-import and nested cases * Add consider-merging-isinstance, superfluous-else-return * Fix consider-using-ternary for 'True and True and True or True' case -- Luis Escobar (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty -- Moisés López (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty + * Add bad-docstring-quotes and docstring-first-line-empty +- Luis Escobar (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty - Yannick Brehon -- Glenn Matthews: +- Glenn Matthews : * autogenerated documentation for optional extensions, * bug fixes and enhancements for docparams (née check_docs) extension -- Elias Dorneles: minor adjust to config defaults and docs -- Yuri Bochkarev: Added epytext support to docparams extension. -- Alexander Todorov: +- Elias Dorneles : minor adjust to config defaults and docs +- Yuri Bochkarev : Added epytext support to docparams extension. +- Alexander Todorov : * added new error conditions to 'bad-super-call', * Added new check for incorrect len(SEQUENCE) usage, * Added new extension for comparison against empty string constants, * Added new extension which detects comparing integers to zero, * Added new useless-return checker, * Added new try-except-raise checker -- Erik Eriksson: Added overlapping-except error check. -- Anthony Foglia (Google): Added simple string slots check. -- Derek Gustafson -- Petr Pulc: require whitespace around annotations -- John Paraskevopoulos: add 'differing-param-doc' and 'differing-type-doc' -- Martin von Gagern (Google): Added 'raising-format-tuple' warning. -- Ahirnish Pareek: 'keyword-arg-before-var-arg' check -- Daniel Miller -- Martin Bašti +- Erik Eriksson : Added overlapping-except error check. +- Anthony Foglia (Google): Added simple string slots check. +- Derek Gustafson +- Petr Pulc : require whitespace around annotations +- John Paraskevopoulos : add 'differing-param-doc' and 'differing-type-doc' +- Martin von Gagern (Google): Added 'raising-format-tuple' warning. +- Ahirnish Pareek : 'keyword-arg-before-var-arg' check +- Daniel Miller +- Martin Bašti * Added new check for shallow copy of os.environ * Added new check for useless `with threading.Lock():` statement -- Jacques Kvam -- Brian Shaginaw: prevent error on exception check for functions -- Ioana Tagirta: fix bad thread instantiation check -- Reverb Chu -- Tobias Hernstig -- Konstantin Manna -- Andreas Freimuth: fix indentation checking with tabs -- Renat Galimov +- Jacques Kvam +- Brian Shaginaw : prevent error on exception check for functions +- Ioana Tagirta : fix bad thread instantiation check +- Reverb Chu +- Tobias Hernstig <30827238+thernstig@users.noreply.github.com> +- Konstantin Manna +- Andreas Freimuth : fix indentation checking with tabs +- Renat Galimov - Thomas Snowden: fix missing-docstring for inner functions -- Mitchell Young: minor adjustment to docparams -- Marianna Polatoglou: minor contribution for wildcard import check -- Ben Green +- Mitchell Young : minor adjustment to docparams +- Marianna Polatoglou : minor contribution for wildcard import check +- Ben Green - Benjamin Freeman - Fureigh -- Jace Browning: updated default report format with clickable paths +- Jace Browning : updated default report format with clickable paths - Sushobhit (sushobhit27) * Added new check 'comparison-with-itself'. * Added new check 'useless-import-alias'. @@ -143,217 +152,411 @@ contributors: * Removed six package dependency. * Added new check 'chained-comparison'. * Added new check 'useless-object-inheritance'. -- Mariatta Wijaya +- Mariatta Wijaya * Added new check `logging-fstring-interpolation` * Documentation typo fixes -- Jason Owen +- Jason Owen - Mark Roman Miller: fix inline defs in too-many-statements -- Adam Dangoor -- Gary Tyler McLeod +- Adam Dangoor +- Gary Tyler McLeod - Wolfgang Grafen - Axel Muller - Fabio Zadrozny - Pierre Rouleau, -- Maarten ter Huurne +- Maarten ter Huurne - Mirko Friedenhagen - Matej Marusak -- Nick Drozd: performance improvements to astroid -- Kosarchuk Sergey -- Kurian Benoy -- Carey Metcalfe: demoted `try-except-raise` from error to warning -- Marcus Näslund (naslundx) -- Natalie Serebryakova -- Caio Carrara -- Roberto Leinardi: PyCharm plugin maintainer -- Aivar Annamaa +- Nick Drozd : performance improvements to astroid +- Kosarchuk Sergey +- Kurian Benoy <70306694+kurianbenoy-aot@users.noreply.github.com> +- Carey Metcalfe : demoted `try-except-raise` from error to warning +- Marcus Näslund (naslundx) +- Natalie Serebryakova +- Caio Carrara +- Roberto Leinardi : PyCharm plugin maintainer +- Aivar Annamaa - Hornwitser: fix import graph - Yuri Gribov -- Drew Risinger -- Ben James -- Tomer Chachamu: simplifiable-if-expression +- Drew Risinger +- Ben James +- Tomer Chachamu : simplifiable-if-expression - Richard Goodman: simplifiable-if-expression -- Alan Chan -- Benjamin Drung: contributing Debian Developer -- Scott Worley -- Michael Hudson-Doyle -- Lucas Cimon -- Mike Miller -- Sergei Lebedev +- Alan Chan +- Benjamin Drung : contributing Debian Developer +- Scott Worley +- Michael Hudson-Doyle +- Lucas Cimon +- Mike Miller +- Sergei Lebedev <185856+superbobry@users.noreply.github.com> (superbobry) - Sasha Bagan -- Pablo Galindo Salgado +- Pablo Galindo Salgado * Fix false positive 'Non-iterable value' with async comprehensions. -- Matus Valo -- Sardorbek Imomaliev -- Justin Li (justinnhli) -- Nicolas Dickreuter -- Pascal Corpet -- Svetoslav Neykov -- Federico Bond -- Fantix King (UChicago) +- Matus Valo +- Sardorbek Imomaliev +- Justin Li (justinnhli) +- Nicolas Dickreuter +- Pascal Corpet +- Svetoslav Neykov +- Federico Bond +- Fantix King (UChicago) - Yory (yory8) -- Thomas Hisch -- Clément Pit-Claudel +- Thomas Hisch +- Clément Pit-Claudel - Goudcode - Paul Renvoise - Bluesheeptoken -- Michael Scott Cuthbert -- Nathan Marrow +- Michael Scott Cuthbert +- Nathan Marrow - Taewon Kim - Daniil Kharkov - Tyler N. Thieding -- Zeb Nicholls +- Zeb Nicholls * Made W9011 compatible with 'of' syntax in return types -- Martin Vielsmaier +- Martin Vielsmaier - Agustin Toledo - Nicholas Smith -- Peter Kolbus (Garmin) +- Peter Kolbus (Garmin) - Oisin Moran -- Andrzej Klajnert +- Andrzej Klajnert - Andrés Pérez Hortal -- Niko Wenselowski -- Danny Hermes -- Eric Froemling -- Robert Schweizer -- Hugo van Kemenade -- Mikhail Fesenko -- Trevor Bekolay +- Niko Wenselowski +- Danny Hermes +- Eric Froemling +- Robert Schweizer +- Hugo van Kemenade +- Mikhail Fesenko +- Trevor Bekolay * Added --list-msgs-enabled command -- Rémi Cardona -- Daniel Draper -- Gabriel R. Sezefredo: Fixed "exception-escape" false positive with generators +- Rémi Cardona +- Daniel Draper +- Gabriel R. Sezefredo : Fixed "exception-escape" false positive with generators - laike9m -- Janne Rönkkö -- Hugues Bruant -- Tim Gates -- Enji Cooper -- Bastien Vallet +- Janne Rönkkö +- Hugues Bruant +- Tim Gates +- Enji Cooper +- Bastien Vallet - Pek Chhan - Craig Henriques -- Matthijs Blom -- Andy Palmer -- Wes Turner (Google): added new check 'inconsistent-quotes' -- Athos Ribeiro: Fixed dict-keys-not-iterating false positive for inverse containment checks -- Anubhav +- Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> +- Andy Palmer <25123779+ninezerozeronine@users.noreply.github.com> +- Wes Turner (Google): added new check 'inconsistent-quotes' +- Athos Ribeiro : Fixed dict-keys-not-iterating false positive for inverse containment checks +- Anubhav <35621759+anubh-v@users.noreply.github.com> - Ben Graham - Anthony Tan - Benny Müller - Bernie Gray - Slavfox -- Matthew Beckers (mattlbeck) -- Yang Yang -- Andrew J. Simmons (anjsimmo) -- Damien Baty -- Daniel R. Neal (danrneal) -- Jeremy Fleischman (jfly) -- Shiv Venkatasubrahmanyam -- Jochen Preusche (iilei) -- Ram Rachum (cool-RR) +- Matthew Beckers <17108752+mattlbeck@users.noreply.github.com> (mattlbeck) +- Yang Yang +- Andrew J. Simmons (anjsimmo) +- Damien Baty +- Daniel R. Neal (danrneal) +- Jeremy Fleischman (jfly) +- Shiv Venkatasubrahmanyam +- Jochen Preusche (iilei) +- Ram Rachum (cool-RR) - D. Alphus (Alphadelta14) -- Pieter Engelbrecht -- Ethan Leba -- Matěj Grabovský -- Yeting Li (yetingli) -- Frost Ming (frostming) -- Luigi Bertaco Cristofolini (luigibertaco) -- Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only -- Ganden Schaffner -- Josselin Feist -- David Cain -- Pedro Algarvio (s0undt3ch) -- Luigi Bertaco Cristofolini (luigibertaco) -- Or Bahari -- Joshua Cannon -- Giuseppe Valente +- Pieter Engelbrecht +- Ethan Leba +- Matěj Grabovský +- Yeting Li (yetingli) +- Frost Ming (frostming) +- Luigi Bertaco Cristofolini (luigibertaco) +- Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only +- Ganden Schaffner +- Josselin Feist +- David Cain +- Pedro Algarvio (s0undt3ch) +- Or Bahari +- Joshua Cannon +- Giuseppe Valente - Takashi Hirashima -- Joffrey Mander -- Julien Palard -- Raphael Gaschignard -- Sorin Sbarnea -- Gergely Kalmár -- Batuhan Taskaya -- Frank Harrison (doublethefish) +- Joffrey Mander +- Julien Palard +- Raphael Gaschignard +- Sorin Sbarnea +- Gergely Kalmár +- Batuhan Taskaya +- Frank Harrison (doublethefish) - Gauthier Sebaux -- Logan Miller (komodo472) -- Matthew Suozzo -- David Gilman -- Ikraduya Edian: Added new checks 'consider-using-generator' and 'use-a-generator'. -- Tiago Honorato -- Lefteris Karapetsas -- Louis Sautier -- Quentin Young -- Alexander Kapshuna -- Mark Byrne -- Konstantina Saketou -- Andrew Howe -- James Sinclair (irgeek) -- Aidan Haase -- Elizabeth Bott -- Sebastian Müller -- Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp +- Logan Miller <14319179+komodo472@users.noreply.github.com> (komodo472) +- Matthew Suozzo +- David Gilman +- Ikraduya Edian : Added new checks 'consider-using-generator' and 'use-a-generator'. +- Tiago Honorato <61059243+tiagohonorato@users.noreply.github.com> +- Lefteris Karapetsas +- Louis Sautier +- Quentin Young +- Alexander Kapshuna +- Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> +- Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> +- Andrew Howe +- James Sinclair (irgeek) +- Aidan Haase <44787650+haasea@users.noreply.github.com> +- Elizabeth Bott <52465744+elizabethbott@users.noreply.github.com> +- Sebastian Müller +- Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp - manderj - qwiddle - das-intensity - Jiajunsu (victor) -- Andrew Haigh (nelfin) -- Aditya Gupta (adityagupta1089) +- Andrew Haigh (nelfin) +- Aditya Gupta (adityagupta1089) * Added ignore_signatures to duplicate checker - ruro -- David Liu (david-yz-liu) +- David Liu (david-yz-liu) - Bernard Nauwelaerts -- Fabian Damken -- Markus Siebenhaar -- Lorena Buciu (lorena-b) -- Sergei Lebedev (superbobry) -- Maksym Humetskyi (mhumetskyi) +- Fabian Damken +- Markus Siebenhaar <41283549+siehar@users.noreply.github.com> +- Lorena Buciu <46202743+lorena-b@users.noreply.github.com> (lorena-b) +- Maksym Humetskyi (mhumetskyi) * Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled * Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled * Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled -- Daniel Dorani (doranid) -- Will Shanks -- Mark Bell +- Daniel Dorani (doranid) +- Will Shanks +- Mark Bell - Marco Gorelli: Documented Jupyter integration -- Rebecca Turner (9999years) +- Rebecca Turner (9999years) - Yilei Yang -- Marcin Kurczewski (rr-) -- Tanvi Moharir: Fix for invalid toml config -- Eisuke Kawashima (e-kwsm) -- Michal Vasilek -- Kai Mueller (kasium) -- Sam Vermeiren (PaaEl) -- Phil A. (flying-sheep) -- Melvin Hazeleger (melvio) -- Hayden Richards (SupImDos) +- Marcin Kurczewski (rr-) +- Tanvi Moharir <74228962+tanvimoharir@users.noreply.github.com>: Fix for invalid toml config +- Eisuke Kawashima (e-kwsm) +- Michal Vasilek +- Kai Mueller <15907922+kasium@users.noreply.github.com> (kasium) +- Sam Vermeiren <88253337+PaaEl@users.noreply.github.com> (PaaEl) +- Philipp Albrecht (pylbrecht) +- Melvin Hazeleger <31448155+melvio@users.noreply.github.com> (melvio) +- Hayden Richards <62866982+SupImDos@users.noreply.github.com> (SupImDos) * Fixed "no-self-use" for async methods * Fixed "docparams" extension for async functions and methods -- Jeroen Seegers (jeroenseegers) +- Jeroen Seegers (jeroenseegers) * Fixed `toml` dependency issue -- Tim Martin -- Jaehoon Hwang (jaehoonhwang) +- Tim Martin +- Jaehoon Hwang (jaehoonhwang) - Samuel Forestier -- Nick Pesce +- Nick Pesce - James DesLauriers -- Youngsoo Sung -- Samuel Freilich (sfreilich) -- Mike Fiedler (miketheman) -- Takahide Nojima -- Tushar Sadhwani (tusharsadhwani) -- Ikraduya Edian -- Antonio Quarta (sgheppy) +- Youngsoo Sung +- Samuel Freilich (sfreilich) +- Mike Fiedler (miketheman) +- Takahide Nojima +- Tushar Sadhwani (tusharsadhwani) +- Antonio Quarta (sgheppy) - Harshil (harshil21) -- Jérome Perrin (perrinjerome) -- Felix von Drigalski (felixvd) -- Jake Lishman (jakelishman) -- Philipp Albrecht (pylbrecht) -- Allan Chandler (allanc65) +- Jérome Perrin (perrinjerome) +- Felix von Drigalski (felixvd) +- Jake Lishman (jakelishman) +- Allan Chandler <95424144+allanc65@users.noreply.github.com> (allanc65) * Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params - Eero Vuojolahti - Kian-Meng, Ang - Nuzula H. Yudaka (Nuzhuka) -- Carli Freudenberg (CarliJoy) +- Carli Freudenberg (CarliJoy) * Fixed issue 5281, added Unicode checker * Improve non-ascii-name checker -- Daniel Brookman -- Téo Bouvard -- Konrad Weihmann -- Sergey B Kirpichev -- Joseph Young (jpy-git) +- Daniel Brookman <53625739+dbrookman@users.noreply.github.com> +- Téo Bouvard +- Konrad Weihmann <46938494+priv-kweihmann@users.noreply.github.com> +- Sergey B Kirpichev +- Joseph Young <80432516+jpy-git@users.noreply.github.com> (jpy-git) +- Adrien Di Mascio +- Ville Skyttä +- Pierre-Yves David +- Nicolas Chauvat +- Holger Peters +- Julien Jehannet +- Anthony Sottile +- Jakub Wilk +- Émile Crater +- Pavel Roskin +- へーさん +- Manuel Vázquez Acosta +- Jim Robertson +- Anthony Truchet +- orSolocate <38433858+orSolocate@users.noreply.github.com> +- Buck (Yelp) +- Mihai Balint +- David Douard +- Alexandru Coman +- Taewon D. Kim +- Sneaky Pete +- Rene Zhang +- Mr. Senko +- Marco Forte +- Ionel Maries Cristian +- Daniel Harding +- wtracy +- jpkotta +- chohner +- Steven M. Vascellaro +- Ricardo Gemignani +- Nick Bastin +- Kylian +- John Leach +- Erik Wright +- Dan Hemberger <846186+hemberger@users.noreply.github.com> +- Aurelien Campeas +- Alexander Pervakov +- Alain Leufroy +- xmo-odoo +- root@clnstor.am.local +- grizzly.nyo@gmail.com +- craig-sh +- bernie gray +- Yilei "Dolee" Yang +- Tyler Thieding +- Thomas Grainger +- Simu Toni +- Radostin Stoyanov +- Paul Renvoisé +- PHeanEX +- Omega Weapon +- Nikolai Kristiansen +- LCD 47 +- John Kirkham +- Jens H. Nielsen +- Harut +- Grygorii Iermolenko +- Filipe Brandenburger +- Derek Harland +- David Pursehouse +- Chris Murray +- Chris Lamb +- Charles Hebert +- Buck Golemon +- Benny Mueller +- Adam Parkin +- 谭九鼎 <109224573@qq.com> +- Łukasz Sznuk +- y2kbugger +- vinnyrose +- ttenhoeve-aa +- thinwybk +- syutbai +- sdet_liang +- sbagan +- pyves@crater.logilab.fr +- paschich +- oittaa <8972248+oittaa@users.noreply.github.com> +- moxian +- mar-chi-pan +- ludal@logilab.fr +- lrjball <50599110+lrjball@users.noreply.github.com> +- jpkotta@shannon +- jaydesl <35102795+jaydesl@users.noreply.github.com> +- jab +- glmdgrielson <32415403+glmdgrielson@users.noreply.github.com> +- glegoux +- gaurikholkar +- flyingbot91 +- fahhem +- fadedDexofan +- danields +- cosven +- cordis-dev +- bluesheeptoken +- anatoly techtonik +- amdev@AMDEV-WS01.cisco.com +- agutole +- Zeckie <49095968+Zeckie@users.noreply.github.com> +- Yuval Langer +- Yury Gribov +- Yoichi Nakayama +- Yannack +- Yann Dirson +- Xi Shen +- Victor Jiajunsu <16359131+jiajunsu@users.noreply.github.com> +- Tomasz Magulski +- Tim Hatch +- T.Rzepka +- Stephen Longofono <8992396+SLongofono@users.noreply.github.com> +- Stanislav Levin +- Skip Montanaro +- Santiago Castro +- Samuel FORESTIER +- Ryan McGuire +- Ry4an Brase +- Ruro +- Roman Ivanov +- Robin Tweedie <70587124+robin-wayve@users.noreply.github.com> +- Randall Leeds +- Qwiddle13 <32040075+Qwiddle13@users.noreply.github.com> +- Peter Dawyndt +- Peter Bittner +- Peter Aronoff +- Paul Cochrane +- Patrik +- Oisín Moran +- Obscuron +- Noam Yorav-Raphael +- Nir Soffer +- Nikita Sobolev +- Nick Smith +- Ned Batchelder +- Mitar +- Mike Bryant +- Michka Popoff +- Michael Kefeder +- Michael Giuffrida +- Matej Marušák +- Marco Edward Gorelli +- Maik Röder +- Kári Tristan Helgason +- Krzysztof Czapla +- Kraig Brockschmidt +- Kound +- Kian Meng, Ang +- Kevin Phillips +- Kevin Jing Qiu +- Kayran Schmidt <59456929+yumasheta@users.noreply.github.com> +- Jürgen Hermann +- Jonathan Kotta +- John McGehee +- John Gabriele +- John Belmonte +- Jared Garst +- Jared Deckard +- James M. Allen +- James Lingard +- James Broadhead +- Jakob Normark +- JT Olds +- Grant Welch +- Fabrice Douchant +- Fabio Natali +- Emmanuel Chaudron +- Edgemaster +- Dr. Nick +- Don Jayamanne +- Dmytro Kyrychuk +- Denis Laxalde +- Daniele Procida +- Daniela Plascencia +- Dan Garrette +- Damien Nozay +- Craig Citro +- Christopher Zurcher +- Cameron Olechowski +- Calin Don +- C.A.M. Gerlach +- Bruno P. Kinoshita +- Brian C. Lane +- Brandon W Maister +- BioGeek +- Benjamin Graham +- Benedikt Morbach +- Banjamin Freeman +- Arun Persaud +- Arthur Lutz +- Antonio Ossa +- Anthony VEREZ +- Anentropic +- Andres Perez Hortal +- Alok Singh <8325708+alok@users.noreply.github.com> +- Alex Jurkiewicz +- Alex Hearn +- Alan Evangelista +- Adrian Chirieac From 5bd082a30886386ff30c900d377b695a3fbfe257 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 23 Mar 2022 16:16:46 +0100 Subject: [PATCH 297/357] Separate contributors without email from others --- CONTRIBUTORS.txt | 121 +++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e1c772d45e..3a59d7336f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -56,11 +56,6 @@ contributors: - Sandro Tosi : Debian packaging - Mads Kiilerich - Boris Feld -- Bill Wendling -- Sebastian Ulrich -- Brian van den Broek: windows installation documentation -- Amaury Forgeot d'Arc: check names imported from a module exists in the module -- Benjamin Niemann: allow block level enabling/disabling of messages - Nathaniel Manista : suspicious lambda checking - David Shea : invalid sequence and slice index - Carl Crowder : don't evaluate the value of arguments for 'dangerous-default-value' @@ -105,7 +100,6 @@ contributors: * Fix consider-using-ternary for 'True and True and True or True' case * Add bad-docstring-quotes and docstring-first-line-empty - Luis Escobar (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty -- Yannick Brehon - Glenn Matthews : * autogenerated documentation for optional extensions, * bug fixes and enhancements for docparams (née check_docs) extension @@ -137,14 +131,11 @@ contributors: - Konstantin Manna - Andreas Freimuth : fix indentation checking with tabs - Renat Galimov -- Thomas Snowden: fix missing-docstring for inner functions - Mitchell Young : minor adjustment to docparams - Marianna Polatoglou : minor contribution for wildcard import check - Ben Green -- Benjamin Freeman -- Fureigh - Jace Browning : updated default report format with clickable paths -- Sushobhit (sushobhit27) +- Sushobhit <31987769+sushobhit27@users.noreply.github.com> (sushobhit27) * Added new check 'comparison-with-itself'. * Added new check 'useless-import-alias'. * Added support of annotations in missing-type-doc and missing-return-type-doc. @@ -156,16 +147,9 @@ contributors: * Added new check `logging-fstring-interpolation` * Documentation typo fixes - Jason Owen -- Mark Roman Miller: fix inline defs in too-many-statements - Adam Dangoor - Gary Tyler McLeod -- Wolfgang Grafen -- Axel Muller -- Fabio Zadrozny -- Pierre Rouleau, - Maarten ter Huurne -- Mirko Friedenhagen -- Matej Marusak - Nick Drozd : performance improvements to astroid - Kosarchuk Sergey - Kurian Benoy <70306694+kurianbenoy-aot@users.noreply.github.com> @@ -175,12 +159,9 @@ contributors: - Caio Carrara - Roberto Leinardi : PyCharm plugin maintainer - Aivar Annamaa -- Hornwitser: fix import graph -- Yuri Gribov - Drew Risinger - Ben James - Tomer Chachamu : simplifiable-if-expression -- Richard Goodman: simplifiable-if-expression - Alan Chan - Benjamin Drung : contributing Debian Developer - Scott Worley @@ -188,7 +169,6 @@ contributors: - Lucas Cimon - Mike Miller - Sergei Lebedev <185856+superbobry@users.noreply.github.com> (superbobry) -- Sasha Bagan - Pablo Galindo Salgado * Fix false positive 'Non-iterable value' with async comprehensions. - Matus Valo @@ -199,26 +179,15 @@ contributors: - Svetoslav Neykov - Federico Bond - Fantix King (UChicago) -- Yory (yory8) - Thomas Hisch - Clément Pit-Claudel -- Goudcode -- Paul Renvoise -- Bluesheeptoken - Michael Scott Cuthbert - Nathan Marrow -- Taewon Kim -- Daniil Kharkov -- Tyler N. Thieding - Zeb Nicholls * Made W9011 compatible with 'of' syntax in return types - Martin Vielsmaier -- Agustin Toledo -- Nicholas Smith - Peter Kolbus (Garmin) -- Oisin Moran - Andrzej Klajnert -- Andrés Pérez Hortal - Niko Wenselowski - Danny Hermes - Eric Froemling @@ -230,24 +199,16 @@ contributors: - Rémi Cardona - Daniel Draper - Gabriel R. Sezefredo : Fixed "exception-escape" false positive with generators -- laike9m - Janne Rönkkö - Hugues Bruant - Tim Gates - Enji Cooper - Bastien Vallet -- Pek Chhan -- Craig Henriques - Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> - Andy Palmer <25123779+ninezerozeronine@users.noreply.github.com> - Wes Turner (Google): added new check 'inconsistent-quotes' - Athos Ribeiro : Fixed dict-keys-not-iterating false positive for inverse containment checks - Anubhav <35621759+anubh-v@users.noreply.github.com> -- Ben Graham -- Anthony Tan -- Benny Müller -- Bernie Gray -- Slavfox - Matthew Beckers <17108752+mattlbeck@users.noreply.github.com> (mattlbeck) - Yang Yang - Andrew J. Simmons (anjsimmo) @@ -257,7 +218,6 @@ contributors: - Shiv Venkatasubrahmanyam - Jochen Preusche (iilei) - Ram Rachum (cool-RR) -- D. Alphus (Alphadelta14) - Pieter Engelbrecht - Ethan Leba - Matěj Grabovský @@ -272,7 +232,6 @@ contributors: - Or Bahari - Joshua Cannon - Giuseppe Valente -- Takashi Hirashima - Joffrey Mander - Julien Palard - Raphael Gaschignard @@ -280,7 +239,6 @@ contributors: - Gergely Kalmár - Batuhan Taskaya - Frank Harrison (doublethefish) -- Gauthier Sebaux - Logan Miller <14319179+komodo472@users.noreply.github.com> (komodo472) - Matthew Suozzo - David Gilman @@ -298,16 +256,10 @@ contributors: - Elizabeth Bott <52465744+elizabethbott@users.noreply.github.com> - Sebastian Müller - Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp -- manderj -- qwiddle -- das-intensity -- Jiajunsu (victor) - Andrew Haigh (nelfin) - Aditya Gupta (adityagupta1089) * Added ignore_signatures to duplicate checker -- ruro - David Liu (david-yz-liu) -- Bernard Nauwelaerts - Fabian Damken - Markus Siebenhaar <41283549+siehar@users.noreply.github.com> - Lorena Buciu <46202743+lorena-b@users.noreply.github.com> (lorena-b) @@ -318,9 +270,7 @@ contributors: - Daniel Dorani (doranid) - Will Shanks - Mark Bell -- Marco Gorelli: Documented Jupyter integration - Rebecca Turner (9999years) -- Yilei Yang - Marcin Kurczewski (rr-) - Tanvi Moharir <74228962+tanvimoharir@users.noreply.github.com>: Fix for invalid toml config - Eisuke Kawashima (e-kwsm) @@ -336,24 +286,18 @@ contributors: * Fixed `toml` dependency issue - Tim Martin - Jaehoon Hwang (jaehoonhwang) -- Samuel Forestier - Nick Pesce -- James DesLauriers - Youngsoo Sung - Samuel Freilich (sfreilich) - Mike Fiedler (miketheman) - Takahide Nojima - Tushar Sadhwani (tusharsadhwani) - Antonio Quarta (sgheppy) -- Harshil (harshil21) - Jérome Perrin (perrinjerome) - Felix von Drigalski (felixvd) - Jake Lishman (jakelishman) - Allan Chandler <95424144+allanc65@users.noreply.github.com> (allanc65) * Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params -- Eero Vuojolahti -- Kian-Meng, Ang -- Nuzula H. Yudaka (Nuzhuka) - Carli Freudenberg (CarliJoy) * Fixed issue 5281, added Unicode checker * Improve non-ascii-name checker @@ -560,3 +504,66 @@ contributors: - Alex Hearn - Alan Evangelista - Adrian Chirieac + + +Co-Author +--------- +The following persons were credited manually but did not commit themselves +under this name, or we did not manage to find their commits in the history. + +- Agustin Toledo +- Amaury Forgeot d'Arc: check names imported from a module exists in the module +- Anthony Tan +- Axel Muller +- Ben Graham +- Benjamin Freeman +- Benjamin Niemann: allow block level enabling/disabling of messages +- Benny Müller +- Bernard Nauwelaerts +- Bernie Gray +- Bill Wendling +- Bluesheeptoken +- Brian van den Broek: windows installation documentation +- Craig Henriques +- D. Alphus (Alphadelta14) +- Daniil Kharkov +- das-intensity +- Eero Vuojolahti +- Fabio Zadrozny +- Fureigh +- Gauthier Sebaux +- Goudcode +- Harshil (harshil21) +- Hornwitser: fix import graph +- James DesLauriers +- Jiajunsu (victor) +- Kian-Meng, Ang +- laike9m +- manderj +- Marco Gorelli: Documented Jupyter integration +- Mark Roman Miller: fix inline defs in too-many-statements +- Matej Marusak +- Mirko Friedenhagen +- Nicholas Smith +- Nuzula H. Yudaka (Nuzhuka) +- Oisin Moran +- Paul Renvoise +- Pek Chhan +- Peter Hammond +- Pierre Rouleau +- qwiddle +- Richard Goodman: simplifiable-if-expression (with Tomer Chachamu) +- ruro +- Samuel Forestier +- Sasha Bagan +- Sebastian Ulrich +- Slavfox +- Taewon Kim +- Takashi Hirashima +- Thomas Snowden: fix missing-docstring for inner functions +- Tyler N. Thieding +- Wolfgang Grafen +- Yannick Brehon +- Yilei Yang +- Yory (yory8) +- Yuri Gribov From 3a5600af11b9850345470c5ac4883d1209d7e7fb Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 23 Mar 2022 12:52:44 +0100 Subject: [PATCH 298/357] Sort contributors according to their number of commits --- CONTRIBUTORS.txt | 480 +++++++++++++++++++++++------------------------ 1 file changed, 240 insertions(+), 240 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3a59d7336f..67a7e5d9bf 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -19,9 +19,11 @@ Maintainers - Marc Mueller <30130371+cdce8p@users.noreply.github.com> - Hippo91 - Łukasz Rogalski +- Jacob Walls - Ashley Whetter - Bryce Guinta -- Jacob Walls +- Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> +- Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> - Dimitri Prybysh * multiple-imports, not-iterable, not-a-mapping, various patches. - Roy Williams (Lyft) @@ -33,8 +35,6 @@ Maintainers * Added Python 3 check for accessing deprecated methods on the 'string' module, various patches. - Florian Bruhin -- Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> -- Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> - Arianna Yang @@ -45,39 +45,23 @@ We would not be here without folks that contributed patches, pull requests, issues and their time to pylint. We're incredibly grateful to all of these contributors: -- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant) -- Martin Pool (Google): - * warnings for anomalous backslashes - * symbolic names for messages (like 'unused') - * etc. -- Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support -- Julien Cristau (Logilab): python 3 support - Emile Anclin (Logilab): python 3 support -- Sandro Tosi : Debian packaging -- Mads Kiilerich -- Boris Feld -- Nathaniel Manista : suspicious lambda checking -- David Shea : invalid sequence and slice index -- Carl Crowder : don't evaluate the value of arguments for 'dangerous-default-value' - Michal Nowikowski : * wrong-spelling-in-comment * wrong-spelling-in-docstring * parallel execution on multiple CPUs -- David Lindquist : logging-format-interpolation warning. +- Bruno Daniel : check_docs extension. +- Sushobhit <31987769+sushobhit27@users.noreply.github.com> (sushobhit27) + * Added new check 'comparison-with-itself'. + * Added new check 'useless-import-alias'. + * Added support of annotations in missing-type-doc and missing-return-type-doc. + * Added new check 'comparison-with-callable'. + * Removed six package dependency. + * Added new check 'chained-comparison'. + * Added new check 'useless-object-inheritance'. - Brett Cannon : * Port source code to be Python 2/3 compatible * Python 3 checker -- Vlad Temian : redundant-unittest-assert and the JSON reporter. -- Cosmin Poieană : unichr-builtin and improvements to bad-open-mode. -- Viorel Știrbu : intern-builtin warning. -- Dan Goldsmith : support for msg-template in HTML reporter. -- Chris Rebert : unidiomatic-typecheck. -- Steven Myint : duplicate-except. -- Radu Ciorba : not-context-manager and confusing-with-statement warnings. -- Bruno Daniel : check_docs extension. -- James Morgensen : ignored-modules option applies to import errors. -- Cezar Elnazli : deprecated-method -- Stéphane Wirtel : nonlocal-without-binding - Laura Médioni (Logilab, on behalf of the CNES): * misplaced-comparison-constant * no-classmethod-decorator @@ -89,8 +73,11 @@ contributors: * ungrouped-imports, * wrong-import-position * redefined-variable-type -- Aru Sahni : Git ignoring, regex-based ignores -- Mike Frysinger +- Alexandre Fayolle (Logilab): TkInter gui, documentation, debian support +- Nick Drozd : performance improvements to astroid +- Julien Cristau (Logilab): python 3 support +- Adrien Di Mascio +- Frank Harrison (doublethefish) - Moisés López (Vauxoo): * Support for deprecated-modules in modules not installed, * Refactor wrong-import-order to integrate it with `isort` library @@ -99,249 +86,129 @@ contributors: * Add consider-merging-isinstance, superfluous-else-return * Fix consider-using-ternary for 'True and True and True or True' case * Add bad-docstring-quotes and docstring-first-line-empty -- Luis Escobar (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty -- Glenn Matthews : - * autogenerated documentation for optional extensions, - * bug fixes and enhancements for docparams (née check_docs) extension -- Elias Dorneles : minor adjust to config defaults and docs -- Yuri Bochkarev : Added epytext support to docparams extension. -- Alexander Todorov : - * added new error conditions to 'bad-super-call', - * Added new check for incorrect len(SEQUENCE) usage, - * Added new extension for comparison against empty string constants, - * Added new extension which detects comparing integers to zero, - * Added new useless-return checker, - * Added new try-except-raise checker -- Erik Eriksson : Added overlapping-except error check. -- Anthony Foglia (Google): Added simple string slots check. -- Derek Gustafson -- Petr Pulc : require whitespace around annotations -- John Paraskevopoulos : add 'differing-param-doc' and 'differing-type-doc' -- Martin von Gagern (Google): Added 'raising-format-tuple' warning. -- Ahirnish Pareek : 'keyword-arg-before-var-arg' check -- Daniel Miller -- Martin Bašti - * Added new check for shallow copy of os.environ - * Added new check for useless `with threading.Lock():` statement -- Jacques Kvam -- Brian Shaginaw : prevent error on exception check for functions -- Ioana Tagirta : fix bad thread instantiation check -- Reverb Chu -- Tobias Hernstig <30827238+thernstig@users.noreply.github.com> -- Konstantin Manna -- Andreas Freimuth : fix indentation checking with tabs -- Renat Galimov -- Mitchell Young : minor adjustment to docparams -- Marianna Polatoglou : minor contribution for wildcard import check -- Ben Green -- Jace Browning : updated default report format with clickable paths -- Sushobhit <31987769+sushobhit27@users.noreply.github.com> (sushobhit27) - * Added new check 'comparison-with-itself'. - * Added new check 'useless-import-alias'. - * Added support of annotations in missing-type-doc and missing-return-type-doc. - * Added new check 'comparison-with-callable'. - * Removed six package dependency. - * Added new check 'chained-comparison'. - * Added new check 'useless-object-inheritance'. -- Mariatta Wijaya - * Added new check `logging-fstring-interpolation` - * Documentation typo fixes -- Jason Owen -- Adam Dangoor -- Gary Tyler McLeod -- Maarten ter Huurne -- Nick Drozd : performance improvements to astroid -- Kosarchuk Sergey -- Kurian Benoy <70306694+kurianbenoy-aot@users.noreply.github.com> -- Carey Metcalfe : demoted `try-except-raise` from error to warning -- Marcus Näslund (naslundx) -- Natalie Serebryakova -- Caio Carrara -- Roberto Leinardi : PyCharm plugin maintainer -- Aivar Annamaa -- Drew Risinger -- Ben James -- Tomer Chachamu : simplifiable-if-expression -- Alan Chan -- Benjamin Drung : contributing Debian Developer -- Scott Worley -- Michael Hudson-Doyle -- Lucas Cimon -- Mike Miller -- Sergei Lebedev <185856+superbobry@users.noreply.github.com> (superbobry) -- Pablo Galindo Salgado - * Fix false positive 'Non-iterable value' with async comprehensions. -- Matus Valo -- Sardorbek Imomaliev -- Justin Li (justinnhli) -- Nicolas Dickreuter -- Pascal Corpet -- Svetoslav Neykov -- Federico Bond -- Fantix King (UChicago) -- Thomas Hisch -- Clément Pit-Claudel -- Michael Scott Cuthbert -- Nathan Marrow -- Zeb Nicholls - * Made W9011 compatible with 'of' syntax in return types -- Martin Vielsmaier -- Peter Kolbus (Garmin) -- Andrzej Klajnert -- Niko Wenselowski -- Danny Hermes -- Eric Froemling -- Robert Schweizer -- Hugo van Kemenade -- Mikhail Fesenko -- Trevor Bekolay - * Added --list-msgs-enabled command -- Rémi Cardona -- Daniel Draper -- Gabriel R. Sezefredo : Fixed "exception-escape" false positive with generators -- Janne Rönkkö -- Hugues Bruant -- Tim Gates -- Enji Cooper -- Bastien Vallet -- Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> -- Andy Palmer <25123779+ninezerozeronine@users.noreply.github.com> -- Wes Turner (Google): added new check 'inconsistent-quotes' -- Athos Ribeiro : Fixed dict-keys-not-iterating false positive for inverse containment checks -- Anubhav <35621759+anubh-v@users.noreply.github.com> -- Matthew Beckers <17108752+mattlbeck@users.noreply.github.com> (mattlbeck) -- Yang Yang -- Andrew J. Simmons (anjsimmo) -- Damien Baty -- Daniel R. Neal (danrneal) -- Jeremy Fleischman (jfly) -- Shiv Venkatasubrahmanyam -- Jochen Preusche (iilei) -- Ram Rachum (cool-RR) -- Pieter Engelbrecht -- Ethan Leba -- Matěj Grabovský -- Yeting Li (yetingli) -- Frost Ming (frostming) -- Luigi Bertaco Cristofolini (luigibertaco) -- Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only -- Ganden Schaffner -- Josselin Feist -- David Cain -- Pedro Algarvio (s0undt3ch) -- Or Bahari -- Joshua Cannon -- Giuseppe Valente -- Joffrey Mander -- Julien Palard -- Raphael Gaschignard -- Sorin Sbarnea -- Gergely Kalmár -- Batuhan Taskaya -- Frank Harrison (doublethefish) -- Logan Miller <14319179+komodo472@users.noreply.github.com> (komodo472) -- Matthew Suozzo -- David Gilman -- Ikraduya Edian : Added new checks 'consider-using-generator' and 'use-a-generator'. -- Tiago Honorato <61059243+tiagohonorato@users.noreply.github.com> -- Lefteris Karapetsas -- Louis Sautier -- Quentin Young -- Alexander Kapshuna -- Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -- Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> -- Andrew Howe -- James Sinclair (irgeek) -- Aidan Haase <44787650+haasea@users.noreply.github.com> -- Elizabeth Bott <52465744+elizabethbott@users.noreply.github.com> -- Sebastian Müller -- Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp -- Andrew Haigh (nelfin) -- Aditya Gupta (adityagupta1089) - * Added ignore_signatures to duplicate checker -- David Liu (david-yz-liu) -- Fabian Damken -- Markus Siebenhaar <41283549+siehar@users.noreply.github.com> -- Lorena Buciu <46202743+lorena-b@users.noreply.github.com> (lorena-b) -- Maksym Humetskyi (mhumetskyi) - * Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled - * Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled - * Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled -- Daniel Dorani (doranid) -- Will Shanks -- Mark Bell -- Rebecca Turner (9999years) -- Marcin Kurczewski (rr-) -- Tanvi Moharir <74228962+tanvimoharir@users.noreply.github.com>: Fix for invalid toml config -- Eisuke Kawashima (e-kwsm) -- Michal Vasilek -- Kai Mueller <15907922+kasium@users.noreply.github.com> (kasium) -- Sam Vermeiren <88253337+PaaEl@users.noreply.github.com> (PaaEl) -- Philipp Albrecht (pylbrecht) -- Melvin Hazeleger <31448155+melvio@users.noreply.github.com> (melvio) -- Hayden Richards <62866982+SupImDos@users.noreply.github.com> (SupImDos) - * Fixed "no-self-use" for async methods - * Fixed "docparams" extension for async functions and methods -- Jeroen Seegers (jeroenseegers) - * Fixed `toml` dependency issue -- Tim Martin -- Jaehoon Hwang (jaehoonhwang) -- Nick Pesce -- Youngsoo Sung -- Samuel Freilich (sfreilich) -- Mike Fiedler (miketheman) -- Takahide Nojima -- Tushar Sadhwani (tusharsadhwani) -- Antonio Quarta (sgheppy) -- Jérome Perrin (perrinjerome) -- Felix von Drigalski (felixvd) -- Jake Lishman (jakelishman) -- Allan Chandler <95424144+allanc65@users.noreply.github.com> (allanc65) - * Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params -- Carli Freudenberg (CarliJoy) - * Fixed issue 5281, added Unicode checker - * Improve non-ascii-name checker -- Daniel Brookman <53625739+dbrookman@users.noreply.github.com> -- Téo Bouvard -- Konrad Weihmann <46938494+priv-kweihmann@users.noreply.github.com> -- Sergey B Kirpichev -- Joseph Young <80432516+jpy-git@users.noreply.github.com> (jpy-git) -- Adrien Di Mascio - Ville Skyttä +- Matus Valo - Pierre-Yves David +- David Shea : invalid sequence and slice index +- Derek Gustafson +- Cezar Elnazli : deprecated-method +- Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> - Nicolas Chauvat +- Radu Ciorba : not-context-manager and confusing-with-statement warnings. - Holger Peters +- Cosmin Poieană : unichr-builtin and improvements to bad-open-mode. +- Steven Myint : duplicate-except. +- Peter Kolbus (Garmin) +- Luigi Bertaco Cristofolini (luigibertaco) +- Glenn Matthews : + * autogenerated documentation for optional extensions, + * bug fixes and enhancements for docparams (née check_docs) extension +- Vlad Temian : redundant-unittest-assert and the JSON reporter. - Julien Jehannet +- Boris Feld - Anthony Sottile +- Pedro Algarvio (s0undt3ch) +- Julien Palard +- David Liu (david-yz-liu) +- Dan Goldsmith : support for msg-template in HTML reporter. +- Mariatta Wijaya + * Added new check `logging-fstring-interpolation` + * Documentation typo fixes - Jakub Wilk - Émile Crater - Pavel Roskin +- Eli Fine (eli88fine): Fixed false positive duplicate code warning for lines with symbols only +- David Gilman +- Andrew Haigh (nelfin) - へーさん +- Thomas Hisch +- Marianna Polatoglou : minor contribution for wildcard import check - Manuel Vázquez Acosta +- Luis Escobar (Vauxoo): Add bad-docstring-quotes and docstring-first-line-empty - Jim Robertson +- Ethan Leba +- Enji Cooper +- David Lindquist : logging-format-interpolation warning. +- Buck (Yelp) - Anthony Truchet +- Alexander Todorov : + * added new error conditions to 'bad-super-call', + * Added new check for incorrect len(SEQUENCE) usage, + * Added new extension for comparison against empty string constants, + * Added new extension which detects comparing integers to zero, + * Added new useless-return checker, + * Added new try-except-raise checker - orSolocate <38433858+orSolocate@users.noreply.github.com> -- Buck (Yelp) +- Téo Bouvard +- Tushar Sadhwani (tusharsadhwani) - Mihai Balint +- Mark Bell +- Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> +- Hugo van Kemenade - David Douard +- Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant) +- Bastien Vallet +- Aru Sahni : Git ignoring, regex-based ignores +- Andreas Freimuth : fix indentation checking with tabs - Alexandru Coman +- Takahide Nojima - Taewon D. Kim - Sneaky Pete - Rene Zhang +- Or Bahari - Mr. Senko +- Martin von Gagern (Google): Added 'raising-format-tuple' warning. +- Martin Vielsmaier +- Martin Pool (Google): + * warnings for anomalous backslashes + * symbolic names for messages (like 'unused') + * etc. +- Martin Bašti + * Added new check for shallow copy of os.environ + * Added new check for useless `with threading.Lock():` statement +- Marcus Näslund (naslundx) - Marco Forte - Ionel Maries Cristian +- Gergely Kalmár - Daniel Harding +- Damien Baty +- Benjamin Drung : contributing Debian Developer +- Anubhav <35621759+anubh-v@users.noreply.github.com> +- Antonio Quarta (sgheppy) +- Andrew J. Simmons (anjsimmo) - wtracy - jpkotta - chohner +- Tim Martin +- Tiago Honorato <61059243+tiagohonorato@users.noreply.github.com> - Steven M. Vascellaro +- Roberto Leinardi : PyCharm plugin maintainer - Ricardo Gemignani +- Pieter Engelbrecht +- Philipp Albrecht (pylbrecht) +- Nicolas Dickreuter - Nick Bastin +- Nathaniel Manista : suspicious lambda checking +- Mike Frysinger +- Maksym Humetskyi (mhumetskyi) + * Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled + * Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled + * Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled +- Lucas Cimon - Kylian +- Konstantin Manna +- Kai Mueller <15907922+kasium@users.noreply.github.com> (kasium) +- Joshua Cannon - John Leach +- James Morgensen : ignored-modules option applies to import errors. +- Jaehoon Hwang (jaehoonhwang) +- Ganden Schaffner +- Frost Ming (frostming) +- Federico Bond - Erik Wright +- Erik Eriksson : Added overlapping-except error check. - Dan Hemberger <846186+hemberger@users.noreply.github.com> +- Chris Rebert : unidiomatic-typecheck. - Aurelien Campeas - Alexander Pervakov - Alain Leufroy @@ -351,27 +218,61 @@ contributors: - craig-sh - bernie gray - Yilei "Dolee" Yang +- Wes Turner (Google): added new check 'inconsistent-quotes' - Tyler Thieding +- Tobias Hernstig <30827238+thernstig@users.noreply.github.com> - Thomas Grainger - Simu Toni +- Sergey B Kirpichev +- Sergei Lebedev <185856+superbobry@users.noreply.github.com> (superbobry) +- Scott Worley +- Rémi Cardona +- Raphael Gaschignard +- Ram Rachum (cool-RR) - Radostin Stoyanov - Paul Renvoisé - PHeanEX - Omega Weapon - Nikolai Kristiansen +- Nick Pesce +- Nathan Marrow +- Mikhail Fesenko +- Matthew Suozzo +- Matthew Beckers <17108752+mattlbeck@users.noreply.github.com> (mattlbeck) +- Mike Miller +- Mads Kiilerich +- Maarten ter Huurne +- Lefteris Karapetsas - LCD 47 +- Justin Li (justinnhli) +- Joseph Young <80432516+jpy-git@users.noreply.github.com> (jpy-git) - John Kirkham - Jens H. Nielsen +- Ioana Tagirta : fix bad thread instantiation check +- Ikraduya Edian : Added new checks 'consider-using-generator' and 'use-a-generator'. +- Hugues Bruant - Harut - Grygorii Iermolenko +- Gabriel R. Sezefredo : Fixed "exception-escape" false positive with generators - Filipe Brandenburger +- Fantix King (UChicago) +- Elias Dorneles : minor adjust to config defaults and docs - Derek Harland - David Pursehouse +- Daniel Miller - Chris Murray - Chris Lamb - Charles Hebert +- Carli Freudenberg (CarliJoy) + * Fixed issue 5281, added Unicode checker + * Improve non-ascii-name checker - Buck Golemon +- Brian Shaginaw : prevent error on exception check for functions - Benny Mueller +- Ben James +- Ben Green +- Batuhan Taskaya +- Alexander Kapshuna - Adam Parkin - 谭九鼎 <109224573@qq.com> - Łukasz Sznuk @@ -406,85 +307,171 @@ contributors: - amdev@AMDEV-WS01.cisco.com - agutole - Zeckie <49095968+Zeckie@users.noreply.github.com> +- Zeb Nicholls + * Made W9011 compatible with 'of' syntax in return types - Yuval Langer - Yury Gribov +- Yuri Bochkarev : Added epytext support to docparams extension. +- Youngsoo Sung - Yoichi Nakayama +- Yeting Li (yetingli) - Yannack - Yann Dirson +- Yang Yang - Xi Shen +- Will Shanks +- Viorel Știrbu : intern-builtin warning. - Victor Jiajunsu <16359131+jiajunsu@users.noreply.github.com> +- Trevor Bekolay + * Added --list-msgs-enabled command +- Tomer Chachamu : simplifiable-if-expression - Tomasz Magulski - Tim Hatch +- Tim Gates +- Tanvi Moharir <74228962+tanvimoharir@users.noreply.github.com>: Fix for invalid toml config - T.Rzepka +- Svetoslav Neykov +- Stéphane Wirtel : nonlocal-without-binding - Stephen Longofono <8992396+SLongofono@users.noreply.github.com> - Stanislav Levin +- Sorin Sbarnea - Skip Montanaro +- Shiv Venkatasubrahmanyam +- Sebastian Müller +- Sardorbek Imomaliev - Santiago Castro +- Sandro Tosi : Debian packaging +- Samuel Freilich (sfreilich) - Samuel FORESTIER +- Sam Vermeiren <88253337+PaaEl@users.noreply.github.com> (PaaEl) - Ryan McGuire - Ry4an Brase - Ruro - Roman Ivanov - Robin Tweedie <70587124+robin-wayve@users.noreply.github.com> +- Robert Schweizer +- Reverb Chu +- Renat Galimov +- Rebecca Turner (9999years) - Randall Leeds +- Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp - Qwiddle13 <32040075+Qwiddle13@users.noreply.github.com> +- Quentin Young +- Petr Pulc : require whitespace around annotations +- Peter Hammond - Peter Dawyndt - Peter Bittner - Peter Aronoff - Paul Cochrane - Patrik +- Pascal Corpet +- Pablo Galindo Salgado + * Fix false positive 'Non-iterable value' with async comprehensions. - Oisín Moran - Obscuron - Noam Yorav-Raphael - Nir Soffer +- Niko Wenselowski - Nikita Sobolev - Nick Smith - Ned Batchelder +- Natalie Serebryakova +- Mitchell Young : minor adjustment to docparams - Mitar +- Mike Fiedler (miketheman) - Mike Bryant - Michka Popoff +- Michal Vasilek +- Michael Scott Cuthbert - Michael Kefeder +- Michael Hudson-Doyle - Michael Giuffrida +- Melvin Hazeleger <31448155+melvio@users.noreply.github.com> (melvio) +- Matěj Grabovský +- Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> - Matej Marušák +- Markus Siebenhaar <41283549+siehar@users.noreply.github.com> - Marco Edward Gorelli +- Marcin Kurczewski (rr-) - Maik Röder +- Louis Sautier +- Lorena Buciu <46202743+lorena-b@users.noreply.github.com> (lorena-b) +- Logan Miller <14319179+komodo472@users.noreply.github.com> (komodo472) - Kári Tristan Helgason +- Kurian Benoy <70306694+kurianbenoy-aot@users.noreply.github.com> - Krzysztof Czapla - Kraig Brockschmidt - Kound +- Kosarchuk Sergey +- Konrad Weihmann <46938494+priv-kweihmann@users.noreply.github.com> - Kian Meng, Ang - Kevin Phillips - Kevin Jing Qiu - Kayran Schmidt <59456929+yumasheta@users.noreply.github.com> - Jürgen Hermann +- Jérome Perrin (perrinjerome) +- Josselin Feist - Jonathan Kotta +- John Paraskevopoulos : add 'differing-param-doc' and 'differing-type-doc' - John McGehee - John Gabriele - John Belmonte +- Joffrey Mander +- Jochen Preusche (iilei) +- Jeroen Seegers (jeroenseegers) + * Fixed `toml` dependency issue +- Jeremy Fleischman (jfly) +- Jason Owen - Jared Garst - Jared Deckard +- Janne Rönkkö +- James Sinclair (irgeek) - James M. Allen - James Lingard - James Broadhead - Jakob Normark +- Jake Lishman (jakelishman) +- Jacques Kvam +- Jace Browning : updated default report format with clickable paths - JT Olds +- Hayden Richards <62866982+SupImDos@users.noreply.github.com> (SupImDos) + * Fixed "no-self-use" for async methods + * Fixed "docparams" extension for async functions and methods - Grant Welch +- Giuseppe Valente +- Gary Tyler McLeod +- Felix von Drigalski (felixvd) - Fabrice Douchant - Fabio Natali +- Fabian Damken +- Eric Froemling - Emmanuel Chaudron +- Elizabeth Bott <52465744+elizabethbott@users.noreply.github.com> +- Eisuke Kawashima (e-kwsm) - Edgemaster +- Drew Risinger - Dr. Nick - Don Jayamanne - Dmytro Kyrychuk - Denis Laxalde +- David Cain +- Danny Hermes - Daniele Procida - Daniela Plascencia +- Daniel R. Neal (danrneal) +- Daniel Draper +- Daniel Dorani (doranid) +- Daniel Brookman <53625739+dbrookman@users.noreply.github.com> - Dan Garrette - Damien Nozay - Craig Citro +- Clément Pit-Claudel - Christopher Zurcher +- Carl Crowder : don't evaluate the value of arguments for 'dangerous-default-value' +- Carey Metcalfe : demoted `try-except-raise` from error to warning - Cameron Olechowski - Calin Don +- Caio Carrara - C.A.M. Gerlach - Bruno P. Kinoshita - Brian C. Lane @@ -493,18 +480,31 @@ contributors: - Benjamin Graham - Benedikt Morbach - Banjamin Freeman +- Athos Ribeiro : Fixed dict-keys-not-iterating false positive for inverse containment checks - Arun Persaud - Arthur Lutz - Antonio Ossa - Anthony VEREZ +- Anthony Foglia (Google): Added simple string slots check. - Anentropic +- Andy Palmer <25123779+ninezerozeronine@users.noreply.github.com> +- Andrzej Klajnert +- Andrew Howe - Andres Perez Hortal - Alok Singh <8325708+alok@users.noreply.github.com> +- Allan Chandler <95424144+allanc65@users.noreply.github.com> (allanc65) + * Fixed issue 5452, false positive missing-param-doc for multi-line Google-style params - Alex Jurkiewicz - Alex Hearn - Alan Evangelista +- Alan Chan +- Aivar Annamaa +- Aidan Haase <44787650+haasea@users.noreply.github.com> +- Ahirnish Pareek : 'keyword-arg-before-var-arg' check - Adrian Chirieac - +- Aditya Gupta (adityagupta1089) + * Added ignore_signatures to duplicate checker +- Adam Dangoor Co-Author --------- From 80838744c6f739fbf2f49bd1bfb70c98a84531aa Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 23 Mar 2022 17:56:09 +0100 Subject: [PATCH 299/357] Cleanup after finding emails and duplicates --- CONTRIBUTORS.txt | 48 +++++++++---------------------- script/.contributors_aliases.json | 6 +++- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 67a7e5d9bf..9a86723b36 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -99,7 +99,7 @@ contributors: - Cosmin Poieană : unichr-builtin and improvements to bad-open-mode. - Steven Myint : duplicate-except. - Peter Kolbus (Garmin) -- Luigi Bertaco Cristofolini (luigibertaco) +- Luigi Bertaco Cristofolini (luigibertaco) - Glenn Matthews : * autogenerated documentation for optional extensions, * bug fixes and enhancements for docparams (née check_docs) extension @@ -145,9 +145,11 @@ contributors: - Mark Bell - Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> - Hugo van Kemenade +- Hornwitser : fix import graph +- Fureigh - David Douard - Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant) -- Bastien Vallet +- Bastien Vallet (Djailla) - Aru Sahni : Git ignoring, regex-based ignores - Andreas Freimuth : fix indentation checking with tabs - Alexandru Coman @@ -239,7 +241,7 @@ contributors: - Mikhail Fesenko - Matthew Suozzo - Matthew Beckers <17108752+mattlbeck@users.noreply.github.com> (mattlbeck) -- Mike Miller +- Mark Roman Miller : fix inline defs in too-many-statements - Mads Kiilerich - Maarten ter Huurne - Lefteris Karapetsas @@ -282,7 +284,6 @@ contributors: - thinwybk - syutbai - sdet_liang -- sbagan - pyves@crater.logilab.fr - paschich - oittaa <8972248+oittaa@users.noreply.github.com> @@ -290,6 +291,7 @@ contributors: - mar-chi-pan - ludal@logilab.fr - lrjball <50599110+lrjball@users.noreply.github.com> +- laike9m - jpkotta@shannon - jaydesl <35102795+jaydesl@users.noreply.github.com> - jab @@ -313,6 +315,7 @@ contributors: - Yury Gribov - Yuri Bochkarev : Added epytext support to docparams extension. - Youngsoo Sung +- Yory <39745367+yory8@users.noreply.github.com> (yory8) - Yoichi Nakayama - Yeting Li (yetingli) - Yannack @@ -335,9 +338,11 @@ contributors: - Stephen Longofono <8992396+SLongofono@users.noreply.github.com> - Stanislav Levin - Sorin Sbarnea +- Slavfox - Skip Montanaro - Shiv Venkatasubrahmanyam - Sebastian Müller +- Sasha Bagan - Sardorbek Imomaliev - Santiago Castro - Sandro Tosi : Debian packaging @@ -358,7 +363,6 @@ contributors: - Qwiddle13 <32040075+Qwiddle13@users.noreply.github.com> - Quentin Young - Petr Pulc : require whitespace around annotations -- Peter Hammond - Peter Dawyndt - Peter Bittner - Peter Aronoff @@ -391,11 +395,11 @@ contributors: - Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> - Matej Marušák - Markus Siebenhaar <41283549+siehar@users.noreply.github.com> -- Marco Edward Gorelli +- Marco Edward Gorelli : Documented Jupyter integration - Marcin Kurczewski (rr-) - Maik Röder - Louis Sautier -- Lorena Buciu <46202743+lorena-b@users.noreply.github.com> (lorena-b) +- Lorena Buciu <46202743+lorena-b@users.noreply.github.com> (lorena-b) - Logan Miller <14319179+komodo472@users.noreply.github.com> (komodo472) - Kári Tristan Helgason - Kurian Benoy <70306694+kurianbenoy-aot@users.noreply.github.com> @@ -437,6 +441,7 @@ contributors: - Hayden Richards <62866982+SupImDos@users.noreply.github.com> (SupImDos) * Fixed "no-self-use" for async methods * Fixed "docparams" extension for async functions and methods +- Harshil <37377066+harshil21@users.noreply.github.com> (harshil21) - Grant Welch - Giuseppe Valente - Gary Tyler McLeod @@ -485,6 +490,7 @@ contributors: - Arthur Lutz - Antonio Ossa - Anthony VEREZ +- Anthony Tan - Anthony Foglia (Google): Added simple string slots check. - Anentropic - Andy Palmer <25123779+ninezerozeronine@users.noreply.github.com> @@ -515,55 +521,27 @@ under this name, or we did not manage to find their commits in the history. - Amaury Forgeot d'Arc: check names imported from a module exists in the module - Anthony Tan - Axel Muller -- Ben Graham -- Benjamin Freeman - Benjamin Niemann: allow block level enabling/disabling of messages -- Benny Müller - Bernard Nauwelaerts -- Bernie Gray - Bill Wendling -- Bluesheeptoken - Brian van den Broek: windows installation documentation - Craig Henriques - D. Alphus (Alphadelta14) - Daniil Kharkov -- das-intensity - Eero Vuojolahti - Fabio Zadrozny -- Fureigh - Gauthier Sebaux -- Goudcode -- Harshil (harshil21) -- Hornwitser: fix import graph - James DesLauriers -- Jiajunsu (victor) -- Kian-Meng, Ang -- laike9m - manderj -- Marco Gorelli: Documented Jupyter integration -- Mark Roman Miller: fix inline defs in too-many-statements -- Matej Marusak - Mirko Friedenhagen - Nicholas Smith - Nuzula H. Yudaka (Nuzhuka) -- Oisin Moran -- Paul Renvoise - Pek Chhan - Peter Hammond - Pierre Rouleau -- qwiddle - Richard Goodman: simplifiable-if-expression (with Tomer Chachamu) -- ruro -- Samuel Forestier -- Sasha Bagan - Sebastian Ulrich -- Slavfox -- Taewon Kim - Takashi Hirashima - Thomas Snowden: fix missing-docstring for inner functions -- Tyler N. Thieding - Wolfgang Grafen - Yannick Brehon -- Yilei Yang -- Yory (yory8) -- Yuri Gribov diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index 9b77661efd..50f879ed06 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -472,6 +472,10 @@ "name": "Pierre Sassoulas", "team": "Maintainers" }, + "pnlbagan@gmail.com": { + "mails": ["pnlbagan@gmail.com"], + "name": "Sasha Bagan" + }, "raphael@makeleaps.com": { "mails": ["raphael@rtpg.co", "raphael@makeleaps.com"], "name": "Raphael Gaschignard" @@ -529,7 +533,7 @@ }, "tanant@users.noreply.github.com": { "mails": ["tanant@users.noreply.github.com"], - "name": "Anthony" + "name": "Anthony Tan" }, "thenault@gmail.com": { "mails": ["thenault@gmail.com", "sylvain.thenault@logilab.fr"], From 9e0baf370a15fecf3360996a6a2ead688fc61894 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 12 Mar 2022 12:39:52 +0100 Subject: [PATCH 300/357] Simplify hard to maintain copyright notice git is the source of truth for the copyright, copyrite (the tool) was taking exponentially longer with each release, and it's polluting the code with sometime as much as 50 lines of names. --- doc/exts/pylint_extensions.py | 1 + doc/exts/pylint_features.py | 1 + doc/exts/pylint_messages.py | 1 + doc/release.md | 2 +- pylint/__init__.py | 9 +-- pylint/__main__.py | 1 + pylint/__pkginfo__.py | 1 + pylint/checkers/__init__.py | 19 +----- pylint/checkers/async.py | 8 +-- pylint/checkers/base.py | 68 +------------------ pylint/checkers/base_checker.py | 18 +---- pylint/checkers/classes/__init__.py | 1 + pylint/checkers/classes/class_checker.py | 49 +------------ .../classes/special_methods_checker.py | 1 + pylint/checkers/deprecated.py | 1 + pylint/checkers/design_analysis.py | 28 +------- pylint/checkers/exceptions.py | 33 +-------- pylint/checkers/format.py | 45 +----------- pylint/checkers/imports.py | 45 +----------- pylint/checkers/logging.py | 23 +------ pylint/checkers/mapreduce_checker.py | 5 +- pylint/checkers/misc.py | 24 +------ pylint/checkers/modified_iterating_checker.py | 1 + pylint/checkers/newstyle.py | 20 +----- pylint/checkers/non_ascii_names.py | 4 +- pylint/checkers/raw_metrics.py | 16 +---- pylint/checkers/refactoring/__init__.py | 35 +--------- .../implicit_booleaness_checker.py | 1 + pylint/checkers/refactoring/not_checker.py | 1 + .../refactoring/recommendation_checker.py | 1 + .../refactoring/refactoring_checker.py | 1 + pylint/checkers/similar.py | 27 +------- pylint/checkers/spelling.py | 28 +------- pylint/checkers/stdlib.py | 35 +--------- pylint/checkers/strings.py | 34 +--------- pylint/checkers/threading_checker.py | 1 + pylint/checkers/typecheck.py | 55 +-------------- pylint/checkers/unicode.py | 4 +- pylint/checkers/unsupported_version.py | 5 +- pylint/checkers/utils.py | 57 +--------------- pylint/checkers/variables.py | 54 +-------------- pylint/config/__init__.py | 34 +--------- pylint/config/config_initialization.py | 1 + pylint/config/configuration_mixin.py | 1 + pylint/config/find_default_config_files.py | 1 + pylint/config/man_help_formatter.py | 1 + pylint/config/option.py | 1 + pylint/config/option_manager_mixin.py | 1 + pylint/config/option_parser.py | 1 + pylint/config/options_provider_mixin.py | 1 + pylint/constants.py | 1 + pylint/epylint.py | 25 +------ pylint/exceptions.py | 12 +--- pylint/extensions/__init__.py | 1 + pylint/extensions/_check_docs_utils.py | 22 +----- pylint/extensions/bad_builtin.py | 10 +-- pylint/extensions/broad_try_clause.py | 9 +-- pylint/extensions/check_elif.py | 12 +--- pylint/extensions/comparetozero.py | 10 +-- pylint/extensions/comparison_placement.py | 1 + pylint/extensions/confusing_elif.py | 7 +- pylint/extensions/docparams.py | 24 +------ pylint/extensions/docstyle.py | 10 +-- pylint/extensions/emptystring.py | 10 +-- pylint/extensions/mccabe.py | 11 +-- pylint/extensions/overlapping_exceptions.py | 1 + pylint/extensions/redefined_variable_type.py | 11 +-- pylint/graph.py | 16 +---- pylint/interfaces.py | 17 +---- pylint/lint/__init__.py | 59 +--------------- pylint/lint/parallel.py | 1 + pylint/lint/pylinter.py | 1 + pylint/lint/report_functions.py | 1 + pylint/lint/run.py | 1 + pylint/lint/utils.py | 1 + pylint/message/__init__.py | 38 +---------- pylint/message/message.py | 1 + pylint/message/message_definition.py | 1 + pylint/message/message_definition_store.py | 1 + pylint/message/message_id_store.py | 1 + pylint/pyreverse/__init__.py | 1 + pylint/pyreverse/diadefslib.py | 19 +----- pylint/pyreverse/diagrams.py | 16 +---- pylint/pyreverse/dot_printer.py | 8 +-- pylint/pyreverse/inspector.py | 14 +--- pylint/pyreverse/main.py | 20 +----- pylint/pyreverse/mermaidjs_printer.py | 3 +- pylint/pyreverse/plantuml_printer.py | 3 +- pylint/pyreverse/printer.py | 7 +- pylint/pyreverse/printer_factory.py | 5 +- pylint/pyreverse/utils.py | 19 +----- pylint/pyreverse/vcg_printer.py | 13 +--- pylint/pyreverse/writer.py | 16 +---- pylint/reporters/__init__.py | 21 +----- pylint/reporters/base_reporter.py | 1 + pylint/reporters/collecting_reporter.py | 1 + pylint/reporters/json_reporter.py | 12 +--- pylint/reporters/multi_reporter.py | 1 + pylint/reporters/reports_handler_mix_in.py | 1 + pylint/reporters/text.py | 19 +----- pylint/reporters/ureports/__init__.py | 1 + pylint/reporters/ureports/base_writer.py | 11 +-- pylint/reporters/ureports/nodes.py | 11 +-- pylint/reporters/ureports/text_writer.py | 10 +-- pylint/testutils/__init__.py | 28 +------- pylint/testutils/checker_test_case.py | 1 + pylint/testutils/configuration_test.py | 1 + pylint/testutils/constants.py | 1 + pylint/testutils/decorator.py | 1 + pylint/testutils/functional/__init__.py | 1 + .../functional/find_functional_tests.py | 1 + .../functional/lint_module_output_update.py | 1 + pylint/testutils/functional/test_file.py | 1 + pylint/testutils/functional_test_file.py | 1 + pylint/testutils/get_test_info.py | 1 + pylint/testutils/global_test_linter.py | 1 + pylint/testutils/lint_module_test.py | 1 + pylint/testutils/output_line.py | 1 + pylint/testutils/pyreverse.py | 1 + pylint/testutils/reporter_for_tests.py | 1 + pylint/testutils/tokenize_str.py | 1 + pylint/testutils/unittest_linter.py | 1 + pylint/typing.py | 1 + pylint/utils/__init__.py | 40 +---------- pylint/utils/ast_walker.py | 1 + pylint/utils/docs.py | 1 + pylint/utils/file_state.py | 1 + pylint/utils/linterstats.py | 1 + pylint/utils/pragma_parser.py | 1 + pylint/utils/utils.py | 1 + tbump.toml | 4 -- tests/benchmark/test_baseline_benchmarks.py | 8 +-- tests/checkers/unittest_base.py | 25 +------ tests/checkers/unittest_base_checker.py | 1 + tests/checkers/unittest_design.py | 7 +- tests/checkers/unittest_format.py | 26 +------ tests/checkers/unittest_imports.py | 17 +---- tests/checkers/unittest_misc.py | 16 +---- tests/checkers/unittest_refactoring.py | 1 + tests/checkers/unittest_similar.py | 20 +----- tests/checkers/unittest_spelling.py | 18 +---- tests/checkers/unittest_stdlib.py | 11 +-- tests/checkers/unittest_strings.py | 11 +-- tests/checkers/unittest_typecheck.py | 25 +------ tests/checkers/unittest_utils.py | 18 +---- tests/checkers/unittest_variables.py | 20 +----- .../config/test_functional_config_loading.py | 1 + tests/config/unittest_config.py | 12 +--- tests/extensions/test_check_docs_utils.py | 12 +--- tests/input/similar_lines_a.py | 1 - tests/input/similar_lines_b.py | 1 - tests/lint/unittest_expand_modules.py | 1 + tests/lint/unittest_lint.py | 38 +---------- tests/message/conftest.py | 1 + .../test_no_removed_msgid_or_symbol_used.py | 1 + tests/message/unittest_message.py | 1 + tests/message/unittest_message_definition.py | 1 + .../unittest_message_definition_store.py | 1 + tests/message/unittest_message_id_store.py | 1 + tests/primer/test_primer_external.py | 1 + tests/primer/test_primer_stdlib.py | 1 + .../profile/test_profile_against_externals.py | 5 +- tests/pyreverse/test_diadefs.py | 21 +----- tests/pyreverse/test_diagrams.py | 5 +- tests/pyreverse/test_inspector.py | 13 +--- tests/pyreverse/test_printer.py | 5 +- tests/pyreverse/test_printer_factory.py | 3 +- tests/pyreverse/test_utils.py | 10 +-- tests/pyreverse/test_writer.py | 17 +---- tests/test_check_parallel.py | 11 +-- tests/test_func.py | 18 +---- tests/test_functional.py | 21 +----- tests/test_import_graph.py | 18 +---- tests/test_numversion.py | 1 + tests/test_regr.py | 17 +---- tests/test_self.py | 35 +--------- tests/testutils/test_decorator.py | 1 + tests/testutils/test_functional_testutils.py | 1 + .../test_lint_module_output_update.py | 1 + tests/testutils/test_output_line.py | 1 + tests/testutils/test_package_to_lint.py | 1 + tests/unittest_reporters_json.py | 13 +--- tests/unittest_reporting.py | 17 +---- tests/utils/unittest_ast_walker.py | 1 + tests/utils/unittest_utils.py | 19 +----- 185 files changed, 190 insertions(+), 1814 deletions(-) diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py index d8f8dfeca5..921db904df 100755 --- a/doc/exts/pylint_extensions.py +++ b/doc/exts/pylint_extensions.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Script used to generate the extensions file before building the actual documentation.""" diff --git a/doc/exts/pylint_features.py b/doc/exts/pylint_features.py index a867dd05fe..b4e5e2df2f 100755 --- a/doc/exts/pylint_features.py +++ b/doc/exts/pylint_features.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Script used to generate the features file before building the actual documentation.""" diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index 0f9ac35de9..51b10482f0 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Script used to generate the messages files.""" diff --git a/doc/release.md b/doc/release.md index e2196859e4..22194cd9b7 100644 --- a/doc/release.md +++ b/doc/release.md @@ -12,7 +12,7 @@ So, you want to release the `X.Y.Z` version of pylint ? 5. Move back to a dev version for pylint with `tbump`: ```bash -tbump X.Y.Z+1-dev0 --no-tag --no-push # You can interrupt during copyrite +tbump X.Y.Z+1-dev0 --no-tag --no-push # You can interrupt after the first step git commit -am "Move back to a dev version following X.Y.Z release" ``` diff --git a/pylint/__init__.py b/pylint/__init__.py index 02df46054e..cb5468eb5a 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -1,13 +1,6 @@ -# Copyright (c) 2008, 2012 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014, 2016-2020 Claudiu Popa -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os import sys diff --git a/pylint/__main__.py b/pylint/__main__.py index 278570f619..267adb9eb0 100644 --- a/pylint/__main__.py +++ b/pylint/__main__.py @@ -2,6 +2,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import pylint diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 9e0f1edaa2..24bc31e99c 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import Tuple __version__ = "2.13.0-dev0" diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 61ce67ffef..1f7f8fd38d 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -1,23 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2013 buck@yelp.com -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017-2018 Bryce Guinta -# Copyright (c) 2018-2021 Pierre Sassoulas -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019 Bruno P. Kinoshita -# Copyright (c) 2020-2021 hippo91 -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Matus Valo - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Utilities methods and classes for checkers. diff --git a/pylint/checkers/async.py b/pylint/checkers/async.py index 4a5c6abf25..58fe8a5d8f 100644 --- a/pylint/checkers/async.py +++ b/pylint/checkers/async.py @@ -1,12 +1,6 @@ -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2017 Derek Gustafson -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker for anything related to the async protocol (PEP 492).""" diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index faacee9984..7f2d9da7b3 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -1,72 +1,6 @@ -# Copyright (c) 2006-2016 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2010 Daniel Harding -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Nick Bastin -# Copyright (c) 2015 Michael Kefeder -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Stephane Wirtel -# Copyright (c) 2015 Cosmin Poieana -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Radu Ciorba -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2019 Ashley Whetter -# Copyright (c) 2016, 2018 Jakub Wilk -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Elias Dorneles -# Copyright (c) 2016 Yannack -# Copyright (c) 2016 Alex Jurkiewicz -# Copyright (c) 2017, 2019-2021 Pierre Sassoulas -# Copyright (c) 2017, 2019-2021 hippo91 -# Copyright (c) 2017 danields -# Copyright (c) 2017 Jacques Kvam -# Copyright (c) 2017 ttenhoeve-aa -# Copyright (c) 2018-2019, 2021 Nick Drozd -# Copyright (c) 2018-2019, 2021 Ville Skyttä -# Copyright (c) 2018 Sergei Lebedev <185856+superbobry@users.noreply.github.com> -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Natalie Serebryakova -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 SergeyKosarchuk -# Copyright (c) 2018 Steven M. Vascellaro -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Chris Lamb -# Copyright (c) 2018 glmdgrielson <32415403+glmdgrielson@users.noreply.github.com> -# Copyright (c) 2019 Daniel Draper -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Niko Wenselowski -# Copyright (c) 2019 Nikita Sobolev -# Copyright (c) 2019 Oisín Moran -# Copyright (c) 2019 Fantix King -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 ethan-leba -# Copyright (c) 2020 へーさん -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Ram Rachum -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 bernie gray -# Copyright (c) 2020 Gabriel R Sezefredo -# Copyright (c) 2020 Benny -# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Tim Martin -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 jaydesl <35102795+jaydesl@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Yilei "Dolee" Yang -# Copyright (c) 2021 Lorena B <46202743+lorena-b@users.noreply.github.com> -# Copyright (c) 2021 David Liu -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021-2022 Or Bahari - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Basic checker for Python code.""" import collections diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index 376e785450..e0e3c03f30 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -1,22 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2013 buck@yelp.com -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017-2018 Bryce Guinta -# Copyright (c) 2018-2021 Pierre Sassoulas -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019 Bruno P. Kinoshita -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import functools from inspect import cleandoc from typing import Any, Optional diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index 2b0e595e64..ac1641fdf4 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index ab954f2e83..42fc83ea70 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1,53 +1,6 @@ -# Copyright (c) 2006-2016 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2010 Maarten ter Huurne -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2014 David Pursehouse -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2016 Anthony Foglia -# Copyright (c) 2016 Florian Bruhin -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2016 Jakub Wilk -# Copyright (c) 2017, 2019-2020 hippo91 -# Copyright (c) 2018, 2021 Ville Skyttä -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018-2019 Nick Drozd -# Copyright (c) 2018-2019 Ashley Whetter -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Ben Green -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 mattlbeck <17108752+mattlbeck@users.noreply.github.com> -# Copyright (c) 2019-2020 craig-sh -# Copyright (c) 2019 Janne Rönkkö -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Grygorii Iermolenko -# Copyright (c) 2019 Andrzej Klajnert -# Copyright (c) 2019 Pascal Corpet -# Copyright (c) 2020 GergelyKalmar -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Samuel Freilich -# Copyright (c) 2021 Nick Pesce -# Copyright (c) 2021 bot -# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> -# Copyright (c) 2021 SupImDos <62866982+SupImDos@users.noreply.github.com> -# Copyright (c) 2021 Kayran Schmidt <59456929+yumasheta@users.noreply.github.com> -# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> -# Copyright (c) 2021 James Sinclair -# Copyright (c) 2021 tiagohonorato <61059243+tiagohonorato@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Classes checker for Python code.""" import collections diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py index d3d2b0935e..e85150f082 100644 --- a/pylint/checkers/classes/special_methods_checker.py +++ b/pylint/checkers/classes/special_methods_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Special methods checker and helper function's module.""" diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index dd75547b95..0edbac4a98 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker mixin for deprecated functionality.""" from itertools import chain diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index d2ad84539e..ad600fc2fd 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -1,32 +1,6 @@ -# Copyright (c) 2006, 2009-2010, 2012-2015 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012, 2014 Google, Inc. -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Łukasz Rogalski -# Copyright (c) 2017 ahirnish -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Mark Miller <725mrm@gmail.com> -# Copyright (c) 2018 Ashley Whetter -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2018 Jakub Wilk -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Michael Scott Cuthbert -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Mike Fiedler -# Copyright (c) 2021 Youngsoo Sung -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Andrew Haigh -# Copyright (c) 2021 Melvin <31448155+melvio@users.noreply.github.com> -# Copyright (c) 2021 Rebecca Turner -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Check for signs of poor design.""" diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 58d273ca2e..1020e7c183 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -1,37 +1,6 @@ -# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2011-2014 Google, Inc. -# Copyright (c) 2012 Tim Hatch -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Rene Zhang -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Steven Myint -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Erik -# Copyright (c) 2016 Jakub Wilk -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 Martin von Gagern -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Natalie Serebryakova -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Carey Metcalfe -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Alexander Todorov -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Djailla -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Ram Rachum -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checks for various exception related errors.""" import builtins diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 716dce4cba..9f125b5681 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -1,49 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012-2015 Google, Inc. -# Copyright (c) 2013 moxian -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 frost-nzcr4 -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Mike Frysinger -# Copyright (c) 2015 Fabio Natali -# Copyright (c) 2015 Harut -# Copyright (c) 2015 Mihai Balint -# Copyright (c) 2015 Pavel Roskin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Petr Pulc -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2016 Ashley Whetter -# Copyright (c) 2017, 2019-2020 hippo91 -# Copyright (c) 2017-2018 Bryce Guinta -# Copyright (c) 2017 Krzysztof Czapla -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 James M. Allen -# Copyright (c) 2017 vinnyrose -# Copyright (c) 2018-2021 Pierre Sassoulas -# Copyright (c) 2018, 2020 Bryce Guinta -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Michael Hudson-Doyle -# Copyright (c) 2018 Natalie Serebryakova -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Marcus Näslund -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Fureigh -# Copyright (c) 2018 Andreas Freimuth -# Copyright (c) 2018 Jakub Wilk -# Copyright (c) 2019 Nick Drozd -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Raphael Gaschignard -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 bot -# Copyright (c) 2021 Ville Skyttä -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Python code format's checker. diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index f9422f9b32..4e8f64ccf0 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -1,49 +1,6 @@ -# Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2013 buck@yelp.com -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2016 Moises Lopez -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Cezar -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Noam Yorav-Raphael -# Copyright (c) 2015 James Morgensen -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2021 Ashley Whetter -# Copyright (c) 2016 Jared Garst -# Copyright (c) 2016 Maik Röder -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2017 Michka Popoff -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 Erik Wright -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Hornwitser -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Natalie Serebryakova -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Marianna Polatoglou -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019, 2021 Nick Drozd -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Nick Smith -# Copyright (c) 2019 Paul Renvoisé -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Will Shanks -# Copyright (c) 2021 Matus Valo -# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> -# Copyright (c) 2021 Andrew Howe - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Imports checkers for Python code.""" diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index aedaf62415..084aeebeec 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -1,27 +1,6 @@ -# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2009, 2012, 2014 Google, Inc. -# Copyright (c) 2012 Mike Bryant -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2019-2020 Ashley Whetter -# Copyright (c) 2016 Chris Murray -# Copyright (c) 2017 guillaume2 -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 Alan Chan -# Copyright (c) 2018 Yury Gribov -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Mariatta Wijaya -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Djailla -# Copyright (c) 2019 Svet -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker for use of Python logging.""" import string diff --git a/pylint/checkers/mapreduce_checker.py b/pylint/checkers/mapreduce_checker.py index a4a2c605c4..3078b6872e 100644 --- a/pylint/checkers/mapreduce_checker.py +++ b/pylint/checkers/mapreduce_checker.py @@ -1,9 +1,6 @@ -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import abc diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index 6461975ae4..c558e28851 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -1,28 +1,6 @@ -# Copyright (c) 2006, 2009-2013 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Alexandru Coman -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Łukasz Rogalski -# Copyright (c) 2016 glegoux -# Copyright (c) 2017-2020 hippo91 -# Copyright (c) 2017 Mikhail Fesenko -# Copyright (c) 2018 Rogalski, Lukasz -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2020 wtracy -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 Benny -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Check source code is ascii only or has an encoding declaration (PEP 263).""" diff --git a/pylint/checkers/modified_iterating_checker.py b/pylint/checkers/modified_iterating_checker.py index 711d37bb0e..b3e5daeae8 100644 --- a/pylint/checkers/modified_iterating_checker.py +++ b/pylint/checkers/modified_iterating_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING, Union diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index eb8473d09f..af4e365d02 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -1,24 +1,6 @@ -# Copyright (c) 2006, 2008-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2016 Jakub Wilk -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Natalie Serebryakova -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Robert Schweizer -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Check for new / old style related problems.""" from typing import TYPE_CHECKING diff --git a/pylint/checkers/non_ascii_names.py b/pylint/checkers/non_ascii_names.py index d9b3bfd505..d80d9dec05 100644 --- a/pylint/checkers/non_ascii_names.py +++ b/pylint/checkers/non_ascii_names.py @@ -1,7 +1,7 @@ -# Copyright (c) 2021-2022 Carli Freudenberg - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors + """All alphanumeric unicode character are allowed in Python but due to similarities in how they look they can be confused. diff --git a/pylint/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index dae296ec84..15f08dc852 100644 --- a/pylint/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py @@ -1,20 +1,6 @@ -# Copyright (c) 2007, 2010, 2013, 2015 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Mike Frysinger -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2020-2021 hippo91 -# Copyright (c) 2020 谭九鼎 <109224573@qq.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import sys import tokenize diff --git a/pylint/checkers/refactoring/__init__.py b/pylint/checkers/refactoring/__init__.py index 8c873c6988..fdcf635fd0 100644 --- a/pylint/checkers/refactoring/__init__.py +++ b/pylint/checkers/refactoring/__init__.py @@ -1,39 +1,6 @@ -# Copyright (c) 2016-2020 Claudiu Popa -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2017-2018, 2020 hippo91 -# Copyright (c) 2017-2018 Ville Skyttä -# Copyright (c) 2017-2018 Bryce Guinta -# Copyright (c) 2017 Hugo -# Copyright (c) 2017 Łukasz Sznuk -# Copyright (c) 2017 Alex Hearn -# Copyright (c) 2017 Antonio Ossa -# Copyright (c) 2018-2019 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Justin Li -# Copyright (c) 2018 Jim Robertson -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Ben James -# Copyright (c) 2018 Tomer Chachamu -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2018 Konstantin Manna -# Copyright (c) 2018 Konstantin -# Copyright (c) 2018 Matej Marušák -# Copyright (c) 2018 Mr. Senko -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Rémi Cardona -# Copyright (c) 2019 Robert Schweizer -# Copyright (c) 2019 PHeanEX -# Copyright (c) 2019 Paul Renvoise -# Copyright (c) 2020 ethan-leba -# Copyright (c) 2020 lrjball <50599110+lrjball@users.noreply.github.com> -# Copyright (c) 2020 Yang Yang -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Looks for code which can be refactored.""" diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index e9482f6b71..dd59b2085b 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import List, Union import astroid diff --git a/pylint/checkers/refactoring/not_checker.py b/pylint/checkers/refactoring/not_checker.py index d1710ea222..c7c8f4289e 100644 --- a/pylint/checkers/refactoring/not_checker.py +++ b/pylint/checkers/refactoring/not_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import astroid diff --git a/pylint/checkers/refactoring/recommendation_checker.py b/pylint/checkers/refactoring/recommendation_checker.py index d517cfd1b5..5aa1d26226 100644 --- a/pylint/checkers/refactoring/recommendation_checker.py +++ b/pylint/checkers/refactoring/recommendation_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import Union import astroid diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 0644271b21..1514770479 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections import copy diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 8e6eee92d5..cd70e326b3 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -1,31 +1,6 @@ -# Copyright (c) 2006, 2008-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 Ry4an Brase -# Copyright (c) 2012 Google, Inc. -# Copyright (c) 2012 Anthony VEREZ -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2017, 2020 Anthony Sottile -# Copyright (c) 2017 Mikhail Fesenko -# Copyright (c) 2018 Scott Worley -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Taewon D. Kim -# Copyright (c) 2020-2021 hippo91 -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2020 Eli Fine -# Copyright (c) 2020 Shiv Venkatasubrahmanyam -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Ville Skyttä -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Maksym Humetskyi -# Copyright (c) 2021 bot -# Copyright (c) 2021 Aditya Gupta - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """A similarities / code duplication command line tool and pylint checker. diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index e63196155a..92d6e037ff 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -1,32 +1,6 @@ -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2015 Pavel Roskin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017, 2020 Pedro Algarvio -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 Mikhail Fesenko -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Peter Kolbus -# Copyright (c) 2019 agutole -# Copyright (c) 2020 Ganden Schaffner -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Eli Fine - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker for spelling errors in comments and docstrings.""" import os diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 5b4670803e..ad5aa3dbcc 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -1,39 +1,6 @@ -# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Cosmin Poieana -# Copyright (c) 2014 Vlad Temian -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Cezar -# Copyright (c) 2015 Chris Rebert -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Jared Garst -# Copyright (c) 2017 Renat Galimov -# Copyright (c) 2017 Martin -# Copyright (c) 2017 Christopher Zurcher -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Banjamin Freeman -# Copyright (c) 2018 Ioana Tagirta -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Julien Palard -# Copyright (c) 2019 laike9m -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Robert Schweizer -# Copyright (c) 2019 fadedDexofan -# Copyright (c) 2020 Sorin Sbarnea -# Copyright (c) 2020 Federico Bond -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 谭九鼎 <109224573@qq.com> -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Yilei "Dolee" Yang -# Copyright (c) 2021 Matus Valo -# Copyright (c) 2021 victor <16359131+jiajunsu@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checkers for various standard library functions.""" diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 3b2ed51068..19bdbce91f 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -1,38 +1,6 @@ -# Copyright (c) 2009-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2010 Daniel Harding -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Rene Zhang -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2018 Jakub Wilk -# Copyright (c) 2016 Peter Dawyndt -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 Ville Skyttä -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018-2019 Lucas Cimon -# Copyright (c) 2018 Alan Chan -# Copyright (c) 2018 Yury Gribov -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Wes Turner -# Copyright (c) 2019 Djailla -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Matthew Suozzo -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 谭九鼎 <109224573@qq.com> -# Copyright (c) 2020 Anthony -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Peter Kolbus - - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker for string formatting operations.""" diff --git a/pylint/checkers/threading_checker.py b/pylint/checkers/threading_checker.py index 6148b82923..649696f4b4 100644 --- a/pylint/checkers/threading_checker.py +++ b/pylint/checkers/threading_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 7cb132830e..f220f4c273 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1,59 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2009 James Lingard -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 David Shea -# Copyright (c) 2014 Steven Myint -# Copyright (c) 2014 Holger Peters -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Anentropic -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Rene Zhang -# Copyright (c) 2015 Radu Ciorba -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2019 Ashley Whetter -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2016 Jürgen Hermann -# Copyright (c) 2016 Jakub Wilk -# Copyright (c) 2016 Filipe Brandenburger -# Copyright (c) 2017, 2021 Ville Skyttä -# Copyright (c) 2017-2018, 2020 hippo91 -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 Derek Gustafson -# Copyright (c) 2018-2019, 2021 Nick Drozd -# Copyright (c) 2018 Pablo Galindo -# Copyright (c) 2018 Jim Robertson -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Ben Green -# Copyright (c) 2018 Konstantin -# Copyright (c) 2018 Justin Li -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Andy Palmer <25123779+ninezerozeronine@users.noreply.github.com> -# Copyright (c) 2019 mattlbeck <17108752+mattlbeck@users.noreply.github.com> -# Copyright (c) 2019 Martin Vielsmaier -# Copyright (c) 2019 Santiago Castro -# Copyright (c) 2019 yory8 <39745367+yory8@users.noreply.github.com> -# Copyright (c) 2019 Federico Bond -# Copyright (c) 2019 Pascal Corpet -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 Julien Palard -# Copyright (c) 2020 Ram Rachum -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 David Liu -# Copyright (c) 2021 doranid -# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> -# Copyright (c) 2021 Andrew Haigh -# Copyright (c) 2021 Jens H. Nielsen -# Copyright (c) 2021 Ikraduya Edian - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Try to find more bugs in the code using astroid inference capabilities.""" diff --git a/pylint/checkers/unicode.py b/pylint/checkers/unicode.py index 93dfacb4b6..a6f46ad4bb 100644 --- a/pylint/checkers/unicode.py +++ b/pylint/checkers/unicode.py @@ -1,7 +1,7 @@ -# Copyright (c) 2021-2022 Carli Freudenberg - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors + """Unicode and some other ASCII characters can be used to create programs that run much different compared to what a human reader would expect from them. diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index 7c82817c00..8c34a921f8 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -1,9 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker for features used that are not supported by all python versions indicated by the py-version setting. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 6966bf16f9..f3b643109b 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1,61 +1,6 @@ -# Copyright (c) 2006-2007, 2009-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2009 Mads Kiilerich -# Copyright (c) 2010 Daniel Harding -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Ricardo Gemignani -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Radu Ciorba -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2018-2019 Ashley Whetter -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016-2017 Moises Lopez -# Copyright (c) 2016 Brian C. Lane -# Copyright (c) 2017-2018, 2020 hippo91 -# Copyright (c) 2017 ttenhoeve-aa -# Copyright (c) 2018 Alan Chan -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Yury Gribov -# Copyright (c) 2018 Caio Carrara -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2018 Brian Shaginaw -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> -# Copyright (c) 2019 Djailla -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Nathan Marrow -# Copyright (c) 2019 Svet -# Copyright (c) 2019 Pascal Corpet -# Copyright (c) 2020 Batuhan Taskaya -# Copyright (c) 2020 Luigi -# Copyright (c) 2020 ethan-leba -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Andrew Simmons -# Copyright (c) 2020 Ram Rachum -# Copyright (c) 2020 Slavfox -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Yu Shao, Pang <36848472+yushao2@users.noreply.github.com> -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Arianna Y <92831762+areveny@users.noreply.github.com> -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Samuel FORESTIER -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 David Liu -# Copyright (c) 2021 Matus Valo -# Copyright (c) 2021 Lorena B <46202743+lorena-b@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Some functions that may be useful for various checkers.""" import builtins diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 3580e660e9..b518eb47e7 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1,58 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2009 Mads Kiilerich -# Copyright (c) 2010 Daniel Harding -# Copyright (c) 2011-2014, 2017 Google, Inc. -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Ricardo Gemignani -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Radu Ciorba -# Copyright (c) 2015 Simu Toni -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016, 2018-2019 Ashley Whetter -# Copyright (c) 2016, 2018 Jakub Wilk -# Copyright (c) 2016-2017 Derek Gustafson -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016 Grant Welch -# Copyright (c) 2017-2018, 2021 Ville Skyttä -# Copyright (c) 2017-2018, 2020 hippo91 -# Copyright (c) 2017 Dan Garrette -# Copyright (c) 2018-2019 Jim Robertson -# Copyright (c) 2018 Mike Miller -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Drew -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Marianna Polatoglou -# Copyright (c) 2018 mar-chi-pan -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019, 2021 Nick Drozd -# Copyright (c) 2019 Djailla -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Andrew Simmons -# Copyright (c) 2020 Andrew Simmons -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 Ashley Whetter -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 David Liu -# Copyright (c) 2021 kasium <15907922+kasium@users.noreply.github.com> -# Copyright (c) 2021 Marcin Kurczewski -# Copyright (c) 2021 Sergei Lebedev <185856+superbobry@users.noreply.github.com> -# Copyright (c) 2021 Lorena B <46202743+lorena-b@users.noreply.github.com> -# Copyright (c) 2021 haasea <44787650+haasea@users.noreply.github.com> -# Copyright (c) 2021 Alexander Kapshuna - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Variables checkers for Python code.""" import collections diff --git a/pylint/config/__init__.py b/pylint/config/__init__.py index 2e378660fe..c4582ee002 100644 --- a/pylint/config/__init__.py +++ b/pylint/config/__init__.py @@ -1,38 +1,6 @@ -# Copyright (c) 2006-2010, 2012-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2008 pyves@crater.logilab.fr -# Copyright (c) 2013 Google, Inc. -# Copyright (c) 2013 John McGehee -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Aru Sahni -# Copyright (c) 2015 John Kirkham -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Erik -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2017-2019 Ville Skyttä -# Copyright (c) 2017 ahirnish -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 Jim Robertson -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Gary Tyler McLeod -# Copyright (c) 2018 Konstantin -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019, 2021 Ashley Whetter -# Copyright (c) 2019 Janne Rönkkö -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Eisuke Kawashima -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os import pathlib diff --git a/pylint/config/config_initialization.py b/pylint/config/config_initialization.py index d038220302..fd36e7c6f1 100644 --- a/pylint/config/config_initialization.py +++ b/pylint/config/config_initialization.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import sys from pathlib import Path diff --git a/pylint/config/configuration_mixin.py b/pylint/config/configuration_mixin.py index a2abcb7528..bb1115ad76 100644 --- a/pylint/config/configuration_mixin.py +++ b/pylint/config/configuration_mixin.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from pylint.config.option_manager_mixin import OptionsManagerMixIn from pylint.config.options_provider_mixin import OptionsProviderMixIn diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index 73a7f0f8a5..d2461a6251 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import configparser import os diff --git a/pylint/config/man_help_formatter.py b/pylint/config/man_help_formatter.py index c00f45468e..5531fec03b 100644 --- a/pylint/config/man_help_formatter.py +++ b/pylint/config/man_help_formatter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import optparse # pylint: disable=deprecated-module import sys diff --git a/pylint/config/option.py b/pylint/config/option.py index cf88e3fc90..5667a14ccf 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import copy import optparse # pylint: disable=deprecated-module diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 387c628235..346533ab60 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections diff --git a/pylint/config/option_parser.py b/pylint/config/option_parser.py index 66b5737234..ad512329bf 100644 --- a/pylint/config/option_parser.py +++ b/pylint/config/option_parser.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import optparse # pylint: disable=deprecated-module diff --git a/pylint/config/options_provider_mixin.py b/pylint/config/options_provider_mixin.py index 8c6204586a..ae467c30c6 100644 --- a/pylint/config/options_provider_mixin.py +++ b/pylint/config/options_provider_mixin.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import optparse # pylint: disable=deprecated-module diff --git a/pylint/constants.py b/pylint/constants.py index ae87d4293d..94e0636bb5 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import platform import sys from typing import Dict, List, NamedTuple, Tuple diff --git a/pylint/epylint.py b/pylint/epylint.py index 223f723a77..d02ce6d5dc 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -1,32 +1,9 @@ # mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 # -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 -# Copyright (c) 2008-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Jakob Normark -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Manuel Vázquez Acosta -# Copyright (c) 2014 Derek Harland -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Mihai Balint -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2017 Daniela Plascencia -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Ryan McGuire -# Copyright (c) 2018 thernstig <30827238+thernstig@users.noreply.github.com> -# Copyright (c) 2018 Radostin Stoyanov -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Emacs and Flymake compatible Pylint. diff --git a/pylint/exceptions.py b/pylint/exceptions.py index 5999dcfd4a..61b5bb07de 100644 --- a/pylint/exceptions.py +++ b/pylint/exceptions.py @@ -1,16 +1,6 @@ -# Copyright (c) 2016-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019 Thomas Hisch -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Ashley Whetter -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Exception classes raised by various operations within pylint.""" diff --git a/pylint/extensions/__init__.py b/pylint/extensions/__init__.py index 4af3aefb17..467608e4ec 100644 --- a/pylint/extensions/__init__.py +++ b/pylint/extensions/__init__.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index ffda42cd37..ff01ef226d 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -1,26 +1,6 @@ -# Copyright (c) 2016-2019, 2021 Ashley Whetter -# Copyright (c) 2016-2020 Claudiu Popa -# Copyright (c) 2016 Yuri Bochkarev -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2017 Mitar -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 Jim Robertson -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Mitchell T.H. Young -# Copyright (c) 2018 Adrian Chirieac -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Danny Hermes -# Copyright (c) 2019 Zeb Nicholls -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 allanc65 <95424144+allanc65@users.noreply.github.com> -# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Utility methods for docstring checking.""" diff --git a/pylint/extensions/bad_builtin.py b/pylint/extensions/bad_builtin.py index bf80722442..62fe6691c2 100644 --- a/pylint/extensions/bad_builtin.py +++ b/pylint/extensions/bad_builtin.py @@ -1,14 +1,6 @@ -# Copyright (c) 2016, 2018, 2020 Claudiu Popa -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checker for deprecated builtins.""" from typing import TYPE_CHECKING diff --git a/pylint/extensions/broad_try_clause.py b/pylint/extensions/broad_try_clause.py index a45dc5fe6b..e51106987b 100644 --- a/pylint/extensions/broad_try_clause.py +++ b/pylint/extensions/broad_try_clause.py @@ -1,13 +1,6 @@ -# Copyright (c) 2019-2020 Claudiu Popa -# Copyright (c) 2019-2020 Tyler Thieding -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Looks for try/except statements with too much code in the try clause.""" from typing import TYPE_CHECKING, Union diff --git a/pylint/extensions/check_elif.py b/pylint/extensions/check_elif.py index 0563d5170c..41d737f5f0 100644 --- a/pylint/extensions/check_elif.py +++ b/pylint/extensions/check_elif.py @@ -1,16 +1,6 @@ -# Copyright (c) 2015 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2016-2020 Claudiu Popa -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 bot -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py index 0d0865a133..37304b93ee 100644 --- a/pylint/extensions/comparetozero.py +++ b/pylint/extensions/comparetozero.py @@ -1,14 +1,6 @@ -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2017-2018, 2020 Claudiu Popa -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 bernie gray - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Looks for comparisons to zero.""" diff --git a/pylint/extensions/comparison_placement.py b/pylint/extensions/comparison_placement.py index f34f1eb821..a85ea91fb6 100644 --- a/pylint/extensions/comparison_placement.py +++ b/pylint/extensions/comparison_placement.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Checks for yoda comparisons (variable before constant) See https://en.wikipedia.org/wiki/Yoda_conditions diff --git a/pylint/extensions/confusing_elif.py b/pylint/extensions/confusing_elif.py index 99588d0b8d..b6eee6ef14 100644 --- a/pylint/extensions/confusing_elif.py +++ b/pylint/extensions/confusing_elif.py @@ -1,11 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Ashley Whetter -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 8d60b11881..ff3a0c641d 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -1,28 +1,6 @@ -# Copyright (c) 2014-2015 Bruno Daniel -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2016-2019 Ashley Whetter -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017 Ville Skyttä -# Copyright (c) 2017 John Paraskevopoulos -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 Jim Robertson -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Adam Dangoor -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Luigi -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Konstantina Saketou <56515303+ksaketou@users.noreply.github.com> -# Copyright (c) 2021 SupImDos <62866982+SupImDos@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Logan Miller <14319179+komodo472@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings.""" import re diff --git a/pylint/extensions/docstyle.py b/pylint/extensions/docstyle.py index e520d0650c..8823d61e57 100644 --- a/pylint/extensions/docstyle.py +++ b/pylint/extensions/docstyle.py @@ -1,14 +1,6 @@ -# Copyright (c) 2016-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Łukasz Rogalski -# Copyright (c) 2016 Luis Escobar -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import linecache from typing import TYPE_CHECKING diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py index 096b96ec90..30a8212257 100644 --- a/pylint/extensions/emptystring.py +++ b/pylint/extensions/emptystring.py @@ -1,14 +1,6 @@ -# Copyright (c) 2016 Alexander Todorov -# Copyright (c) 2017-2018, 2020 Claudiu Popa -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Looks for comparisons to empty string.""" diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py index b84865decf..6b3e4080d6 100644 --- a/pylint/extensions/mccabe.py +++ b/pylint/extensions/mccabe.py @@ -1,15 +1,6 @@ -# Copyright (c) 2016-2020 Claudiu Popa -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Ville Skyttä -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Module to add McCabe checker class for pylint.""" diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index c3bf9aab2e..771a9a499c 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Looks for overlapping exceptions.""" diff --git a/pylint/extensions/redefined_variable_type.py b/pylint/extensions/redefined_variable_type.py index 527418bf53..65e666f213 100644 --- a/pylint/extensions/redefined_variable_type.py +++ b/pylint/extensions/redefined_variable_type.py @@ -1,15 +1,6 @@ -# Copyright (c) 2016-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING, List diff --git a/pylint/graph.py b/pylint/graph.py index c35cf8c5da..07e3aa549c 100644 --- a/pylint/graph.py +++ b/pylint/graph.py @@ -1,20 +1,6 @@ -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2016 Ashley Whetter -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Nick Drozd -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 谭九鼎 <109224573@qq.com> -# Copyright (c) 2020 Benjamin Graham -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Andrew Howe - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Graph manipulation utilities. diff --git a/pylint/interfaces.py b/pylint/interfaces.py index e2b4b44220..fe48d4daa2 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -1,21 +1,6 @@ -# Copyright (c) 2009-2010, 2012-2013 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Interfaces for Pylint objects.""" from collections import namedtuple diff --git a/pylint/lint/__init__.py b/pylint/lint/__init__.py index ae3c56c890..b110728658 100644 --- a/pylint/lint/__init__.py +++ b/pylint/lint/__init__.py @@ -1,63 +1,6 @@ -# Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2008 Fabrice Douchant -# Copyright (c) 2009 Vincent -# Copyright (c) 2009 Mads Kiilerich -# Copyright (c) 2011-2014 Google, Inc. -# Copyright (c) 2012 David Pursehouse -# Copyright (c) 2012 Kevin Jing Qiu -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2012 JT Olds -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014-2015 Michal Nowikowski -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Alexandru Coman -# Copyright (c) 2014 Daniel Harding -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2014 Dan Goldsmith -# Copyright (c) 2015-2016 Florian Bruhin -# Copyright (c) 2015 Aru Sahni -# Copyright (c) 2015 Steven Myint -# Copyright (c) 2015 Simu Toni -# Copyright (c) 2015 Mihai Balint -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017 Łukasz Rogalski -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Alan Evangelista -# Copyright (c) 2017-2019 hippo91 -# Copyright (c) 2017-2018 Ville Skyttä -# Copyright (c) 2017 Daniel Miller -# Copyright (c) 2017 Roman Ivanov -# Copyright (c) 2017 Ned Batchelder -# Copyright (c) 2018-2021 Pierre Sassoulas -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018-2019 Nick Drozd -# Copyright (c) 2018 Matus Valo -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Randall Leeds -# Copyright (c) 2018 Mike Frysinger -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Jason Owen -# Copyright (c) 2018 Gary Tyler McLeod -# Copyright (c) 2018 Yuval Langer -# Copyright (c) 2018 kapsh -# Copyright (c) 2019 syutbai -# Copyright (c) 2019 Thomas Hisch -# Copyright (c) 2019 Hugues -# Copyright (c) 2019 Janne Rönkkö -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2019 Trevor Bekolay -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Robert Schweizer -# Copyright (c) 2019 Andres Perez Hortal -# Copyright (c) 2019 Peter Kolbus -# Copyright (c) 2019 Nicolas Dickreuter -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2020 anubh-v -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Pylint [options] modules_or_packages. diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index 99c3714931..5cf0b0b67f 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections import functools diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 9f94da292d..248df9d06d 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections import contextlib diff --git a/pylint/lint/report_functions.py b/pylint/lint/report_functions.py index 0f5e9360db..052c564a7f 100644 --- a/pylint/lint/report_functions.py +++ b/pylint/lint/report_functions.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections from typing import DefaultDict, Dict, Union diff --git a/pylint/lint/run.py b/pylint/lint/run.py index b719051844..28e45dde66 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os import sys diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index 410269e9d9..4a1a375703 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import contextlib import sys diff --git a/pylint/message/__init__.py b/pylint/message/__init__.py index 68159bf342..1238c127ae 100644 --- a/pylint/message/__init__.py +++ b/pylint/message/__init__.py @@ -1,42 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2009 Vincent -# Copyright (c) 2009 Mads Kiilerich -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014-2015 Michal Nowikowski -# Copyright (c) 2014 LCD 47 -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2014 Damien Nozay -# Copyright (c) 2015 Aru Sahni -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Simu Toni -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Łukasz Rogalski -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Ashley Whetter -# Copyright (c) 2016 xmo-odoo -# Copyright (c) 2017-2019, 2021 Pierre Sassoulas -# Copyright (c) 2017-2018, 2020 hippo91 -# Copyright (c) 2017, 2020 Anthony Sottile -# Copyright (c) 2017-2018 Bryce Guinta -# Copyright (c) 2017 Chris Lamb -# Copyright (c) 2017 Thomas Hisch -# Copyright (c) 2017 Mikhail Fesenko -# Copyright (c) 2017 Craig Citro -# Copyright (c) 2017 Ville Skyttä -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Reverb C -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """All the classes related to Message handling.""" diff --git a/pylint/message/message.py b/pylint/message/message.py index 8a12d84b84..79db438784 100644 --- a/pylint/message/message.py +++ b/pylint/message/message.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections diff --git a/pylint/message/message_definition.py b/pylint/message/message_definition.py index a286f5a6d5..ea65a32b88 100644 --- a/pylint/message/message_definition.py +++ b/pylint/message/message_definition.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import sys from typing import TYPE_CHECKING, List, Optional, Tuple diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py index c8b4e166a0..643bc26a93 100644 --- a/pylint/message/message_definition_store.py +++ b/pylint/message/message_definition_store.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections import functools diff --git a/pylint/message/message_id_store.py b/pylint/message/message_id_store.py index e585be5a06..b45d074c65 100644 --- a/pylint/message/message_id_store.py +++ b/pylint/message/message_id_store.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import Dict, List, NoReturn, Optional, Tuple from pylint.exceptions import InvalidMessageError, UnknownMessageError diff --git a/pylint/pyreverse/__init__.py b/pylint/pyreverse/__init__.py index cce5caec3a..f0ca20a7bf 100644 --- a/pylint/pyreverse/__init__.py +++ b/pylint/pyreverse/__init__.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Pyreverse.extensions.""" diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index 6fa8ea107b..50b9ad9b31 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -1,23 +1,6 @@ -# Copyright (c) 2006, 2008-2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Ashley Whetter -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 bot -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Handle diagram generation options for class diagram or default diagrams.""" diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index fa88e38165..c2699f7674 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -1,20 +1,6 @@ -# Copyright (c) 2006, 2008-2010, 2012-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Takahide Nojima -# Copyright (c) 2021 bot -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Diagram objects.""" import astroid diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index 21f41d765c..841d96fdca 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -1,12 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Ashley Whetter -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Class to generate files in dot format and image formats supported by Graphviz.""" import os diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 808f34266c..7ef3f7bb64 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -1,18 +1,6 @@ -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Visitor doing some postprocessing on the astroid tree. diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index aefffb315d..9efe9ee4d7 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -1,24 +1,6 @@ -# Copyright (c) 2008-2010, 2012-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Alexander Pervakov -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Antonio Quarta -# Copyright (c) 2021 Tushar Sadhwani -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 bot -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """%prog [options] . diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index a725d03f4c..b0452d2882 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -1,7 +1,6 @@ -# Copyright (c) 2021 Antonio Quarta - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Class to generate files in mermaidjs format.""" from typing import Dict, Optional diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index 2e643fe1fe..8674aead73 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -1,7 +1,6 @@ -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Class to generate files in dot format and image formats supported by Graphviz.""" from typing import Dict, Optional diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index 6f4d62f364..346579719a 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -1,11 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Ashley Whetter -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Base class defining the interface for a printer.""" from abc import ABC, abstractmethod diff --git a/pylint/pyreverse/printer_factory.py b/pylint/pyreverse/printer_factory.py index bdcaf0869d..27eb7f7ab8 100644 --- a/pylint/pyreverse/printer_factory.py +++ b/pylint/pyreverse/printer_factory.py @@ -1,9 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import Dict, Type diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index f7eff0ee0b..d18efe32ee 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -1,23 +1,6 @@ -# Copyright (c) 2006, 2008, 2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2020 yeting li -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 bernie gray -# Copyright (c) 2021 bot -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Generic classes/functions for pyreverse core/extensions.""" import os diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index 52e99a300a..3a4d25edcf 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -1,17 +1,6 @@ -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2018 ssolanki -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Ram Rachum -# Copyright (c) 2020 谭九鼎 <109224573@qq.com> -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Functions to generate files readable with Georg Sander's vcg (Visualization of Compiler Graphs). diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 86a03ac7e6..a45394ac76 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -1,20 +1,6 @@ -# Copyright (c) 2008-2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Mike Frysinger -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 ssolanki -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Kylian -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Utilities for creating VCG and Dot diagrams.""" diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 92b6bae21e..2420a03abf 100644 --- a/pylint/reporters/__init__.py +++ b/pylint/reporters/__init__.py @@ -1,25 +1,6 @@ -# Copyright (c) 2006, 2010, 2012-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Ricardo Gemignani -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Simu Toni -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2017 Kári Tristan Helgason -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 ruro - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Utilities methods and classes for reporters.""" from typing import TYPE_CHECKING diff --git a/pylint/reporters/base_reporter.py b/pylint/reporters/base_reporter.py index 570175567b..fc7422370f 100644 --- a/pylint/reporters/base_reporter.py +++ b/pylint/reporters/base_reporter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os import sys diff --git a/pylint/reporters/collecting_reporter.py b/pylint/reporters/collecting_reporter.py index 9b787342af..2ee041b01c 100644 --- a/pylint/reporters/collecting_reporter.py +++ b/pylint/reporters/collecting_reporter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import TYPE_CHECKING from pylint.reporters.base_reporter import BaseReporter diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index 5c20747735..cb7418fbbc 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -1,16 +1,6 @@ -# Copyright (c) 2014 Vlad Temian -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2017 guillaume2 -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Clément Pit-Claudel -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """JSON reporter.""" import json diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index c2c7382d29..63b65eb0c4 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index 01d9947fd7..f6486c95dd 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections from typing import ( diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 2e1abf6bfa..09a113c415 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -1,23 +1,6 @@ -# Copyright (c) 2006-2007, 2010-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 y2kbugger -# Copyright (c) 2018-2019 Nick Drozd -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Jace Browning -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 bot - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Plain text reporters:. diff --git a/pylint/reporters/ureports/__init__.py b/pylint/reporters/ureports/__init__.py index 29bc85d894..51e490b4da 100644 --- a/pylint/reporters/ureports/__init__.py +++ b/pylint/reporters/ureports/__init__.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors __all__ = ("BaseWriter",) diff --git a/pylint/reporters/ureports/base_writer.py b/pylint/reporters/ureports/base_writer.py index dba57c86fe..38eba6227e 100644 --- a/pylint/reporters/ureports/base_writer.py +++ b/pylint/reporters/ureports/base_writer.py @@ -1,15 +1,6 @@ -# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Anthony Sottile -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Nick Drozd -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Universal report objects and some formatting drivers. diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index a4e32f81e3..f7d6f89c90 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -1,15 +1,6 @@ -# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Micro reports objects. diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py index cb80e67713..b247af4a02 100644 --- a/pylint/reporters/ureports/text_writer.py +++ b/pylint/reporters/ureports/text_writer.py @@ -1,14 +1,6 @@ -# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 bot - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Text formatting drivers for ureports.""" diff --git a/pylint/testutils/__init__.py b/pylint/testutils/__init__.py index 2b392d0584..a7ac8680c9 100644 --- a/pylint/testutils/__init__.py +++ b/pylint/testutils/__init__.py @@ -1,32 +1,6 @@ -# Copyright (c) 2012-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2018, 2020 Claudiu Popa -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2013 buck@yelp.com -# Copyright (c) 2014 LCD 47 -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Ricardo Gemignani -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Pavel Roskin -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 Roy Williams -# Copyright (c) 2016 xmo-odoo -# Copyright (c) 2017 Bryce Guinta -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Mr. Senko -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 谭九鼎 <109224573@qq.com> -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Lefteris Karapetsas - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Functional/non regression tests for pylint.""" diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index 739db50810..84df31e049 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import contextlib import warnings diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index b699f32428..591dc57bd8 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Utility functions for configuration testing.""" import copy diff --git a/pylint/testutils/constants.py b/pylint/testutils/constants.py index c46da393ce..305b367538 100644 --- a/pylint/testutils/constants.py +++ b/pylint/testutils/constants.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import operator import re diff --git a/pylint/testutils/decorator.py b/pylint/testutils/decorator.py index 4cee70302f..efa572b59f 100644 --- a/pylint/testutils/decorator.py +++ b/pylint/testutils/decorator.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import functools import optparse # pylint: disable=deprecated-module diff --git a/pylint/testutils/functional/__init__.py b/pylint/testutils/functional/__init__.py index 607c22cb45..d9df9d338b 100644 --- a/pylint/testutils/functional/__init__.py +++ b/pylint/testutils/functional/__init__.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors __all__ = [ "FunctionalTestFile", diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py index cddf0ade73..6fbdded121 100644 --- a/pylint/testutils/functional/find_functional_tests.py +++ b/pylint/testutils/functional/find_functional_tests.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os from pathlib import Path diff --git a/pylint/testutils/functional/lint_module_output_update.py b/pylint/testutils/functional/lint_module_output_update.py index 0bd46fc0bf..1f5fc8392c 100644 --- a/pylint/testutils/functional/lint_module_output_update.py +++ b/pylint/testutils/functional/lint_module_output_update.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import csv import os diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index 1ebc30d0e0..aaaa7e8a9e 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import configparser import sys diff --git a/pylint/testutils/functional_test_file.py b/pylint/testutils/functional_test_file.py index cb13cd21d8..1a3c07eff8 100644 --- a/pylint/testutils/functional_test_file.py +++ b/pylint/testutils/functional_test_file.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors __all__ = [ "FunctionalTestFile", diff --git a/pylint/testutils/get_test_info.py b/pylint/testutils/get_test_info.py index 32498b8ba2..7a8bb9f92a 100644 --- a/pylint/testutils/get_test_info.py +++ b/pylint/testutils/get_test_info.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from glob import glob from os.path import basename, join, splitext diff --git a/pylint/testutils/global_test_linter.py b/pylint/testutils/global_test_linter.py index 2e6028e302..57506491d6 100644 --- a/pylint/testutils/global_test_linter.py +++ b/pylint/testutils/global_test_linter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from pylint import checkers diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index ed20ed8604..19459a0bb2 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import csv import operator diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index 70dc01f383..62ba9b039d 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import warnings from typing import Any, NamedTuple, Optional, Sequence, Tuple, TypeVar, Union diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index 34d66d2177..b4e83f5d2b 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import List, Optional, Tuple diff --git a/pylint/testutils/reporter_for_tests.py b/pylint/testutils/reporter_for_tests.py index 0c2b456f30..0610e6594e 100644 --- a/pylint/testutils/reporter_for_tests.py +++ b/pylint/testutils/reporter_for_tests.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from io import StringIO from os import getcwd, sep diff --git a/pylint/testutils/tokenize_str.py b/pylint/testutils/tokenize_str.py index 2c4f31acb5..5ce152e6ee 100644 --- a/pylint/testutils/tokenize_str.py +++ b/pylint/testutils/tokenize_str.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import tokenize from io import StringIO diff --git a/pylint/testutils/unittest_linter.py b/pylint/testutils/unittest_linter.py index 37d430fd2b..51f60c7342 100644 --- a/pylint/testutils/unittest_linter.py +++ b/pylint/testutils/unittest_linter.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import Any, Optional diff --git a/pylint/typing.py b/pylint/typing.py index 84c49df558..84488987ac 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """A collection of typing utilities.""" import sys diff --git a/pylint/utils/__init__.py b/pylint/utils/__init__.py index 53d07825ec..7406f7ec6e 100644 --- a/pylint/utils/__init__.py +++ b/pylint/utils/__init__.py @@ -1,44 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2009 Vincent -# Copyright (c) 2009 Mads Kiilerich -# Copyright (c) 2012-2014 Google, Inc. -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014-2015 Michal Nowikowski -# Copyright (c) 2014 LCD 47 -# Copyright (c) 2014 Brett Cannon -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2014 Damien Nozay -# Copyright (c) 2015 Aru Sahni -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Simu Toni -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Łukasz Rogalski -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Ashley Whetter -# Copyright (c) 2016 xmo-odoo -# Copyright (c) 2017-2021 Pierre Sassoulas -# Copyright (c) 2017-2018, 2020-2021 hippo91 -# Copyright (c) 2017, 2020 Anthony Sottile -# Copyright (c) 2017-2018 Bryce Guinta -# Copyright (c) 2017 Chris Lamb -# Copyright (c) 2017 Thomas Hisch -# Copyright (c) 2017 Mikhail Fesenko -# Copyright (c) 2017 Craig Citro -# Copyright (c) 2017 Ville Skyttä -# Copyright (c) 2018 ssolanki -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Reverb C -# Copyright (c) 2018 Nick Drozd -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Some various utilities and helper classes, most of them used in the main pylint class diff --git a/pylint/utils/ast_walker.py b/pylint/utils/ast_walker.py index f9e573a95b..08fc3af925 100644 --- a/pylint/utils/ast_walker.py +++ b/pylint/utils/ast_walker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections import traceback diff --git a/pylint/utils/docs.py b/pylint/utils/docs.py index 848a498684..22412b803c 100644 --- a/pylint/utils/docs.py +++ b/pylint/utils/docs.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Various helper functions to create the docs of a linter object.""" import sys diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index 30e91c3ea1..b7f09a6b9c 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import collections import sys diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py index 54b98d533e..e09e0ce432 100644 --- a/pylint/utils/linterstats.py +++ b/pylint/utils/linterstats.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import sys from typing import Dict, List, Optional, Set, cast diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index e09cba7c9e..090a336fc5 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import re from collections import namedtuple diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 14f57a16c1..835351ec37 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors try: diff --git a/tbump.toml b/tbump.toml index 6744a745ed..2116fdb7cc 100644 --- a/tbump.toml +++ b/tbump.toml @@ -40,10 +40,6 @@ cmd = "contributors-txt-normalize-configuration -a script/.contributors_aliases. name = "Upgrade the contributors list" cmd = "python3 script/create_contributor_list.py" -[[before_commit]] -name = "Upgrade copyrights" -cmd = "pip3 install copyrite;copyrite --contribution-threshold 1 --change-threshold 3 --backend-type git --aliases=.copyrite_aliases . --jobs=8" - [[before_commit]] name = "Apply pre-commit" cmd = "pre-commit run --all-files||echo 'Hack so this command does not fail'" diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index 5617996dfd..b4550db431 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -1,14 +1,8 @@ """Profiles basic -jX functionality.""" -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Claudiu Popa -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Ville Skyttä -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors # pylint: disable=missing-function-docstring diff --git a/tests/checkers/unittest_base.py b/tests/checkers/unittest_base.py index c1c76610ca..b0238c3eae 100644 --- a/tests/checkers/unittest_base.py +++ b/tests/checkers/unittest_base.py @@ -1,29 +1,6 @@ -# Copyright (c) 2013-2015 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 Yannack -# Copyright (c) 2017, 2019-2021 Pierre Sassoulas -# Copyright (c) 2017, 2019 Ville Skyttä -# Copyright (c) 2017 ttenhoeve-aa -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 Fureigh -# Copyright (c) 2018 glmdgrielson <32415403+glmdgrielson@users.noreply.github.com> -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020-2021 hippo91 -# Copyright (c) 2020 ethan-leba -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 bernie gray -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Yilei "Dolee" Yang -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021-2022 Or Bahari -# Copyright (c) 2021 David Gilman - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unittest for the base checker.""" diff --git a/tests/checkers/unittest_base_checker.py b/tests/checkers/unittest_base_checker.py index ba5e8dfcc2..7efdf2aba9 100644 --- a/tests/checkers/unittest_base_checker.py +++ b/tests/checkers/unittest_base_checker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unittest for the BaseChecker class.""" diff --git a/tests/checkers/unittest_design.py b/tests/checkers/unittest_design.py index b60106590e..95baea3b21 100644 --- a/tests/checkers/unittest_design.py +++ b/tests/checkers/unittest_design.py @@ -1,11 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Mike Fiedler -# Copyright (c) 2021 Ashley Whetter -# Copyright (c) 2021 Rebecca Turner - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import astroid diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 30972cb5c7..8c5a2836ce 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -1,30 +1,6 @@ -# Copyright (c) 2009-2011, 2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 buck -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Harut -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Petr Pulc -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017 Krzysztof Czapla -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2017 James M. Allen -# Copyright (c) 2017 vinnyrose -# Copyright (c) 2018, 2020 Bryce Guinta -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Check format checker helper functions.""" diff --git a/tests/checkers/unittest_imports.py b/tests/checkers/unittest_imports.py index c5285d2598..7f1181339e 100644 --- a/tests/checkers/unittest_imports.py +++ b/tests/checkers/unittest_imports.py @@ -1,21 +1,6 @@ -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Cezar -# Copyright (c) 2015 James Morgensen -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Hornwitser -# Copyright (c) 2018 Marianna Polatoglou -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Nick Drozd -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit tests for the imports checker.""" diff --git a/tests/checkers/unittest_misc.py b/tests/checkers/unittest_misc.py index bd15a932ed..9c0ec70895 100644 --- a/tests/checkers/unittest_misc.py +++ b/tests/checkers/unittest_misc.py @@ -1,20 +1,6 @@ -# Copyright (c) 2013-2014, 2016-2020 Claudiu Popa -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 glegoux -# Copyright (c) 2018 Rogalski, Lukasz -# Copyright (c) 2018 Anthony Sottile -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Tests for the misc checker.""" diff --git a/tests/checkers/unittest_refactoring.py b/tests/checkers/unittest_refactoring.py index 86ed28a4da..2a28c0bede 100644 --- a/tests/checkers/unittest_refactoring.py +++ b/tests/checkers/unittest_refactoring.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index 2f2b43115e..3911db93bc 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -1,24 +1,6 @@ -# Copyright (c) 2010, 2012, 2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 Ry4an Brase -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Scott Worley -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2019 Taewon D. Kim -# Copyright (c) 2020-2021 hippo91 -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2020 Eli Fine -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Maksym Humetskyi -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Aditya Gupta - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from contextlib import redirect_stdout from io import StringIO diff --git a/tests/checkers/unittest_spelling.py b/tests/checkers/unittest_spelling.py index 838e4755a4..6f7308aaa0 100644 --- a/tests/checkers/unittest_spelling.py +++ b/tests/checkers/unittest_spelling.py @@ -1,22 +1,6 @@ -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017, 2020 Pedro Algarvio -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2019 agutole -# Copyright (c) 2020 Ganden Schaffner -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Eli Fine - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unittest for the spelling checker.""" diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py index 890fa7a8a9..480da89921 100644 --- a/tests/checkers/unittest_stdlib.py +++ b/tests/checkers/unittest_stdlib.py @@ -1,15 +1,6 @@ -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Cezar -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017 Martin -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import contextlib from typing import Any, Callable, Iterator, Optional, Union diff --git a/tests/checkers/unittest_strings.py b/tests/checkers/unittest_strings.py index 9f2d378016..529a9284fc 100644 --- a/tests/checkers/unittest_strings.py +++ b/tests/checkers/unittest_strings.py @@ -1,15 +1,6 @@ -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Yury Gribov -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from pylint.checkers import strings diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index 4564a461ef..736a16ca20 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -1,29 +1,6 @@ -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Holger Peters -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Dmitry Pribysh -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 Filipe Brandenburger -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2019 Martin Vielsmaier -# Copyright (c) 2019 Federico Bond -# Copyright (c) 2020 Julien Palard -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 David Liu -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 David Gilman - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import astroid import pytest diff --git a/tests/checkers/unittest_utils.py b/tests/checkers/unittest_utils.py index 1b916b98a6..e27443f26d 100644 --- a/tests/checkers/unittest_utils.py +++ b/tests/checkers/unittest_utils.py @@ -1,22 +1,6 @@ -# Copyright (c) 2010 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2013-2020 Claudiu Popa -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Alan Chan -# Copyright (c) 2018 Caio Carrara -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2019 Nathan Marrow -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Tests for the pylint.checkers.utils module.""" diff --git a/tests/checkers/unittest_variables.py b/tests/checkers/unittest_variables.py index f42c3fbae6..e9ebbd6137 100644 --- a/tests/checkers/unittest_variables.py +++ b/tests/checkers/unittest_variables.py @@ -1,24 +1,6 @@ -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017 Ville Skyttä -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 Bryce Guinta -# Copyright (c) 2018 mar-chi-pan -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Andrew Simmons -# Copyright (c) 2020 Andrew Simmons -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Sergei Lebedev <185856+superbobry@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import os import re diff --git a/tests/config/test_functional_config_loading.py b/tests/config/test_functional_config_loading.py index 1937435f73..174af48b89 100644 --- a/tests/config/test_functional_config_loading.py +++ b/tests/config/test_functional_config_loading.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """This launches the configuration functional tests. This permits to test configuration files by providing a file with the appropriate extension in the ``tests/config/functional`` diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py index fd56a3f928..16e12350e3 100644 --- a/tests/config/unittest_config.py +++ b/tests/config/unittest_config.py @@ -1,16 +1,6 @@ -# Copyright (c) 2015 Aru Sahni -# Copyright (c) 2016-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit tests for the config module.""" diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py index 2fa5719fd5..abc0751d82 100644 --- a/tests/extensions/test_check_docs_utils.py +++ b/tests/extensions/test_check_docs_utils.py @@ -1,16 +1,6 @@ -# Copyright (c) 2016-2018, 2020 Claudiu Popa -# Copyright (c) 2016, 2019 Ashley Whetter -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Anthony Sottile -# Copyright (c) 2019, 2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit tests for utils functions in :mod:`pylint.extensions._check_docs_utils`.""" import astroid diff --git a/tests/input/similar_lines_a.py b/tests/input/similar_lines_a.py index 65a72a79d5..bbd13b5f37 100644 --- a/tests/input/similar_lines_a.py +++ b/tests/input/similar_lines_a.py @@ -1,7 +1,6 @@ """ A file designed to have lines of similarity when compared to similar_lines_b We use lorm-ipsum to generate 'random' code. """ -# Copyright (c) 2020 Frank Harrison def adipiscing(elit): diff --git a/tests/input/similar_lines_b.py b/tests/input/similar_lines_b.py index 21634883d8..fbca78fb9b 100644 --- a/tests/input/similar_lines_b.py +++ b/tests/input/similar_lines_b.py @@ -2,7 +2,6 @@ similarity when compared to its sister file As with the sister file, we use lorm-ipsum to generate 'random' code. """ -# Copyright (c) 2020 Frank Harrison class Nulla: diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index bb00a67ace..6c0855e9d4 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import re diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 9dd6ae4dec..04b94d4e8e 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -1,41 +1,7 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2011-2014 Google, Inc. -# Copyright (c) 2012 Kevin Jing Qiu -# Copyright (c) 2012 Anthony VEREZ -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Florian Bruhin -# Copyright (c) 2015 Noam Yorav-Raphael -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017 Derek Gustafson -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2017-2021 Pierre Sassoulas -# Copyright (c) 2017, 2021 Ville Skyttä -# Copyright (c) 2017 Craig Citro -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2018, 2020 Anthony Sottile -# Copyright (c) 2018 Matus Valo -# Copyright (c) 2018 Scott Worley -# Copyright (c) 2018 Randall Leeds -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Reverb C -# Copyright (c) 2019 Janne Rönkkö -# Copyright (c) 2019 Trevor Bekolay -# Copyright (c) 2019 Andres Perez Hortal -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 Martin Vielsmaier -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Michal Vasilek -# Copyright (c) 2021 Eisuke Kawashima -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors + # pylint: disable=redefined-outer-name import os diff --git a/tests/message/conftest.py b/tests/message/conftest.py index e30dbb5355..aee6f50194 100644 --- a/tests/message/conftest.py +++ b/tests/message/conftest.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors # pylint: disable=redefined-outer-name diff --git a/tests/message/test_no_removed_msgid_or_symbol_used.py b/tests/message/test_no_removed_msgid_or_symbol_used.py index c6ece36794..d829c2943a 100644 --- a/tests/message/test_no_removed_msgid_or_symbol_used.py +++ b/tests/message/test_no_removed_msgid_or_symbol_used.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from pylint.constants import DELETED_MESSAGES from pylint.lint import PyLinter diff --git a/tests/message/unittest_message.py b/tests/message/unittest_message.py index d402e1d330..f8397cc3cb 100644 --- a/tests/message/unittest_message.py +++ b/tests/message/unittest_message.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import ValuesView diff --git a/tests/message/unittest_message_definition.py b/tests/message/unittest_message_definition.py index 2015c21549..51211b8ba2 100644 --- a/tests/message/unittest_message_definition.py +++ b/tests/message/unittest_message_definition.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import sys from unittest import mock diff --git a/tests/message/unittest_message_definition_store.py b/tests/message/unittest_message_definition_store.py index e13163080a..96756ce866 100644 --- a/tests/message/unittest_message_definition_store.py +++ b/tests/message/unittest_message_definition_store.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from contextlib import redirect_stdout from io import StringIO diff --git a/tests/message/unittest_message_id_store.py b/tests/message/unittest_message_id_store.py index b44e33c6db..4be49b1826 100644 --- a/tests/message/unittest_message_id_store.py +++ b/tests/message/unittest_message_id_store.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from pathlib import Path from typing import Dict, ValuesView diff --git a/tests/primer/test_primer_external.py b/tests/primer/test_primer_external.py index 193eabf1d8..c9ec9f167d 100644 --- a/tests/primer/test_primer_external.py +++ b/tests/primer/test_primer_external.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import json import logging import subprocess diff --git a/tests/primer/test_primer_stdlib.py b/tests/primer/test_primer_stdlib.py index 824c1feac9..cb84437fbd 100644 --- a/tests/primer/test_primer_stdlib.py +++ b/tests/primer/test_primer_stdlib.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import contextlib import io diff --git a/tests/profile/test_profile_against_externals.py b/tests/profile/test_profile_against_externals.py index 7be4930e82..643896623e 100644 --- a/tests/profile/test_profile_against_externals.py +++ b/tests/profile/test_profile_against_externals.py @@ -1,11 +1,8 @@ """Profiles basic -jX functionality.""" -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors # pylint: disable=missing-function-docstring diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index d9a3589360..1a43daaa99 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -1,25 +1,6 @@ -# Copyright (c) 2008-2010, 2013 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Takahide Nojima -# Copyright (c) 2021 Ville Skyttä -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 bot - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit test for the extensions.diadefslib modules.""" # pylint: disable=redefined-outer-name diff --git a/tests/pyreverse/test_diagrams.py b/tests/pyreverse/test_diagrams.py index b307a37d8e..6bf0ab7064 100644 --- a/tests/pyreverse/test_diagrams.py +++ b/tests/pyreverse/test_diagrams.py @@ -1,9 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Takahide Nojima -# # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit test for the diagrams modules.""" from typing import Callable diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py index 3a4ee423c2..c93e802338 100644 --- a/tests/pyreverse/test_inspector.py +++ b/tests/pyreverse/test_inspector.py @@ -1,17 +1,6 @@ -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Takahide Nojima -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """For the visitors.diadefs module.""" # pylint: disable=redefined-outer-name diff --git a/tests/pyreverse/test_printer.py b/tests/pyreverse/test_printer.py index 5406c6e83f..26faea74f4 100644 --- a/tests/pyreverse/test_printer.py +++ b/tests/pyreverse/test_printer.py @@ -1,9 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from typing import Type diff --git a/tests/pyreverse/test_printer_factory.py b/tests/pyreverse/test_printer_factory.py index 73a5e2670b..1f00559551 100644 --- a/tests/pyreverse/test_printer_factory.py +++ b/tests/pyreverse/test_printer_factory.py @@ -1,7 +1,6 @@ -# Copyright (c) 2021 Andreas Finkler - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit tests for pylint.pyreverse.printer_factory.""" diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index 322b5bea24..095939ad66 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -1,12 +1,6 @@ -# Copyright (c) 2021 Pierre Sassoulas -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Ashley Whetter -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler - # 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 +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Tests for pylint.pyreverse.utils.""" diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 1226182138..3864bd51ee 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -1,21 +1,6 @@ -# Copyright (c) 2008, 2010, 2013 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Unit test for ``DiagramWriter``.""" diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 5bc1a6f279..7011461b2c 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -1,13 +1,8 @@ -"""Puts the check_parallel system under test.""" -# Copyright (c) 2020-2021 Pierre Sassoulas -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2021 Jaehoon Hwang -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Julien Palard -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors + +"""Puts the check_parallel system under test.""" # pylint: disable=protected-access,missing-function-docstring,no-self-use diff --git a/tests/test_func.py b/tests/test_func.py index 492797ec21..a372b4a284 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -1,22 +1,6 @@ -# Copyright (c) 2006-2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017 Michka Popoff -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Functional/non regression tests for pylint.""" diff --git a/tests/test_functional.py b/tests/test_functional.py index 27fda03097..86f8fe73b3 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -1,25 +1,6 @@ -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 Michal Nowikowski -# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Łukasz Rogalski -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Lucas Cimon -# Copyright (c) 2018 Ville Skyttä -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Mr. Senko -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2020 bernie gray -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Functional full-module tests for PyLint.""" import sys diff --git a/tests/test_import_graph.py b/tests/test_import_graph.py index 9a4ea59a63..7dc06307f9 100644 --- a/tests/test_import_graph.py +++ b/tests/test_import_graph.py @@ -1,21 +1,7 @@ -# Copyright (c) 2006-2008, 2010, 2013 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2018 Reverb C -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Andrew Howe - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors + # pylint: disable=redefined-outer-name import os diff --git a/tests/test_numversion.py b/tests/test_numversion.py index 46f154da73..beffc31bb7 100644 --- a/tests/test_numversion.py +++ b/tests/test_numversion.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import pytest diff --git a/tests/test_regr.py b/tests/test_regr.py index 2980057c6e..486c9b8b75 100644 --- a/tests/test_regr.py +++ b/tests/test_regr.py @@ -1,21 +1,6 @@ -# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2012 FELD Boris -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017 Derek Gustafson -# Copyright (c) 2018 Reverb C -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2020 Damien Baty -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Andrew Haigh -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Non regression tests for pylint, which requires a too specific configuration to be incorporated in the automatic functional test framework diff --git a/tests/test_self.py b/tests/test_self.py index 0018b47a9b..f2911ee096 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1,39 +1,6 @@ -# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014-2020 Claudiu Popa -# Copyright (c) 2014 Vlad Temian -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 Moises Lopez -# Copyright (c) 2017, 2019-2021 Pierre Sassoulas -# Copyright (c) 2017, 2021 Ville Skyttä -# Copyright (c) 2017, 2020 hippo91 -# Copyright (c) 2017, 2019 Thomas Hisch -# Copyright (c) 2017 Daniel Miller -# Copyright (c) 2017 Bryce Guinta -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2018 Jason Owen -# Copyright (c) 2018 Jace Browning -# Copyright (c) 2018 Reverb C -# Copyright (c) 2019 Hugues -# Copyright (c) 2019 Hugo van Kemenade -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 Frank Harrison -# Copyright (c) 2020 Matěj Grabovský -# Copyright (c) 2020 Pieter Engelbrecht -# Copyright (c) 2020 Clément Pit-Claudel -# Copyright (c) 2020 Anthony Sottile -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Mark Bell -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Dr. Nick -# Copyright (c) 2021 Andreas Finkler -# Copyright (c) 2021 chohner -# Copyright (c) 2021 Louis Sautier - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors # pylint: disable=too-many-public-methods diff --git a/tests/testutils/test_decorator.py b/tests/testutils/test_decorator.py index 4ab9d04f04..64594b39c3 100644 --- a/tests/testutils/test_decorator.py +++ b/tests/testutils/test_decorator.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import pytest diff --git a/tests/testutils/test_functional_testutils.py b/tests/testutils/test_functional_testutils.py index 032f6ac120..136bda749d 100644 --- a/tests/testutils/test_functional_testutils.py +++ b/tests/testutils/test_functional_testutils.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Tests for the functional test framework.""" diff --git a/tests/testutils/test_lint_module_output_update.py b/tests/testutils/test_lint_module_output_update.py index ad414a9ddb..332463b033 100644 --- a/tests/testutils/test_lint_module_output_update.py +++ b/tests/testutils/test_lint_module_output_update.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors # pylint: disable=redefined-outer-name from pathlib import Path diff --git a/tests/testutils/test_output_line.py b/tests/testutils/test_output_line.py index 48ae107213..6a0e355184 100644 --- a/tests/testutils/test_output_line.py +++ b/tests/testutils/test_output_line.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors # pylint: disable=redefined-outer-name diff --git a/tests/testutils/test_package_to_lint.py b/tests/testutils/test_package_to_lint.py index bf90db3e74..a79faa60c3 100644 --- a/tests/testutils/test_package_to_lint.py +++ b/tests/testutils/test_package_to_lint.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors from pylint.testutils.primer import PRIMER_DIRECTORY_PATH, PackageToLint diff --git a/tests/unittest_reporters_json.py b/tests/unittest_reporters_json.py index be7aadabac..66da977a37 100644 --- a/tests/unittest_reporters_json.py +++ b/tests/unittest_reporters_json.py @@ -1,17 +1,6 @@ -# Copyright (c) 2014 Vlad Temian -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2017 guillaume2 -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors """Test for the JSON reporter.""" diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index d6cb182206..4bbef21151 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -1,20 +1,7 @@ -# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014-2018, 2020 Claudiu Popa -# Copyright (c) 2014 Calin Don -# Copyright (c) 2014 Google, Inc. -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016-2017 Derek Gustafson -# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> -# Copyright (c) 2019-2021 Pierre Sassoulas -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 ruro - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors + # pylint: disable=redefined-outer-name import sys import warnings diff --git a/tests/utils/unittest_ast_walker.py b/tests/utils/unittest_ast_walker.py index 9d350830a1..8f24bdaaa3 100644 --- a/tests/utils/unittest_ast_walker.py +++ b/tests/utils/unittest_ast_walker.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import warnings from typing import Dict, Set diff --git a/tests/utils/unittest_utils.py b/tests/utils/unittest_utils.py index 485756e7da..2f9ed9f4b7 100644 --- a/tests/utils/unittest_utils.py +++ b/tests/utils/unittest_utils.py @@ -1,23 +1,6 @@ -# Copyright (c) 2013-2014 Google, Inc. -# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) -# Copyright (c) 2014 Arun Persaud -# Copyright (c) 2015-2018, 2020 Claudiu Popa -# Copyright (c) 2015 Aru Sahni -# Copyright (c) 2015 Ionel Cristian Maries -# Copyright (c) 2016 Derek Gustafson -# Copyright (c) 2016 Glenn Matthews -# Copyright (c) 2017-2019, 2021 Pierre Sassoulas -# Copyright (c) 2017-2018, 2020 Anthony Sottile -# Copyright (c) 2017 ttenhoeve-aa -# Copyright (c) 2017 Łukasz Rogalski -# Copyright (c) 2019 Ashley Whetter -# Copyright (c) 2020 Peter Kolbus -# Copyright (c) 2020 hippo91 -# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> -# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> - # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors import io From ec4a3f7f1eac9e605b35d1ac56680e1608c48be3 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 1 Mar 2022 16:04:49 +0100 Subject: [PATCH 301/357] Add a pre-commit hook to check the copyright notice Fix the existing file so they have a notice. No header for setup.py or examples or doc --- .pre-commit-config.yaml | 9 ++++++++- doc/conf.py | 5 ++++- doc/exts/pylint_extensions.py | 3 ++- doc/exts/pylint_features.py | 3 ++- doc/exts/pylint_messages.py | 2 +- pylint/__init__.py | 2 +- pylint/__main__.py | 2 +- pylint/__pkginfo__.py | 3 ++- pylint/checkers/__init__.py | 2 +- pylint/checkers/async.py | 2 +- pylint/checkers/base.py | 2 +- pylint/checkers/base_checker.py | 3 ++- pylint/checkers/classes/__init__.py | 2 +- pylint/checkers/classes/class_checker.py | 2 +- pylint/checkers/classes/special_methods_checker.py | 2 +- pylint/checkers/deprecated.py | 2 +- pylint/checkers/design_analysis.py | 2 +- pylint/checkers/ellipsis_checker.py | 4 ++++ pylint/checkers/exceptions.py | 2 +- pylint/checkers/format.py | 2 +- pylint/checkers/imports.py | 2 +- pylint/checkers/logging.py | 2 +- pylint/checkers/mapreduce_checker.py | 3 ++- pylint/checkers/misc.py | 3 +-- pylint/checkers/modified_iterating_checker.py | 2 +- pylint/checkers/newstyle.py | 2 +- pylint/checkers/non_ascii_names.py | 2 +- pylint/checkers/raw_metrics.py | 2 +- pylint/checkers/refactoring/__init__.py | 2 +- .../checkers/refactoring/implicit_booleaness_checker.py | 3 ++- pylint/checkers/refactoring/not_checker.py | 3 +-- pylint/checkers/refactoring/recommendation_checker.py | 3 ++- pylint/checkers/refactoring/refactoring_checker.py | 2 +- pylint/checkers/similar.py | 2 +- pylint/checkers/spelling.py | 2 +- pylint/checkers/stdlib.py | 2 +- pylint/checkers/strings.py | 2 +- pylint/checkers/threading_checker.py | 2 +- pylint/checkers/typecheck.py | 2 +- pylint/checkers/unicode.py | 2 +- pylint/checkers/unsupported_version.py | 2 +- pylint/checkers/utils.py | 2 +- pylint/checkers/variables.py | 2 +- pylint/config/__init__.py | 2 +- pylint/config/config_initialization.py | 2 +- pylint/config/configuration_mixin.py | 2 +- pylint/config/find_default_config_files.py | 2 +- pylint/config/man_help_formatter.py | 2 +- pylint/config/option.py | 2 +- pylint/config/option_manager_mixin.py | 3 +-- pylint/config/option_parser.py | 2 +- pylint/config/options_provider_mixin.py | 3 +-- pylint/constants.py | 3 ++- pylint/epylint.py | 2 +- pylint/exceptions.py | 2 +- pylint/extensions/__init__.py | 2 +- pylint/extensions/_check_docs_utils.py | 2 +- pylint/extensions/bad_builtin.py | 2 +- pylint/extensions/broad_try_clause.py | 2 +- pylint/extensions/check_elif.py | 2 +- pylint/extensions/code_style.py | 4 ++++ pylint/extensions/comparetozero.py | 2 +- pylint/extensions/comparison_placement.py | 2 +- pylint/extensions/confusing_elif.py | 2 +- pylint/extensions/consider_ternary_expression.py | 4 ++++ pylint/extensions/docparams.py | 2 +- pylint/extensions/docstyle.py | 2 +- pylint/extensions/empty_comment.py | 4 ++++ pylint/extensions/emptystring.py | 2 +- pylint/extensions/eq_without_hash.py | 1 + pylint/extensions/for_any_all.py | 4 ++++ pylint/extensions/mccabe.py | 2 +- pylint/extensions/overlapping_exceptions.py | 2 +- pylint/extensions/private_import.py | 4 ++++ pylint/extensions/redefined_variable_type.py | 2 +- pylint/extensions/set_membership.py | 4 ++++ pylint/extensions/typing.py | 4 ++++ pylint/extensions/while_used.py | 4 ++++ pylint/graph.py | 2 +- pylint/interfaces.py | 2 +- pylint/lint/__init__.py | 2 +- pylint/lint/expand_modules.py | 4 ++++ pylint/lint/parallel.py | 2 +- pylint/lint/pylinter.py | 2 +- pylint/lint/report_functions.py | 2 +- pylint/lint/run.py | 2 +- pylint/lint/utils.py | 2 +- pylint/message/__init__.py | 2 +- pylint/message/message.py | 3 +-- pylint/message/message_definition.py | 2 +- pylint/message/message_definition_store.py | 2 +- pylint/message/message_id_store.py | 3 ++- pylint/pyreverse/__init__.py | 2 +- pylint/pyreverse/diadefslib.py | 2 +- pylint/pyreverse/diagrams.py | 2 +- pylint/pyreverse/dot_printer.py | 2 +- pylint/pyreverse/inspector.py | 2 +- pylint/pyreverse/main.py | 2 +- pylint/pyreverse/mermaidjs_printer.py | 2 +- pylint/pyreverse/plantuml_printer.py | 2 +- pylint/pyreverse/printer.py | 2 +- pylint/pyreverse/printer_factory.py | 2 +- pylint/pyreverse/utils.py | 2 +- pylint/pyreverse/vcg_printer.py | 2 +- pylint/pyreverse/writer.py | 2 +- pylint/reporters/__init__.py | 2 +- pylint/reporters/base_reporter.py | 2 +- pylint/reporters/collecting_reporter.py | 3 ++- pylint/reporters/json_reporter.py | 2 +- pylint/reporters/multi_reporter.py | 3 +-- pylint/reporters/reports_handler_mix_in.py | 2 +- pylint/reporters/text.py | 2 +- pylint/reporters/ureports/__init__.py | 2 +- pylint/reporters/ureports/base_writer.py | 2 +- pylint/reporters/ureports/nodes.py | 2 +- pylint/reporters/ureports/text_writer.py | 2 +- pylint/testutils/__init__.py | 2 +- pylint/testutils/checker_test_case.py | 2 +- pylint/testutils/configuration_test.py | 2 +- pylint/testutils/constants.py | 2 +- pylint/testutils/decorator.py | 2 +- pylint/testutils/functional/__init__.py | 2 +- pylint/testutils/functional/find_functional_tests.py | 2 +- pylint/testutils/functional/lint_module_output_update.py | 2 +- pylint/testutils/functional/test_file.py | 2 +- pylint/testutils/functional_test_file.py | 2 +- pylint/testutils/get_test_info.py | 2 +- pylint/testutils/global_test_linter.py | 3 +-- pylint/testutils/lint_module_test.py | 2 +- pylint/testutils/output_line.py | 2 +- pylint/testutils/primer.py | 4 ++++ pylint/testutils/pyreverse.py | 2 +- pylint/testutils/reporter_for_tests.py | 2 +- pylint/testutils/tokenize_str.py | 2 +- pylint/testutils/unittest_linter.py | 2 +- pylint/typing.py | 2 +- pylint/utils/__init__.py | 2 +- pylint/utils/ast_walker.py | 2 +- pylint/utils/docs.py | 3 ++- pylint/utils/file_state.py | 2 +- pylint/utils/linterstats.py | 2 +- pylint/utils/pragma_parser.py | 2 +- pylint/utils/utils.py | 3 +-- tests/benchmark/test_baseline_benchmarks.py | 2 +- tests/checkers/__init__.py | 3 +++ tests/checkers/conftest.py | 4 ++++ tests/checkers/unittest_base.py | 2 +- tests/checkers/unittest_base_checker.py | 2 +- tests/checkers/unittest_deprecated.py | 4 ++++ tests/checkers/unittest_design.py | 3 +-- tests/checkers/unittest_format.py | 2 +- tests/checkers/unittest_imports.py | 2 +- tests/checkers/unittest_misc.py | 2 +- tests/checkers/unittest_non_ascii_name.py | 4 ++++ tests/checkers/unittest_refactoring.py | 2 +- tests/checkers/unittest_similar.py | 2 +- tests/checkers/unittest_spelling.py | 2 +- tests/checkers/unittest_stdlib.py | 2 +- tests/checkers/unittest_strings.py | 3 +-- tests/checkers/unittest_typecheck.py | 2 +- tests/checkers/unittest_unicode/__init__.py | 4 ++++ tests/checkers/unittest_unicode/unittest_bad_chars.py | 4 ++++ .../unittest_unicode/unittest_bidirectional_unicode.py | 4 ++++ tests/checkers/unittest_unicode/unittest_functions.py | 4 ++++ .../unittest_unicode/unittest_invalid_encoding.py | 4 ++++ tests/checkers/unittest_utils.py | 2 +- tests/checkers/unittest_variables.py | 2 +- tests/config/conftest.py | 4 ++++ tests/config/file_to_lint.py | 4 ++++ tests/config/test_config.py | 4 ++++ tests/config/test_functional_config_loading.py | 2 +- tests/config/unittest_config.py | 2 +- tests/conftest.py | 4 ++++ tests/extensions/__init__.py | 3 +++ tests/extensions/test_check_docs_utils.py | 2 +- tests/extensions/test_private_import.py | 4 ++++ tests/lint/__init__.py | 3 +++ tests/lint/test_pylinter.py | 4 ++++ tests/lint/test_utils.py | 4 ++++ tests/lint/unittest_expand_modules.py | 3 +-- tests/lint/unittest_lint.py | 2 +- tests/message/__init__.py | 3 +++ tests/message/conftest.py | 3 ++- tests/message/test_no_removed_msgid_or_symbol_used.py | 2 +- tests/message/unittest_message.py | 2 +- tests/message/unittest_message_definition.py | 2 +- tests/message/unittest_message_definition_store.py | 2 +- tests/message/unittest_message_id_store.py | 2 +- tests/primer/test_primer_external.py | 3 ++- tests/primer/test_primer_stdlib.py | 2 +- tests/profile/test_profile_against_externals.py | 2 +- tests/pyreverse/conftest.py | 4 ++++ tests/pyreverse/test_diadefs.py | 2 +- tests/pyreverse/test_diagrams.py | 2 +- tests/pyreverse/test_inspector.py | 2 +- tests/pyreverse/test_main.py | 4 ++++ tests/pyreverse/test_printer.py | 2 +- tests/pyreverse/test_printer_factory.py | 2 +- tests/pyreverse/test_utils.py | 2 +- tests/pyreverse/test_writer.py | 2 +- tests/test_check_parallel.py | 2 +- tests/test_epylint.py | 4 ++++ tests/test_func.py | 2 +- tests/test_functional.py | 2 +- tests/test_functional_directories.py | 4 ++++ tests/test_import_graph.py | 2 +- tests/test_numversion.py | 2 +- tests/test_pragma_parser.py | 4 ++++ tests/test_pylint_runners.py | 4 ++++ tests/test_regr.py | 2 +- tests/test_self.py | 2 +- tests/test_similar.py | 2 +- tests/testutils/dummy_checker.py | 3 +++ tests/testutils/test_configuration_test.py | 4 ++++ tests/testutils/test_decorator.py | 3 +-- tests/testutils/test_functional_testutils.py | 2 +- tests/testutils/test_lint_module_output_update.py | 2 +- tests/testutils/test_output_line.py | 2 +- tests/testutils/test_package_to_lint.py | 2 +- tests/unittest_reporters_json.py | 2 +- tests/unittest_reporting.py | 2 +- tests/utils/__init__.py | 3 +++ tests/utils/unittest_ast_walker.py | 2 +- tests/utils/unittest_utils.py | 2 +- 224 files changed, 358 insertions(+), 196 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f19027503c..6bd4832deb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,13 +13,20 @@ repos: rev: v1.4 hooks: - id: autoflake - exclude: &fixtures tests/functional/|tests/input|tests/regrtest_data/|tests/data/|doc/data/messages + exclude: &fixtures tests/functional/|tests/input|tests/regrtest_data/|tests/data/|doc/data/messages|tests/testutils/data/ args: - --in-place - --remove-all-unused-imports - --expand-star-imports - --remove-duplicate-keys - --remove-unused-variables + - repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit + rev: f683ab7d10d5f7e779c75aea17197007583d14e4 + hooks: + - id: copyright-notice + args: ["--notice=script/copyright.txt", "--enforce-all"] + exclude: tests/functional/|tests/input|tests/regrtest_data/|tests/data/|doc/data/messages|tests/testutils/data/|examples/|setup.py + types: [python] - repo: https://github.com/asottile/pyupgrade rev: v2.31.1 hooks: diff --git a/doc/conf.py b/doc/conf.py index 0f15ffd91c..8b548ddc33 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,7 @@ -# +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + # Pylint documentation build configuration file, created by # sphinx-quickstart on Thu Apr 4 20:31:25 2013. # diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py index 921db904df..da1b9ee2e4 100755 --- a/doc/exts/pylint_extensions.py +++ b/doc/exts/pylint_extensions.py @@ -1,7 +1,8 @@ #!/usr/bin/env python + # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Script used to generate the extensions file before building the actual documentation.""" diff --git a/doc/exts/pylint_features.py b/doc/exts/pylint_features.py index b4e5e2df2f..a605102243 100755 --- a/doc/exts/pylint_features.py +++ b/doc/exts/pylint_features.py @@ -1,7 +1,8 @@ #!/usr/bin/env python + # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Script used to generate the features file before building the actual documentation.""" diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index 51b10482f0..5c63171264 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Script used to generate the messages files.""" diff --git a/pylint/__init__.py b/pylint/__init__.py index cb5468eb5a..9fde923dc0 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os import sys diff --git a/pylint/__main__.py b/pylint/__main__.py index 267adb9eb0..7df5805f93 100644 --- a/pylint/__main__.py +++ b/pylint/__main__.py @@ -2,7 +2,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import pylint diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 24bc31e99c..0063381eb1 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import Tuple __version__ = "2.13.0-dev0" diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 1f7f8fd38d..2855d70293 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Utilities methods and classes for checkers. diff --git a/pylint/checkers/async.py b/pylint/checkers/async.py index 58fe8a5d8f..a85ab41170 100644 --- a/pylint/checkers/async.py +++ b/pylint/checkers/async.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker for anything related to the async protocol (PEP 492).""" diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 7f2d9da7b3..38791c05ea 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Basic checker for Python code.""" import collections diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index e0e3c03f30..1cdc014fc0 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import functools from inspect import cleandoc from typing import Any, Optional diff --git a/pylint/checkers/classes/__init__.py b/pylint/checkers/classes/__init__.py index ac1641fdf4..5e9bc5ae36 100644 --- a/pylint/checkers/classes/__init__.py +++ b/pylint/checkers/classes/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 42fc83ea70..91343a5d26 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Classes checker for Python code.""" import collections diff --git a/pylint/checkers/classes/special_methods_checker.py b/pylint/checkers/classes/special_methods_checker.py index e85150f082..9a7c9a7629 100644 --- a/pylint/checkers/classes/special_methods_checker.py +++ b/pylint/checkers/classes/special_methods_checker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Special methods checker and helper function's module.""" diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 0edbac4a98..299922f2b3 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker mixin for deprecated functionality.""" from itertools import chain diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index ad600fc2fd..dcc43dc502 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Check for signs of poor design.""" diff --git a/pylint/checkers/ellipsis_checker.py b/pylint/checkers/ellipsis_checker.py index 76e8a77ce0..2d904a28d2 100644 --- a/pylint/checkers/ellipsis_checker.py +++ b/pylint/checkers/ellipsis_checker.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Ellipsis checker for Python code.""" from typing import TYPE_CHECKING diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 1020e7c183..6d0397e528 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checks for various exception related errors.""" import builtins diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 9f125b5681..fae6085ca8 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Python code format's checker. diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 4e8f64ccf0..fb99bbf4b6 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Imports checkers for Python code.""" diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index 084aeebeec..743f40db8c 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker for use of Python logging.""" import string diff --git a/pylint/checkers/mapreduce_checker.py b/pylint/checkers/mapreduce_checker.py index 3078b6872e..d6ee875dc5 100644 --- a/pylint/checkers/mapreduce_checker.py +++ b/pylint/checkers/mapreduce_checker.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import abc diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index c558e28851..e5e77bb81d 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Check source code is ascii only or has an encoding declaration (PEP 263).""" diff --git a/pylint/checkers/modified_iterating_checker.py b/pylint/checkers/modified_iterating_checker.py index b3e5daeae8..d1fe08d40c 100644 --- a/pylint/checkers/modified_iterating_checker.py +++ b/pylint/checkers/modified_iterating_checker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING, Union diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index af4e365d02..15833f2937 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Check for new / old style related problems.""" from typing import TYPE_CHECKING diff --git a/pylint/checkers/non_ascii_names.py b/pylint/checkers/non_ascii_names.py index d80d9dec05..ece663ab4d 100644 --- a/pylint/checkers/non_ascii_names.py +++ b/pylint/checkers/non_ascii_names.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """All alphanumeric unicode character are allowed in Python but due to similarities in how they look they can be confused. diff --git a/pylint/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index 15f08dc852..524ecbf854 100644 --- a/pylint/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import sys import tokenize diff --git a/pylint/checkers/refactoring/__init__.py b/pylint/checkers/refactoring/__init__.py index fdcf635fd0..481845f925 100644 --- a/pylint/checkers/refactoring/__init__.py +++ b/pylint/checkers/refactoring/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Looks for code which can be refactored.""" diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index dd59b2085b..e52b972d8f 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import List, Union import astroid diff --git a/pylint/checkers/refactoring/not_checker.py b/pylint/checkers/refactoring/not_checker.py index c7c8f4289e..85d33bfdb6 100644 --- a/pylint/checkers/refactoring/not_checker.py +++ b/pylint/checkers/refactoring/not_checker.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import astroid from astroid import nodes diff --git a/pylint/checkers/refactoring/recommendation_checker.py b/pylint/checkers/refactoring/recommendation_checker.py index 5aa1d26226..1fb7e72375 100644 --- a/pylint/checkers/refactoring/recommendation_checker.py +++ b/pylint/checkers/refactoring/recommendation_checker.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import Union import astroid diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 1514770479..8dc65f129d 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import copy diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index cd70e326b3..69b401fa45 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """A similarities / code duplication command line tool and pylint checker. diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index 92d6e037ff..f0c5effeb6 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker for spelling errors in comments and docstrings.""" import os diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index ad5aa3dbcc..007a4f9788 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checkers for various standard library functions.""" diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 19bdbce91f..dd9f1887f4 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker for string formatting operations.""" diff --git a/pylint/checkers/threading_checker.py b/pylint/checkers/threading_checker.py index 649696f4b4..6a720d334f 100644 --- a/pylint/checkers/threading_checker.py +++ b/pylint/checkers/threading_checker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f220f4c273..70b958238d 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Try to find more bugs in the code using astroid inference capabilities.""" diff --git a/pylint/checkers/unicode.py b/pylint/checkers/unicode.py index a6f46ad4bb..8939524df2 100644 --- a/pylint/checkers/unicode.py +++ b/pylint/checkers/unicode.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unicode and some other ASCII characters can be used to create programs that run much different compared to what a human reader would expect from them. diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index 8c34a921f8..d63d4fb20b 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker for features used that are not supported by all python versions indicated by the py-version setting. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index f3b643109b..344792d09b 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Some functions that may be useful for various checkers.""" import builtins diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index b518eb47e7..7600eb00ee 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Variables checkers for Python code.""" import collections diff --git a/pylint/config/__init__.py b/pylint/config/__init__.py index c4582ee002..12f834d81b 100644 --- a/pylint/config/__init__.py +++ b/pylint/config/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os import pathlib diff --git a/pylint/config/config_initialization.py b/pylint/config/config_initialization.py index fd36e7c6f1..6be0ab8b93 100644 --- a/pylint/config/config_initialization.py +++ b/pylint/config/config_initialization.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import sys from pathlib import Path diff --git a/pylint/config/configuration_mixin.py b/pylint/config/configuration_mixin.py index bb1115ad76..3db9acaa1c 100644 --- a/pylint/config/configuration_mixin.py +++ b/pylint/config/configuration_mixin.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from pylint.config.option_manager_mixin import OptionsManagerMixIn from pylint.config.options_provider_mixin import OptionsProviderMixIn diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index d2461a6251..7538f5b15e 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import configparser import os diff --git a/pylint/config/man_help_formatter.py b/pylint/config/man_help_formatter.py index 5531fec03b..84837cc3c1 100644 --- a/pylint/config/man_help_formatter.py +++ b/pylint/config/man_help_formatter.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import optparse # pylint: disable=deprecated-module import sys diff --git a/pylint/config/option.py b/pylint/config/option.py index 5667a14ccf..260e0a2207 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import copy import optparse # pylint: disable=deprecated-module diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 346533ab60..44e1127379 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import configparser diff --git a/pylint/config/option_parser.py b/pylint/config/option_parser.py index ad512329bf..b5d228ec23 100644 --- a/pylint/config/option_parser.py +++ b/pylint/config/option_parser.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import optparse # pylint: disable=deprecated-module diff --git a/pylint/config/options_provider_mixin.py b/pylint/config/options_provider_mixin.py index ae467c30c6..e937fee155 100644 --- a/pylint/config/options_provider_mixin.py +++ b/pylint/config/options_provider_mixin.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import optparse # pylint: disable=deprecated-module from typing import Any, Dict, Tuple diff --git a/pylint/constants.py b/pylint/constants.py index 94e0636bb5..a6c9aa63d4 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import platform import sys from typing import Dict, List, NamedTuple, Tuple diff --git a/pylint/epylint.py b/pylint/epylint.py index d02ce6d5dc..2eade40f9b 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -3,7 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Emacs and Flymake compatible Pylint. diff --git a/pylint/exceptions.py b/pylint/exceptions.py index 61b5bb07de..06ed21acd3 100644 --- a/pylint/exceptions.py +++ b/pylint/exceptions.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Exception classes raised by various operations within pylint.""" diff --git a/pylint/extensions/__init__.py b/pylint/extensions/__init__.py index 467608e4ec..393fe5e9e4 100644 --- a/pylint/extensions/__init__.py +++ b/pylint/extensions/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index ff01ef226d..4853162c0f 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Utility methods for docstring checking.""" diff --git a/pylint/extensions/bad_builtin.py b/pylint/extensions/bad_builtin.py index 62fe6691c2..259df6e954 100644 --- a/pylint/extensions/bad_builtin.py +++ b/pylint/extensions/bad_builtin.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checker for deprecated builtins.""" from typing import TYPE_CHECKING diff --git a/pylint/extensions/broad_try_clause.py b/pylint/extensions/broad_try_clause.py index e51106987b..ddb7f75a38 100644 --- a/pylint/extensions/broad_try_clause.py +++ b/pylint/extensions/broad_try_clause.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Looks for try/except statements with too much code in the try clause.""" from typing import TYPE_CHECKING, Union diff --git a/pylint/extensions/check_elif.py b/pylint/extensions/check_elif.py index 41d737f5f0..6ffc32009b 100644 --- a/pylint/extensions/check_elif.py +++ b/pylint/extensions/check_elif.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index d1bb04928a..74e838ed20 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import sys from typing import TYPE_CHECKING, List, Optional, Set, Tuple, Type, Union, cast diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py index 37304b93ee..a1db3e5312 100644 --- a/pylint/extensions/comparetozero.py +++ b/pylint/extensions/comparetozero.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Looks for comparisons to zero.""" diff --git a/pylint/extensions/comparison_placement.py b/pylint/extensions/comparison_placement.py index a85ea91fb6..8156f98b30 100644 --- a/pylint/extensions/comparison_placement.py +++ b/pylint/extensions/comparison_placement.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Checks for yoda comparisons (variable before constant) See https://en.wikipedia.org/wiki/Yoda_conditions diff --git a/pylint/extensions/confusing_elif.py b/pylint/extensions/confusing_elif.py index b6eee6ef14..9f815da96d 100644 --- a/pylint/extensions/confusing_elif.py +++ b/pylint/extensions/confusing_elif.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING diff --git a/pylint/extensions/consider_ternary_expression.py b/pylint/extensions/consider_ternary_expression.py index 6dabe3613f..aece514b77 100644 --- a/pylint/extensions/consider_ternary_expression.py +++ b/pylint/extensions/consider_ternary_expression.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Check for if / assign blocks that can be rewritten with if-expressions.""" from typing import TYPE_CHECKING diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index ff3a0c641d..eff625b92d 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Pylint plugin for checking in Sphinx, Google, or Numpy style docstrings.""" import re diff --git a/pylint/extensions/docstyle.py b/pylint/extensions/docstyle.py index 8823d61e57..537f110dc4 100644 --- a/pylint/extensions/docstyle.py +++ b/pylint/extensions/docstyle.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import linecache from typing import TYPE_CHECKING diff --git a/pylint/extensions/empty_comment.py b/pylint/extensions/empty_comment.py index 68b48970d9..4308364bb6 100644 --- a/pylint/extensions/empty_comment.py +++ b/pylint/extensions/empty_comment.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import TYPE_CHECKING from astroid import nodes diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py index 30a8212257..68f086fb9b 100644 --- a/pylint/extensions/emptystring.py +++ b/pylint/extensions/emptystring.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Looks for comparisons to empty string.""" diff --git a/pylint/extensions/eq_without_hash.py b/pylint/extensions/eq_without_hash.py index aeadac9b33..9cbbb54522 100644 --- a/pylint/extensions/eq_without_hash.py +++ b/pylint/extensions/eq_without_hash.py @@ -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/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """This is the remnant of the python3 checker. diff --git a/pylint/extensions/for_any_all.py b/pylint/extensions/for_any_all.py index 915fae8a33..078c204023 100644 --- a/pylint/extensions/for_any_all.py +++ b/pylint/extensions/for_any_all.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Check for use of for loops that only check for a condition.""" from typing import TYPE_CHECKING diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py index 6b3e4080d6..5d2e2fc238 100644 --- a/pylint/extensions/mccabe.py +++ b/pylint/extensions/mccabe.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Module to add McCabe checker class for pylint.""" diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index 771a9a499c..17eaa6d3ac 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Looks for overlapping exceptions.""" diff --git a/pylint/extensions/private_import.py b/pylint/extensions/private_import.py index 88033fa1e5..8d57eca837 100644 --- a/pylint/extensions/private_import.py +++ b/pylint/extensions/private_import.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Check for imports on private external modules and names.""" from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Union diff --git a/pylint/extensions/redefined_variable_type.py b/pylint/extensions/redefined_variable_type.py index 65e666f213..41464630b9 100644 --- a/pylint/extensions/redefined_variable_type.py +++ b/pylint/extensions/redefined_variable_type.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import TYPE_CHECKING, List diff --git a/pylint/extensions/set_membership.py b/pylint/extensions/set_membership.py index 6ba5166c5b..57089dc161 100644 --- a/pylint/extensions/set_membership.py +++ b/pylint/extensions/set_membership.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import TYPE_CHECKING from astroid import nodes diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 07d18fdc7f..d6d67515a0 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import TYPE_CHECKING, Dict, List, NamedTuple, Set, Union import astroid.bases diff --git a/pylint/extensions/while_used.py b/pylint/extensions/while_used.py index dc9861bac7..4c41510d4f 100644 --- a/pylint/extensions/while_used.py +++ b/pylint/extensions/while_used.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Check for use of while loops.""" from typing import TYPE_CHECKING diff --git a/pylint/graph.py b/pylint/graph.py index 07e3aa549c..8191847249 100644 --- a/pylint/graph.py +++ b/pylint/graph.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Graph manipulation utilities. diff --git a/pylint/interfaces.py b/pylint/interfaces.py index fe48d4daa2..7f6edbdf52 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Interfaces for Pylint objects.""" from collections import namedtuple diff --git a/pylint/lint/__init__.py b/pylint/lint/__init__.py index b110728658..7cfa7f59d8 100644 --- a/pylint/lint/__init__.py +++ b/pylint/lint/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Pylint [options] modules_or_packages. diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 236e49c992..184316e9bd 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import os import sys from typing import List, Pattern, Tuple diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index 5cf0b0b67f..ea5d7abacc 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import functools diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 248df9d06d..0bf8961c1e 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import contextlib diff --git a/pylint/lint/report_functions.py b/pylint/lint/report_functions.py index 052c564a7f..f4c0a57106 100644 --- a/pylint/lint/report_functions.py +++ b/pylint/lint/report_functions.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections from typing import DefaultDict, Dict, Union diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 28e45dde66..2d28cc47f5 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os import sys diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index 4a1a375703..8377dfdd44 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import contextlib import sys diff --git a/pylint/message/__init__.py b/pylint/message/__init__.py index 1238c127ae..11d2b17dce 100644 --- a/pylint/message/__init__.py +++ b/pylint/message/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """All the classes related to Message handling.""" diff --git a/pylint/message/message.py b/pylint/message/message.py index 79db438784..297442e450 100644 --- a/pylint/message/message.py +++ b/pylint/message/message.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections from typing import Optional, Tuple, Union, overload diff --git a/pylint/message/message_definition.py b/pylint/message/message_definition.py index ea65a32b88..a729e595c7 100644 --- a/pylint/message/message_definition.py +++ b/pylint/message/message_definition.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import sys from typing import TYPE_CHECKING, List, Optional, Tuple diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py index 643bc26a93..e3605fab6d 100644 --- a/pylint/message/message_definition_store.py +++ b/pylint/message/message_definition_store.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import functools diff --git a/pylint/message/message_id_store.py b/pylint/message/message_id_store.py index b45d074c65..d4ccbe6bc8 100644 --- a/pylint/message/message_id_store.py +++ b/pylint/message/message_id_store.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import Dict, List, NoReturn, Optional, Tuple from pylint.exceptions import InvalidMessageError, UnknownMessageError diff --git a/pylint/pyreverse/__init__.py b/pylint/pyreverse/__init__.py index f0ca20a7bf..458c0f35db 100644 --- a/pylint/pyreverse/__init__.py +++ b/pylint/pyreverse/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Pyreverse.extensions.""" diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index 50b9ad9b31..b5dd3fb506 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Handle diagram generation options for class diagram or default diagrams.""" diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index c2699f7674..2ad50910dd 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Diagram objects.""" import astroid diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index 841d96fdca..0cca0ab619 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Class to generate files in dot format and image formats supported by Graphviz.""" import os diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 7ef3f7bb64..91d2199f22 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Visitor doing some postprocessing on the astroid tree. diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 9efe9ee4d7..ceaa123511 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """%prog [options] . diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index b0452d2882..b1b56fcd25 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Class to generate files in mermaidjs format.""" from typing import Dict, Optional diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index 8674aead73..57fe3a9d06 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Class to generate files in dot format and image formats supported by Graphviz.""" from typing import Dict, Optional diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index 346579719a..43cc534e5e 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Base class defining the interface for a printer.""" from abc import ABC, abstractmethod diff --git a/pylint/pyreverse/printer_factory.py b/pylint/pyreverse/printer_factory.py index 27eb7f7ab8..29a406b01f 100644 --- a/pylint/pyreverse/printer_factory.py +++ b/pylint/pyreverse/printer_factory.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import Dict, Type diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index d18efe32ee..8b279281e4 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Generic classes/functions for pyreverse core/extensions.""" import os diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index 3a4d25edcf..fa5e90a29a 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Functions to generate files readable with Georg Sander's vcg (Visualization of Compiler Graphs). diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index a45394ac76..a3120e6d9c 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Utilities for creating VCG and Dot diagrams.""" diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 2420a03abf..47bb680b04 100644 --- a/pylint/reporters/__init__.py +++ b/pylint/reporters/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Utilities methods and classes for reporters.""" from typing import TYPE_CHECKING diff --git a/pylint/reporters/base_reporter.py b/pylint/reporters/base_reporter.py index fc7422370f..b45be0dc4f 100644 --- a/pylint/reporters/base_reporter.py +++ b/pylint/reporters/base_reporter.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os import sys diff --git a/pylint/reporters/collecting_reporter.py b/pylint/reporters/collecting_reporter.py index 2ee041b01c..1562ab15d2 100644 --- a/pylint/reporters/collecting_reporter.py +++ b/pylint/reporters/collecting_reporter.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import TYPE_CHECKING from pylint.reporters.base_reporter import BaseReporter diff --git a/pylint/reporters/json_reporter.py b/pylint/reporters/json_reporter.py index cb7418fbbc..f7e782c9ac 100644 --- a/pylint/reporters/json_reporter.py +++ b/pylint/reporters/json_reporter.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """JSON reporter.""" import json diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index 63b65eb0c4..43822f05c2 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os from typing import IO, TYPE_CHECKING, Any, AnyStr, Callable, List, Optional diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index f6486c95dd..890d58660d 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections from typing import ( diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 09a113c415..d37de5b963 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Plain text reporters:. diff --git a/pylint/reporters/ureports/__init__.py b/pylint/reporters/ureports/__init__.py index 51e490b4da..a6a0946af9 100644 --- a/pylint/reporters/ureports/__init__.py +++ b/pylint/reporters/ureports/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt __all__ = ("BaseWriter",) diff --git a/pylint/reporters/ureports/base_writer.py b/pylint/reporters/ureports/base_writer.py index 38eba6227e..4f0ce55c81 100644 --- a/pylint/reporters/ureports/base_writer.py +++ b/pylint/reporters/ureports/base_writer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Universal report objects and some formatting drivers. diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index f7d6f89c90..543082ae9a 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Micro reports objects. diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py index b247af4a02..fc4ce67df9 100644 --- a/pylint/reporters/ureports/text_writer.py +++ b/pylint/reporters/ureports/text_writer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Text formatting drivers for ureports.""" diff --git a/pylint/testutils/__init__.py b/pylint/testutils/__init__.py index a7ac8680c9..0b55fc8e99 100644 --- a/pylint/testutils/__init__.py +++ b/pylint/testutils/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Functional/non regression tests for pylint.""" diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index 84df31e049..b6e10fb481 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import contextlib import warnings diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index 591dc57bd8..c6241f31ed 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Utility functions for configuration testing.""" import copy diff --git a/pylint/testutils/constants.py b/pylint/testutils/constants.py index 305b367538..1c3c69d795 100644 --- a/pylint/testutils/constants.py +++ b/pylint/testutils/constants.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import operator import re diff --git a/pylint/testutils/decorator.py b/pylint/testutils/decorator.py index efa572b59f..b2a790c452 100644 --- a/pylint/testutils/decorator.py +++ b/pylint/testutils/decorator.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import functools import optparse # pylint: disable=deprecated-module diff --git a/pylint/testutils/functional/__init__.py b/pylint/testutils/functional/__init__.py index d9df9d338b..4840e1ba64 100644 --- a/pylint/testutils/functional/__init__.py +++ b/pylint/testutils/functional/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt __all__ = [ "FunctionalTestFile", diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py index 6fbdded121..78d821e319 100644 --- a/pylint/testutils/functional/find_functional_tests.py +++ b/pylint/testutils/functional/find_functional_tests.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os from pathlib import Path diff --git a/pylint/testutils/functional/lint_module_output_update.py b/pylint/testutils/functional/lint_module_output_update.py index 1f5fc8392c..ae577911f4 100644 --- a/pylint/testutils/functional/lint_module_output_update.py +++ b/pylint/testutils/functional/lint_module_output_update.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import csv import os diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index aaaa7e8a9e..ae8d0f2ce3 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import configparser import sys diff --git a/pylint/testutils/functional_test_file.py b/pylint/testutils/functional_test_file.py index 1a3c07eff8..fc1cdcbb14 100644 --- a/pylint/testutils/functional_test_file.py +++ b/pylint/testutils/functional_test_file.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt __all__ = [ "FunctionalTestFile", diff --git a/pylint/testutils/get_test_info.py b/pylint/testutils/get_test_info.py index 7a8bb9f92a..d0bf8f4e3f 100644 --- a/pylint/testutils/get_test_info.py +++ b/pylint/testutils/get_test_info.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from glob import glob from os.path import basename, join, splitext diff --git a/pylint/testutils/global_test_linter.py b/pylint/testutils/global_test_linter.py index 57506491d6..f6f421dab2 100644 --- a/pylint/testutils/global_test_linter.py +++ b/pylint/testutils/global_test_linter.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from pylint import checkers from pylint.lint import PyLinter diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 19459a0bb2..3bac0785d7 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import csv import operator diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index 62ba9b039d..f6461adc7a 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import warnings from typing import Any, NamedTuple, Optional, Sequence, Tuple, TypeVar, Union diff --git a/pylint/testutils/primer.py b/pylint/testutils/primer.py index 558ad582e0..e0f6f2e4d0 100644 --- a/pylint/testutils/primer.py +++ b/pylint/testutils/primer.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import logging from pathlib import Path from typing import Dict, List, Optional, Union diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index b4e83f5d2b..f4e9bc7fc9 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import List, Optional, Tuple diff --git a/pylint/testutils/reporter_for_tests.py b/pylint/testutils/reporter_for_tests.py index 0610e6594e..47c12a3d2f 100644 --- a/pylint/testutils/reporter_for_tests.py +++ b/pylint/testutils/reporter_for_tests.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from io import StringIO from os import getcwd, sep diff --git a/pylint/testutils/tokenize_str.py b/pylint/testutils/tokenize_str.py index 5ce152e6ee..0b478c32cb 100644 --- a/pylint/testutils/tokenize_str.py +++ b/pylint/testutils/tokenize_str.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import tokenize from io import StringIO diff --git a/pylint/testutils/unittest_linter.py b/pylint/testutils/unittest_linter.py index 51f60c7342..a7d7fc02df 100644 --- a/pylint/testutils/unittest_linter.py +++ b/pylint/testutils/unittest_linter.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import Any, Optional diff --git a/pylint/typing.py b/pylint/typing.py index 84488987ac..32b2ac4190 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """A collection of typing utilities.""" import sys diff --git a/pylint/utils/__init__.py b/pylint/utils/__init__.py index 7406f7ec6e..51813f994a 100644 --- a/pylint/utils/__init__.py +++ b/pylint/utils/__init__.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Some various utilities and helper classes, most of them used in the main pylint class diff --git a/pylint/utils/ast_walker.py b/pylint/utils/ast_walker.py index 08fc3af925..415bd97ddb 100644 --- a/pylint/utils/ast_walker.py +++ b/pylint/utils/ast_walker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import traceback diff --git a/pylint/utils/docs.py b/pylint/utils/docs.py index 22412b803c..b6fb877f0f 100644 --- a/pylint/utils/docs.py +++ b/pylint/utils/docs.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Various helper functions to create the docs of a linter object.""" import sys diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index b7f09a6b9c..b820affccd 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import collections import sys diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py index e09e0ce432..f7ca05f610 100644 --- a/pylint/utils/linterstats.py +++ b/pylint/utils/linterstats.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import sys from typing import Dict, List, Optional, Set, cast diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index 090a336fc5..01cdf440af 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import re from collections import namedtuple diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 835351ec37..6a60cd8e96 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt try: import isort.api diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index b4550db431..f9e997ef3d 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -2,7 +2,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=missing-function-docstring diff --git a/tests/checkers/__init__.py b/tests/checkers/__init__.py index e69de29bb2..e8a8ff79fd 100644 --- a/tests/checkers/__init__.py +++ b/tests/checkers/__init__.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/tests/checkers/conftest.py b/tests/checkers/conftest.py index e831015420..ac2a187d45 100644 --- a/tests/checkers/conftest.py +++ b/tests/checkers/conftest.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from pathlib import Path HERE = Path(__file__).parent diff --git a/tests/checkers/unittest_base.py b/tests/checkers/unittest_base.py index b0238c3eae..198eb174e1 100644 --- a/tests/checkers/unittest_base.py +++ b/tests/checkers/unittest_base.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unittest for the base checker.""" diff --git a/tests/checkers/unittest_base_checker.py b/tests/checkers/unittest_base_checker.py index 7efdf2aba9..68d36cd6c5 100644 --- a/tests/checkers/unittest_base_checker.py +++ b/tests/checkers/unittest_base_checker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unittest for the BaseChecker class.""" diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index 75608acf4d..0b35b297aa 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import List, Optional, Set, Tuple, Union import astroid diff --git a/tests/checkers/unittest_design.py b/tests/checkers/unittest_design.py index 95baea3b21..46eab45d72 100644 --- a/tests/checkers/unittest_design.py +++ b/tests/checkers/unittest_design.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import astroid diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 8c5a2836ce..fa8b549a53 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Check format checker helper functions.""" diff --git a/tests/checkers/unittest_imports.py b/tests/checkers/unittest_imports.py index 7f1181339e..ac36f784fb 100644 --- a/tests/checkers/unittest_imports.py +++ b/tests/checkers/unittest_imports.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit tests for the imports checker.""" diff --git a/tests/checkers/unittest_misc.py b/tests/checkers/unittest_misc.py index 9c0ec70895..e360d3bb68 100644 --- a/tests/checkers/unittest_misc.py +++ b/tests/checkers/unittest_misc.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Tests for the misc checker.""" diff --git a/tests/checkers/unittest_non_ascii_name.py b/tests/checkers/unittest_non_ascii_name.py index 4d69a65194..f6c9abcdbd 100644 --- a/tests/checkers/unittest_non_ascii_name.py +++ b/tests/checkers/unittest_non_ascii_name.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import sys from typing import Iterable, Optional diff --git a/tests/checkers/unittest_refactoring.py b/tests/checkers/unittest_refactoring.py index 2a28c0bede..fda53098d7 100644 --- a/tests/checkers/unittest_refactoring.py +++ b/tests/checkers/unittest_refactoring.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index 3911db93bc..64ed46eb02 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from contextlib import redirect_stdout from io import StringIO diff --git a/tests/checkers/unittest_spelling.py b/tests/checkers/unittest_spelling.py index 6f7308aaa0..3d45539138 100644 --- a/tests/checkers/unittest_spelling.py +++ b/tests/checkers/unittest_spelling.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unittest for the spelling checker.""" diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py index 480da89921..cdd61e6bad 100644 --- a/tests/checkers/unittest_stdlib.py +++ b/tests/checkers/unittest_stdlib.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import contextlib from typing import Any, Callable, Iterator, Optional, Union diff --git a/tests/checkers/unittest_strings.py b/tests/checkers/unittest_strings.py index 529a9284fc..7927dcc49d 100644 --- a/tests/checkers/unittest_strings.py +++ b/tests/checkers/unittest_strings.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from pylint.checkers import strings diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index 736a16ca20..afcc2dc2e7 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import astroid import pytest diff --git a/tests/checkers/unittest_unicode/__init__.py b/tests/checkers/unittest_unicode/__init__.py index 7a748245af..df1e5bf3dc 100644 --- a/tests/checkers/unittest_unicode/__init__.py +++ b/tests/checkers/unittest_unicode/__init__.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import io from pathlib import Path diff --git a/tests/checkers/unittest_unicode/unittest_bad_chars.py b/tests/checkers/unittest_unicode/unittest_bad_chars.py index 8c98e094bd..154faad5a0 100644 --- a/tests/checkers/unittest_unicode/unittest_bad_chars.py +++ b/tests/checkers/unittest_unicode/unittest_bad_chars.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + # pylint: disable=redefined-outer-name import itertools from pathlib import Path diff --git a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py index 2416957c92..68134fcc98 100644 --- a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py +++ b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import itertools import unicodedata from pathlib import Path diff --git a/tests/checkers/unittest_unicode/unittest_functions.py b/tests/checkers/unittest_unicode/unittest_functions.py index d14d2fdadf..bf42df996c 100644 --- a/tests/checkers/unittest_unicode/unittest_functions.py +++ b/tests/checkers/unittest_unicode/unittest_functions.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import itertools from pathlib import Path from typing import Dict diff --git a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py index 5facf9a059..ff46539e46 100644 --- a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py +++ b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import codecs import io import shutil diff --git a/tests/checkers/unittest_utils.py b/tests/checkers/unittest_utils.py index e27443f26d..121bcef2e9 100644 --- a/tests/checkers/unittest_utils.py +++ b/tests/checkers/unittest_utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Tests for the pylint.checkers.utils module.""" diff --git a/tests/checkers/unittest_variables.py b/tests/checkers/unittest_variables.py index e9ebbd6137..c2d2d8ec34 100644 --- a/tests/checkers/unittest_variables.py +++ b/tests/checkers/unittest_variables.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import os import re diff --git a/tests/config/conftest.py b/tests/config/conftest.py index 5a6778af2f..491b16267a 100644 --- a/tests/config/conftest.py +++ b/tests/config/conftest.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from pathlib import Path import pytest diff --git a/tests/config/file_to_lint.py b/tests/config/file_to_lint.py index bc3f722d99..b6a8c14c86 100644 --- a/tests/config/file_to_lint.py +++ b/tests/config/file_to_lint.py @@ -1 +1,5 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Perfect module with only documentation for configuration tests.""" diff --git a/tests/config/test_config.py b/tests/config/test_config.py index f89e624168..701d24a822 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import os from pathlib import Path from typing import Optional, Set diff --git a/tests/config/test_functional_config_loading.py b/tests/config/test_functional_config_loading.py index 174af48b89..e4ed15051b 100644 --- a/tests/config/test_functional_config_loading.py +++ b/tests/config/test_functional_config_loading.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """This launches the configuration functional tests. This permits to test configuration files by providing a file with the appropriate extension in the ``tests/config/functional`` diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py index 16e12350e3..14c3b72320 100644 --- a/tests/config/unittest_config.py +++ b/tests/config/unittest_config.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit tests for the config module.""" diff --git a/tests/conftest.py b/tests/conftest.py index 1f2347a1ab..dd0b1195e3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + # pylint: disable=redefined-outer-name import os from pathlib import Path diff --git a/tests/extensions/__init__.py b/tests/extensions/__init__.py index e69de29bb2..e8a8ff79fd 100644 --- a/tests/extensions/__init__.py +++ b/tests/extensions/__init__.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py index abc0751d82..b14138a921 100644 --- a/tests/extensions/test_check_docs_utils.py +++ b/tests/extensions/test_check_docs_utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit tests for utils functions in :mod:`pylint.extensions._check_docs_utils`.""" import astroid diff --git a/tests/extensions/test_private_import.py b/tests/extensions/test_private_import.py index 10648a8f91..d2d79947fb 100644 --- a/tests/extensions/test_private_import.py +++ b/tests/extensions/test_private_import.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Tests the local module directory comparison logic which requires mocking file directories""" from unittest.mock import patch diff --git a/tests/lint/__init__.py b/tests/lint/__init__.py index e69de29bb2..e8a8ff79fd 100644 --- a/tests/lint/__init__.py +++ b/tests/lint/__init__.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/tests/lint/test_pylinter.py b/tests/lint/test_pylinter.py index 9d6695a250..57ad0f7b8e 100644 --- a/tests/lint/test_pylinter.py +++ b/tests/lint/test_pylinter.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import Any, NoReturn from unittest.mock import patch diff --git a/tests/lint/test_utils.py b/tests/lint/test_utils.py index a1fa536140..891ee0020b 100644 --- a/tests/lint/test_utils.py +++ b/tests/lint/test_utils.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import unittest.mock from pathlib import Path, PosixPath diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 6c0855e9d4..75d344707c 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import re from pathlib import Path diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 04b94d4e8e..91df7dc1f6 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=redefined-outer-name diff --git a/tests/message/__init__.py b/tests/message/__init__.py index e69de29bb2..e8a8ff79fd 100644 --- a/tests/message/__init__.py +++ b/tests/message/__init__.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/tests/message/conftest.py b/tests/message/conftest.py index aee6f50194..7a9a399cfa 100644 --- a/tests/message/conftest.py +++ b/tests/message/conftest.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + # pylint: disable=redefined-outer-name diff --git a/tests/message/test_no_removed_msgid_or_symbol_used.py b/tests/message/test_no_removed_msgid_or_symbol_used.py index d829c2943a..e00450b9a7 100644 --- a/tests/message/test_no_removed_msgid_or_symbol_used.py +++ b/tests/message/test_no_removed_msgid_or_symbol_used.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from pylint.constants import DELETED_MESSAGES from pylint.lint import PyLinter diff --git a/tests/message/unittest_message.py b/tests/message/unittest_message.py index f8397cc3cb..ef560a6499 100644 --- a/tests/message/unittest_message.py +++ b/tests/message/unittest_message.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import ValuesView diff --git a/tests/message/unittest_message_definition.py b/tests/message/unittest_message_definition.py index 51211b8ba2..c81763ff3d 100644 --- a/tests/message/unittest_message_definition.py +++ b/tests/message/unittest_message_definition.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import sys from unittest import mock diff --git a/tests/message/unittest_message_definition_store.py b/tests/message/unittest_message_definition_store.py index 96756ce866..a7d8352c64 100644 --- a/tests/message/unittest_message_definition_store.py +++ b/tests/message/unittest_message_definition_store.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from contextlib import redirect_stdout from io import StringIO diff --git a/tests/message/unittest_message_id_store.py b/tests/message/unittest_message_id_store.py index 4be49b1826..150ec8d698 100644 --- a/tests/message/unittest_message_id_store.py +++ b/tests/message/unittest_message_id_store.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from pathlib import Path from typing import Dict, ValuesView diff --git a/tests/primer/test_primer_external.py b/tests/primer/test_primer_external.py index c9ec9f167d..4a43259621 100644 --- a/tests/primer/test_primer_external.py +++ b/tests/primer/test_primer_external.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import json import logging import subprocess diff --git a/tests/primer/test_primer_stdlib.py b/tests/primer/test_primer_stdlib.py index cb84437fbd..59599ae00a 100644 --- a/tests/primer/test_primer_stdlib.py +++ b/tests/primer/test_primer_stdlib.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import contextlib import io diff --git a/tests/profile/test_profile_against_externals.py b/tests/profile/test_profile_against_externals.py index 643896623e..81819476eb 100644 --- a/tests/profile/test_profile_against_externals.py +++ b/tests/profile/test_profile_against_externals.py @@ -2,7 +2,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=missing-function-docstring diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py index ed3ad8f422..52b9198836 100644 --- a/tests/pyreverse/conftest.py +++ b/tests/pyreverse/conftest.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + from typing import Callable, Optional import pytest diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index 1a43daaa99..ecb601b0c6 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit test for the extensions.diadefslib modules.""" # pylint: disable=redefined-outer-name diff --git a/tests/pyreverse/test_diagrams.py b/tests/pyreverse/test_diagrams.py index 6bf0ab7064..f10af571db 100644 --- a/tests/pyreverse/test_diagrams.py +++ b/tests/pyreverse/test_diagrams.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit test for the diagrams modules.""" from typing import Callable diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py index c93e802338..8f053c1486 100644 --- a/tests/pyreverse/test_inspector.py +++ b/tests/pyreverse/test_inspector.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """For the visitors.diadefs module.""" # pylint: disable=redefined-outer-name diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index 2cc422c4ac..eda6e99743 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Unittest for the main module.""" import os import sys diff --git a/tests/pyreverse/test_printer.py b/tests/pyreverse/test_printer.py index 26faea74f4..9128fc6ddf 100644 --- a/tests/pyreverse/test_printer.py +++ b/tests/pyreverse/test_printer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from typing import Type diff --git a/tests/pyreverse/test_printer_factory.py b/tests/pyreverse/test_printer_factory.py index 1f00559551..97ee1179c8 100644 --- a/tests/pyreverse/test_printer_factory.py +++ b/tests/pyreverse/test_printer_factory.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit tests for pylint.pyreverse.printer_factory.""" diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index 095939ad66..da75fa9310 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Tests for pylint.pyreverse.utils.""" diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 3864bd51ee..d3b2d8f448 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Unit test for ``DiagramWriter``.""" diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 7011461b2c..d41ed13f4d 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Puts the check_parallel system under test.""" diff --git a/tests/test_epylint.py b/tests/test_epylint.py index 8e7ff9b79f..e1b090395a 100644 --- a/tests/test_epylint.py +++ b/tests/test_epylint.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Test for issue https://github.com/PyCQA/pylint/issues/4286.""" # pylint: disable=redefined-outer-name from pathlib import PosixPath diff --git a/tests/test_func.py b/tests/test_func.py index a372b4a284..2de78c5d0f 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Functional/non regression tests for pylint.""" diff --git a/tests/test_functional.py b/tests/test_functional.py index 86f8fe73b3..b328af5fcc 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Functional full-module tests for PyLint.""" import sys diff --git a/tests/test_functional_directories.py b/tests/test_functional_directories.py index a01f19cdd1..f757723322 100644 --- a/tests/test_functional_directories.py +++ b/tests/test_functional_directories.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Test that the directory structure of the functional tests is correct.""" from pathlib import Path diff --git a/tests/test_import_graph.py b/tests/test_import_graph.py index 7dc06307f9..9ea57e2cef 100644 --- a/tests/test_import_graph.py +++ b/tests/test_import_graph.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=redefined-outer-name diff --git a/tests/test_numversion.py b/tests/test_numversion.py index beffc31bb7..1bfb451da7 100644 --- a/tests/test_numversion.py +++ b/tests/test_numversion.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import pytest diff --git a/tests/test_pragma_parser.py b/tests/test_pragma_parser.py index 7bf00c7a34..d8dd508b08 100644 --- a/tests/test_pragma_parser.py +++ b/tests/test_pragma_parser.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import pytest from pylint.utils.pragma_parser import ( diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py index e4b20c0a87..303b433d35 100644 --- a/tests/test_pylint_runners.py +++ b/tests/test_pylint_runners.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + # pylint: disable=missing-module-docstring, missing-function-docstring import os import sys diff --git a/tests/test_regr.py b/tests/test_regr.py index 486c9b8b75..76bcafd68d 100644 --- a/tests/test_regr.py +++ b/tests/test_regr.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Non regression tests for pylint, which requires a too specific configuration to be incorporated in the automatic functional test framework diff --git a/tests/test_self.py b/tests/test_self.py index f2911ee096..36084b717a 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=too-many-public-methods diff --git a/tests/test_similar.py b/tests/test_similar.py index 53c71cbd1c..f595102bca 100644 --- a/tests/test_similar.py +++ b/tests/test_similar.py @@ -1,6 +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/main/LICENSE - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import contextlib import os diff --git a/tests/testutils/dummy_checker.py b/tests/testutils/dummy_checker.py index e69de29bb2..e8a8ff79fd 100644 --- a/tests/testutils/dummy_checker.py +++ b/tests/testutils/dummy_checker.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/tests/testutils/test_configuration_test.py b/tests/testutils/test_configuration_test.py index 195c4415c5..2efa65df37 100644 --- a/tests/testutils/test_configuration_test.py +++ b/tests/testutils/test_configuration_test.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + import logging from pathlib import Path diff --git a/tests/testutils/test_decorator.py b/tests/testutils/test_decorator.py index 64594b39c3..42b88ab5c9 100644 --- a/tests/testutils/test_decorator.py +++ b/tests/testutils/test_decorator.py @@ -1,7 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors - +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import pytest diff --git a/tests/testutils/test_functional_testutils.py b/tests/testutils/test_functional_testutils.py index 136bda749d..f37ffb1ca1 100644 --- a/tests/testutils/test_functional_testutils.py +++ b/tests/testutils/test_functional_testutils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Tests for the functional test framework.""" diff --git a/tests/testutils/test_lint_module_output_update.py b/tests/testutils/test_lint_module_output_update.py index 332463b033..6d46f84b47 100644 --- a/tests/testutils/test_lint_module_output_update.py +++ b/tests/testutils/test_lint_module_output_update.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=redefined-outer-name from pathlib import Path diff --git a/tests/testutils/test_output_line.py b/tests/testutils/test_output_line.py index 6a0e355184..6e6335ecc5 100644 --- a/tests/testutils/test_output_line.py +++ b/tests/testutils/test_output_line.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=redefined-outer-name diff --git a/tests/testutils/test_package_to_lint.py b/tests/testutils/test_package_to_lint.py index a79faa60c3..c3d2a4ab4d 100644 --- a/tests/testutils/test_package_to_lint.py +++ b/tests/testutils/test_package_to_lint.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt from pylint.testutils.primer import PRIMER_DIRECTORY_PATH, PackageToLint diff --git a/tests/unittest_reporters_json.py b/tests/unittest_reporters_json.py index 66da977a37..421506fe50 100644 --- a/tests/unittest_reporters_json.py +++ b/tests/unittest_reporters_json.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Test for the JSON reporter.""" diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 4bbef21151..6db3f5a962 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=redefined-outer-name import sys diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index e69de29bb2..e8a8ff79fd 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -0,0 +1,3 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt diff --git a/tests/utils/unittest_ast_walker.py b/tests/utils/unittest_ast_walker.py index 8f24bdaaa3..ec271bee78 100644 --- a/tests/utils/unittest_ast_walker.py +++ b/tests/utils/unittest_ast_walker.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import warnings from typing import Dict, Set diff --git a/tests/utils/unittest_utils.py b/tests/utils/unittest_utils.py index 2f9ed9f4b7..c802c40e2d 100644 --- a/tests/utils/unittest_utils.py +++ b/tests/utils/unittest_utils.py @@ -1,6 +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/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/graphs/contributors +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt import io From e1ad7f26415fe74585e3f65aceaf5852672d67d9 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 24 Mar 2022 12:43:05 +0100 Subject: [PATCH 302/357] Upgrade documentation for contributors.txt --- .github/PULL_REQUEST_TEMPLATE.md | 3 ++- doc/development_guide/contribute.rst | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 46b9eb71a1..c1c6615b09 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,11 +3,12 @@ Thank you for submitting a PR to pylint! To ease the process of reviewing your PR, do make sure to complete the following boxes. -- [ ] Add yourself to ``CONTRIBUTORS.txt`` if you are a new contributor. - [ ] Add a ChangeLog entry describing what your PR does. - [ ] If it's a new feature, or an important bug fix, add a What's New entry in `doc/whatsnew/`. - [ ] Write a good description on what the PR does. +- [ ] If you used multiple emails or multiple names when contributing, add your mails + and preferred name in ``script/.contributors_aliases.json`` --> ## Type of Changes diff --git a/doc/development_guide/contribute.rst b/doc/development_guide/contribute.rst index aa975bb278..a157532406 100644 --- a/doc/development_guide/contribute.rst +++ b/doc/development_guide/contribute.rst @@ -87,7 +87,8 @@ of Pylint as it gives you access to the latest ``ast`` parser. - Add a short entry in :file:`doc/whatsnew/VERSION.rst`. -- Add yourself to the `CONTRIBUTORS.txt` file. +- If you used multiple emails or multiple names when contributing, add your mails + and preferred name in the ``script/.contributors_aliases.json`` file. - Write a comprehensive commit message @@ -100,8 +101,6 @@ of Pylint as it gives you access to the latest ``ast`` parser. - Send a pull request from GitHub (see `About pull requests`_ for more insight about this topic) - - .. _`Closing issues via commit messages`: https://help.github.com/articles/closing-issues-via-commit-messages/ .. _`About pull requests`: https://help.github.com/articles/using-pull-requests/ .. _tox: https://tox.readthedocs.io/en/latest/ From 7965f08e87a3232aceb75a2508ec52b7afbc9fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:15:09 +0100 Subject: [PATCH 303/357] Add bad-open-mode and unspecified-encoding documentation (#5954) Co-authored-by: Vladyslav Krylasov --- doc/data/messages/b/bad-open-mode/bad.py | 3 +++ doc/data/messages/b/bad-open-mode/good.py | 3 +++ doc/data/messages/u/unspecified-encoding/bad.py | 3 +++ doc/data/messages/u/unspecified-encoding/good.py | 3 +++ pylint/checkers/stdlib.py | 2 +- 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 doc/data/messages/b/bad-open-mode/bad.py create mode 100644 doc/data/messages/b/bad-open-mode/good.py create mode 100644 doc/data/messages/u/unspecified-encoding/bad.py create mode 100644 doc/data/messages/u/unspecified-encoding/good.py diff --git a/doc/data/messages/b/bad-open-mode/bad.py b/doc/data/messages/b/bad-open-mode/bad.py new file mode 100644 index 0000000000..28cfb62283 --- /dev/null +++ b/doc/data/messages/b/bad-open-mode/bad.py @@ -0,0 +1,3 @@ +def foo(file_path): + with open(file_path, "rwx") as file: # [bad-open-mode] + contents = file.read() diff --git a/doc/data/messages/b/bad-open-mode/good.py b/doc/data/messages/b/bad-open-mode/good.py new file mode 100644 index 0000000000..64a853b38c --- /dev/null +++ b/doc/data/messages/b/bad-open-mode/good.py @@ -0,0 +1,3 @@ +def foo(file_path): + with open(file_path, "r") as file: + contents = file.read() diff --git a/doc/data/messages/u/unspecified-encoding/bad.py b/doc/data/messages/u/unspecified-encoding/bad.py new file mode 100644 index 0000000000..4645923c58 --- /dev/null +++ b/doc/data/messages/u/unspecified-encoding/bad.py @@ -0,0 +1,3 @@ +def foo(file_path): + with open(file_path) as file: # [unspecified-encoding] + contents = file.read() diff --git a/doc/data/messages/u/unspecified-encoding/good.py b/doc/data/messages/u/unspecified-encoding/good.py new file mode 100644 index 0000000000..a267a36070 --- /dev/null +++ b/doc/data/messages/u/unspecified-encoding/good.py @@ -0,0 +1,3 @@ +def foo(file_path): + with open(file_path, encoding="utf-8") as file: + contents = file.read() diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 007a4f9788..8761c45b3f 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -330,7 +330,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "bad-open-mode", "Python supports: r, w, a[, x] modes with b, +, " "and U (only with r) options. " - "See https://docs.python.org/2/library/functions.html#open", + "See https://docs.python.org/3/library/functions.html#open", ), "W1502": ( "Using datetime.time in a boolean context.", From 251802607688b47b91c1ccca54a715bd07d261a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:16:00 +0100 Subject: [PATCH 304/357] Add CI test for message documentation (#5956) --- .github/workflows/ci.yaml | 31 ++++++ doc/data/messages/e/empty-docstring/bad.py | 4 +- doc/test_messages_documentation.py | 110 +++++++++++++++++++++ 3 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 doc/test_messages_documentation.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5bf14f0601..8432c93fbe 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -148,6 +148,37 @@ jobs: . venv/bin/activate pytest tests/ -k unittest_spelling + documentation: + name: checks / documentation + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: prepare-base + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.0.0 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v3.0.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + - name: Restore Python virtual environment + id: cache-venv + uses: actions/cache@v3.0.0 + with: + path: venv + key: + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} + - name: Fail job if Python cache restore failed + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + echo "Failed to restore Python venv from cache" + exit 1 + - name: Run checks on documentation code examples + run: | + . venv/bin/activate + pytest doc/test_messages_documentation.py + prepare-tests-linux: name: tests / prepare / ${{ matrix.python-version }} / Linux runs-on: ubuntu-latest diff --git a/doc/data/messages/e/empty-docstring/bad.py b/doc/data/messages/e/empty-docstring/bad.py index 83d3601251..33dbb5f881 100644 --- a/doc/data/messages/e/empty-docstring/bad.py +++ b/doc/data/messages/e/empty-docstring/bad.py @@ -1,2 +1,2 @@ -def foo(): - pass # [emtpy-docstring] +def foo(): # [empty-docstring] + """""" diff --git a/doc/test_messages_documentation.py b/doc/test_messages_documentation.py new file mode 100644 index 0000000000..06c71d725c --- /dev/null +++ b/doc/test_messages_documentation.py @@ -0,0 +1,110 @@ +"""Functional tests for the code examples in the messages documentation.""" + +from collections import Counter +from pathlib import Path +from typing import Counter as CounterType +from typing import List, TextIO, Tuple + +import pytest + +from pylint import checkers +from pylint.config.config_initialization import _config_initialization +from pylint.lint import PyLinter +from pylint.message.message import Message +from pylint.testutils.constants import _EXPECTED_RE +from pylint.testutils.reporter_for_tests import FunctionalTestReporter + +MessageCounter = CounterType[Tuple[int, str]] + + +def get_functional_test_files_from_directory(input_dir: Path) -> List[Tuple[str, Path]]: + """Get all functional tests in the input_dir.""" + suite: List[Tuple[str, Path]] = [] + + for subdirectory in input_dir.iterdir(): + for message_dir in subdirectory.iterdir(): + if (message_dir / "good.py").exists(): + suite.append( + (message_dir.stem, message_dir / "good.py"), + ) + if (message_dir / "bad.py").exists(): + suite.append( + (message_dir.stem, message_dir / "bad.py"), + ) + return suite + + +TESTS_DIR = Path(__file__).parent.resolve() / "data" / "messages" +TESTS = get_functional_test_files_from_directory(TESTS_DIR) +TESTS_NAMES = [f"{t[0]}-{t[1].stem}" for t in TESTS] + + +class LintModuleTest: + def __init__(self, test_file: Tuple[str, Path]) -> None: + self._test_file = test_file + + _test_reporter = FunctionalTestReporter() + + self._linter = PyLinter() + self._linter.config.persistent = 0 + checkers.initialize(self._linter) + + _config_initialization( + self._linter, + args_list=[ + str(test_file[1]), + "--disable=all", + f"--enable={test_file[0]}", + ], + reporter=_test_reporter, + ) + + def runTest(self) -> None: + self._runTest() + + @staticmethod + def get_expected_messages(stream: TextIO) -> MessageCounter: + """Parse a file and get expected messages.""" + messages: MessageCounter = Counter() + for i, line in enumerate(stream): + match = _EXPECTED_RE.search(line) + if match is None: + continue + + line = match.group("line") + if line is None: + lineno = i + 1 + else: + lineno = int(line) + + for msg_id in match.group("msgs").split(","): + messages[lineno, msg_id.strip()] += 1 + return messages + + def _get_expected(self) -> MessageCounter: + """Get the expected messages for a file.""" + with open(self._test_file[1], encoding="utf8") as f: + expected_msgs = self.get_expected_messages(f) + return expected_msgs + + def _get_actual(self) -> MessageCounter: + """Get the actual messages after a run.""" + messages: List[Message] = self._linter.reporter.messages + messages.sort(key=lambda m: (m.line, m.symbol, m.msg)) + received_msgs: MessageCounter = Counter() + for msg in messages: + received_msgs[msg.line, msg.symbol] += 1 + return received_msgs + + def _runTest(self) -> None: + """Run the test and assert message differences.""" + self._linter.check([str(self._test_file[1])]) + expected_messages = self._get_expected() + actual_messages = self._get_actual() + assert expected_messages == actual_messages + + +@pytest.mark.parametrize("test_file", TESTS, ids=TESTS_NAMES) +def test_code_examples(test_file: Tuple[str, Path]) -> None: + lint_test = LintModuleTest(test_file) + lint_test.runTest() From 2b6184ba758ca7b2645ffccf678cdfa750d9f09c Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 24 Mar 2022 09:38:31 +0100 Subject: [PATCH 305/357] Use the new process created in https://github.com/PyCQA/astroid/pull/1438 --- doc/release.md | 100 +++++++++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/doc/release.md b/doc/release.md index 22194cd9b7..9052927b4f 100644 --- a/doc/release.md +++ b/doc/release.md @@ -1,52 +1,80 @@ -# Releasing a pylint version +# Releasing an astroid version -So, you want to release the `X.Y.Z` version of pylint ? +So, you want to release the `X.Y.Z` version of astroid ? -## Process +## Releasing a major or minor version -1. Check if the dependencies of the package are correct, make sure that astroid is - pinned to the latest version. -2. Install the release dependencies `pip3 install pre-commit tbump tox` -3. Bump the version by using `tbump X.Y.Z --no-tag --no-push` -4. Check the result (Do `git diff vX.Y.Z-1 ChangeLog doc/whatsnew/` in particular). -5. Move back to a dev version for pylint with `tbump`: +**Before releasing a major or minor version check if there are any unreleased commits on +the maintenance branch. If so, release a last patch release first. See +`Releasing a patch version`.** + +- Remove the empty changelog for the last unreleased patch version `X.Y-1.Z'`. (For + example: `v2.3.5`) +- Check the result of `git diff vX.Y-1.Z' ChangeLog`. (For example: + `git diff v2.3.4 ChangeLog`) +- Install the release dependencies: `pip3 install -r requirements_test.txt` +- Bump the version and release by using `tbump X.Y.0 --no-push --no-tag`. (For example: + `tbump 2.4.0 --no-push --no-tag`) +- Check the commit created with `git show` amend the commit if required. +- Move the `main` branch up to a dev version with `tbump`: ```bash -tbump X.Y.Z+1-dev0 --no-tag --no-push # You can interrupt after the first step -git commit -am "Move back to a dev version following X.Y.Z release" +tbump X.Y+1.0-dev0 --no-tag --no-push # You can interrupt after the first step +git commit -am "Upgrade the version to x.y+1.0-dev0 following x.y.0 release" ``` -4. Check the result -5. Open a merge request with the two commits (no one can push directly on `main`) -6. After the merge recover the merged commits and tag the first one (the version should - be `X.Y.Z`) as `vX.Y.Z` -7. Push the tag -8. Go to GitHub, click on "Releases" then on the `vX.Y.Z` tag, choose edit tag, and copy - past the changelog in the description. This will trigger the release to pypi. +For example: -## Post release +```bash +tbump 2.5.0-dev0 --no-tag --no-push +git commit -am "Upgrade the version to 2.5.0-dev0 following 2.4.0 release" +``` -### Merge tags in main for pre-commit +Check the commit and then push to a release branch -If the tag you just made is not part of the main branch, merge the tag `vX.Y.Z` in the -main branch by doing a history only merge. It's done in order to signal that this is an -official release tag, and for `pre-commit autoupdate` to works. +- Open a merge request with the two commits (no one can push directly on `main`) +- Trigger the "release tests" workflow in GitHub Actions. +- After the merge, recover the merged commits on `main` and tag the first one (the + version should be `X.Y.Z`) as `vX.Y.Z` (For example: `v2.4.0`) +- Push the tag. +- Release the version on GitHub with the same name as the tag and copy and paste the + appropriate changelog in the description. This triggers the PyPI release. +- Delete the `maintenance/X.Y-1.x` branch. (For example: `maintenance/2.3.x`) +- Create a `maintenance/X.Y.x` (For example: `maintenance/2.4.x` from the `v2.4.0` tag.) -```bash -git checkout main -git merge --no-edit --strategy=ours vX.Y.Z -git push -``` +## Backporting a fix from `main` to the maintenance branch + +Whenever a commit on `main` should be released in a patch release on the current +maintenance branch we cherry-pick the commit from `main`. + +- During the merge request on `main`, make sure that the changelog is for the patch + version `X.Y-1.Z'`. (For example: `v2.3.5`) +- After the PR is merged on `main` cherry-pick the commits on the `maintenance/X.Y.x` + branch (For example: from `maintenance/2.4.x` cherry-pick a commit from `main`) +- Release a patch version -### Milestone handling +## Releasing a patch version -We move issue that were not done in the next milestone and block release only if it's an -issue labelled as blocker. +We release patch versions when a crash or a bug is fixed on the main branch and has been +cherry-picked on the maintenance branch. -- Close milestone `X.Y.Z` -- Create the milestones for `X.Y.Z+1`, (or `X.Y+1.0`, and `X+1.0.0` if applicable) +- Check the result of `git diff vX.Y-1.Z-1 ChangeLog`. (For example: + `git diff v2.3.4 ChangeLog`) +- Install the release dependencies: `pip3 install -r requirements_test.txt` +- Bump the version and release by using `tbump X.Y-1.Z --no-push`. (For example: + `tbump 2.3.5 --no-push`) +- Check the result visually and then by triggering the "release tests" workflow in + GitHub Actions first. +- Push the tag. +- Release the version on GitHub with the same name as the tag and copy and paste the + appropriate changelog in the description. This triggers the PyPI release. +- Merge the `maintenance/X.Y.x` branch on the main branch. The main branch should have + the changelog for `X.Y-1.Z+1` (For example `v2.3.6`). This merge is required so + `pre-commit autoupdate` works for pylint. +- Fix version conflicts properly, or bump the version to `X.Y.0-devZ` (For example: + `2.4.0-dev6`) before pushing on the main branch -#### What's new +## Milestone handling -If it's a minor release, create a new `What's new in Pylint X.Y+1` document. Add it to -`doc/index.rst`. Take a look at the examples from `doc/whatsnew`. +We move issues that were not done to the next milestone and block releases only if there +are any open issues labelled as `blocker`. From c508d84858228dbd37214a9747b8073afa16e643 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 27 Feb 2022 22:56:43 +0100 Subject: [PATCH 306/357] Adaptation for pylint --- doc/release.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/release.md b/doc/release.md index 9052927b4f..25c87bbe57 100644 --- a/doc/release.md +++ b/doc/release.md @@ -1,6 +1,6 @@ -# Releasing an astroid version +# Releasing a pylint version -So, you want to release the `X.Y.Z` version of astroid ? +So, you want to release the `X.Y.Z` version of pylint ? ## Releasing a major or minor version @@ -16,6 +16,8 @@ the maintenance branch. If so, release a last patch release first. See - Bump the version and release by using `tbump X.Y.0 --no-push --no-tag`. (For example: `tbump 2.4.0 --no-push --no-tag`) - Check the commit created with `git show` amend the commit if required. +- Create a new `What's new in Pylint X.Y+1` document. Add it to `doc/index.rst`. Take a + look at the examples from `doc/whatsnew`. Commit that with `git commit -am "wip"`. - Move the `main` branch up to a dev version with `tbump`: ```bash @@ -30,7 +32,8 @@ tbump 2.5.0-dev0 --no-tag --no-push git commit -am "Upgrade the version to 2.5.0-dev0 following 2.4.0 release" ``` -Check the commit and then push to a release branch +Check the commit, fixup the 'wip' commit with the what's new then push to a release +branch - Open a merge request with the two commits (no one can push directly on `main`) - Trigger the "release tests" workflow in GitHub Actions. From 40d9eae15e6727b9b6dca82da2b984b4223cc4a3 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 24 Mar 2022 13:41:18 +0100 Subject: [PATCH 307/357] Fix copyright in doc/test_messages_documentation.py --- doc/test_messages_documentation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/test_messages_documentation.py b/doc/test_messages_documentation.py index 06c71d725c..8cc311a705 100644 --- a/doc/test_messages_documentation.py +++ b/doc/test_messages_documentation.py @@ -1,3 +1,7 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + """Functional tests for the code examples in the messages documentation.""" from collections import Counter From 6bbb56539f27347fc8f69733e0ebbccd2318f287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 24 Mar 2022 15:48:21 +0100 Subject: [PATCH 308/357] Add documentation for access-member-before-definition (#5958) Co-authored-by: Vladyslav Krylasov --- doc/data/messages/a/access-member-before-definition/bad.py | 5 +++++ doc/data/messages/a/access-member-before-definition/good.py | 5 +++++ pylint/checkers/classes/class_checker.py | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 doc/data/messages/a/access-member-before-definition/bad.py create mode 100644 doc/data/messages/a/access-member-before-definition/good.py diff --git a/doc/data/messages/a/access-member-before-definition/bad.py b/doc/data/messages/a/access-member-before-definition/bad.py new file mode 100644 index 0000000000..cb17c86eef --- /dev/null +++ b/doc/data/messages/a/access-member-before-definition/bad.py @@ -0,0 +1,5 @@ +class Foo: + def __init__(self, param): + if self.param: # [access-member-before-definition] + pass + self.param = param diff --git a/doc/data/messages/a/access-member-before-definition/good.py b/doc/data/messages/a/access-member-before-definition/good.py new file mode 100644 index 0000000000..b2c877a4bd --- /dev/null +++ b/doc/data/messages/a/access-member-before-definition/good.py @@ -0,0 +1,5 @@ +class Foo: + def __init__(self, param): + self.param = param + if self.param: + pass diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 91343a5d26..7492c9b42c 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -830,7 +830,11 @@ def _check_typing_final(self, node: nodes.ClassDef) -> None: node=node, ) - @check_messages("unused-private-member", "attribute-defined-outside-init") + @check_messages( + "unused-private-member", + "attribute-defined-outside-init", + "access-member-before-definition", + ) def leave_classdef(self, node: nodes.ClassDef) -> None: """Checker for Class nodes. From bb8e098f5a8c18ac68cebdae12d48138bab858b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:28:53 +0100 Subject: [PATCH 309/357] Make ``arguments-differ`` check extra parameters for default values (#5539) Co-authored-by: Pierre Sassoulas Co-authored-by: Jacob Walls --- ChangeLog | 5 +++ doc/whatsnew/2.13.rst | 5 +++ pylint/checkers/classes/class_checker.py | 44 ++++++++++++++++++++---- tests/functional/a/arguments_differ.py | 32 +++++++++++++++++ tests/functional/a/arguments_differ.txt | 11 +++--- tests/functional/a/arguments_renamed.py | 4 +-- tests/functional/a/arguments_renamed.txt | 3 +- 7 files changed, 88 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index b82e345bb0..b75fe9515a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -480,6 +480,11 @@ Release date: TBA * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* ``arguments-differ`` will no longer complain about method redefinitions with extra parameters + that have default values. + + Closes #1556, #5338 + * Fixed false positive ``unexpected-keyword-arg`` for decorators. Closes #258 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index a47516e528..47e78e5207 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -470,6 +470,11 @@ Other Changes * The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests without these will trigger a ``DeprecationWarning``. +* ``arguments-differ`` will no longer complain about method redefinitions with extra parameters + that have default values. + + Closes #1556, #5338 + * Disables for ``deprecated-module`` and similar warnings for stdlib features deprecated in newer versions of Python no longer raise ``useless-suppression`` when linting with older Python interpreters where those features are not yet deprecated. diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 7492c9b42c..e258f6642a 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -191,15 +191,21 @@ def _has_different_parameters( overridden: List[nodes.AssignName], dummy_parameter_regex: Pattern, ) -> List[str]: - result = [] + result: List[str] = [] zipped = zip_longest(original, overridden) for original_param, overridden_param in zipped: - params = (original_param, overridden_param) - if not all(params): + if not overridden_param: return ["Number of parameters "] + if not original_param: + try: + overridden_param.parent.default_value(overridden_param.name) + continue + except astroid.NoDefault: + return ["Number of parameters "] + # check for the arguments' name - names = [param.name for param in params] + names = [param.name for param in (original_param, overridden_param)] if any(dummy_parameter_regex.match(name) for name in names): continue if original_param.name != overridden_param.name: @@ -211,6 +217,29 @@ def _has_different_parameters( return result +def _has_different_keyword_only_parameters( + original: List[nodes.AssignName], + overridden: List[nodes.AssignName], +) -> List[str]: + """Determine if the two methods have different keyword only parameters.""" + original_names = [i.name for i in original] + overridden_names = [i.name for i in overridden] + + if any(name not in overridden_names for name in original_names): + return ["Number of parameters "] + + for name in overridden_names: + if name in original_names: + continue + + try: + overridden[0].parent.default_value(name) + except astroid.NoDefault: + return ["Number of parameters "] + + return [] + + def _different_parameters( original: nodes.FunctionDef, overridden: nodes.FunctionDef, @@ -253,8 +282,8 @@ def _different_parameters( different_positional = _has_different_parameters( original_parameters, overridden_parameters, dummy_parameter_regex ) - different_kwonly = _has_different_parameters( - original_kwonlyargs, overridden.args.kwonlyargs, dummy_parameter_regex + different_kwonly = _has_different_keyword_only_parameters( + original_kwonlyargs, overridden.args.kwonlyargs ) if different_kwonly and different_positional: if "Number " in different_positional[0] and "Number " in different_kwonly[0]: @@ -483,7 +512,8 @@ def _has_same_layout_slots(slots, assigned_value): "%s %s %r method", "arguments-differ", "Used when a method has a different number of arguments than in " - "the implemented interface or in an overridden method.", + "the implemented interface or in an overridden method. Extra arguments " + "with default values are ignored.", ), "W0222": ( "Signature differs from %s %r method", diff --git a/tests/functional/a/arguments_differ.py b/tests/functional/a/arguments_differ.py index d6689d9202..1dbd40086e 100644 --- a/tests/functional/a/arguments_differ.py +++ b/tests/functional/a/arguments_differ.py @@ -252,6 +252,7 @@ def meth(self, _arg, dummy): import typing # pylint: disable=wrong-import-position from typing import Dict # pylint: disable=wrong-import-position + class ParentT1: def func(self, user_input: Dict[str, int]) -> None: pass @@ -326,3 +327,34 @@ class Foo2(AbstractFoo): def kwonly_6(self, first, *args, **kwargs): # valid override "One positional with the rest variadics to pass through parent params" + + +# Adding arguments with default values to a child class is valid +# See: +# https://github.com/PyCQA/pylint/issues/1556 +# https://github.com/PyCQA/pylint/issues/5338 + + +class BaseClass: + def method(self, arg: str): + print(self, arg) + + +class DerivedClassWithAnnotation(BaseClass): + def method(self, arg: str, param1: int = 42, *, param2: int = 42): + print(arg, param1, param2) + + +class DerivedClassWithoutAnnotation(BaseClass): + def method(self, arg, param1=42, *, param2=42): + print(arg, param1, param2) + + +class AClass: + def method(self, *, arg1): + print(self) + + +class ClassWithNewNonDefaultKeywordOnly(AClass): + def method(self, *, arg2, arg1=None): # [arguments-differ] + ... diff --git a/tests/functional/a/arguments_differ.txt b/tests/functional/a/arguments_differ.txt index 07ef79a0f9..bb1abb2eb9 100644 --- a/tests/functional/a/arguments_differ.txt +++ b/tests/functional/a/arguments_differ.txt @@ -5,8 +5,9 @@ arguments-differ:68:4:68:18:VarargsChild.has_kwargs:Variadics removed in overrid arguments-renamed:71:4:71:17:VarargsChild.no_kwargs:Parameter 'args' has been renamed to 'arg' in overridden 'VarargsChild.no_kwargs' method:UNDEFINED arguments-differ:144:4:144:12:StaticmethodChild2.func:Number of parameters was 1 in 'Staticmethod.func' and is now 2 in overridden 'StaticmethodChild2.func' method:UNDEFINED arguments-differ:180:4:180:12:SecondChangesArgs.test:Number of parameters was 2 in 'FirstHasArgs.test' and is now 4 in overridden 'SecondChangesArgs.test' method:UNDEFINED -arguments-differ:306:4:306:16:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method:UNDEFINED -arguments-differ:309:4:309:16:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method:UNDEFINED -arguments-differ:312:4:312:16:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method:UNDEFINED -arguments-differ:315:4:315:16:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method:UNDEFINED -arguments-differ:318:4:318:16:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method:UNDEFINED +arguments-differ:307:4:307:16:Foo.kwonly_1:Number of parameters was 4 in 'AbstractFoo.kwonly_1' and is now 3 in overridden 'Foo.kwonly_1' method:UNDEFINED +arguments-differ:310:4:310:16:Foo.kwonly_2:Number of parameters was 3 in 'AbstractFoo.kwonly_2' and is now 2 in overridden 'Foo.kwonly_2' method:UNDEFINED +arguments-differ:313:4:313:16:Foo.kwonly_3:Number of parameters was 3 in 'AbstractFoo.kwonly_3' and is now 3 in overridden 'Foo.kwonly_3' method:UNDEFINED +arguments-differ:316:4:316:16:Foo.kwonly_4:Number of parameters was 3 in 'AbstractFoo.kwonly_4' and is now 3 in overridden 'Foo.kwonly_4' method:UNDEFINED +arguments-differ:319:4:319:16:Foo.kwonly_5:Variadics removed in overridden 'Foo.kwonly_5' method:UNDEFINED +arguments-differ:359:4:359:14:ClassWithNewNonDefaultKeywordOnly.method:Number of parameters was 2 in 'AClass.method' and is now 3 in overridden 'ClassWithNewNonDefaultKeywordOnly.method' method:UNDEFINED diff --git a/tests/functional/a/arguments_renamed.py b/tests/functional/a/arguments_renamed.py index e24b670fda..2b5474c831 100644 --- a/tests/functional/a/arguments_renamed.py +++ b/tests/functional/a/arguments_renamed.py @@ -40,7 +40,7 @@ class Child(Parent): def test(self, arg1): # [arguments-renamed] return arg1 + 1 - def kwargs_test(self, arg, *, value1, var2): #[arguments-renamed] + def kwargs_test(self, arg, *, value1, var2): #[arguments-differ] print(f"keyword parameters are {value1} and {var2}.") class Child2(Parent): @@ -48,7 +48,7 @@ class Child2(Parent): def test(self, var): # [arguments-renamed] return var + 1 - def kwargs_test(self, *, var1, kw2): #[arguments-renamed, arguments-differ] + def kwargs_test(self, *, var1, kw2): #[arguments-differ] print(f"keyword parameters are {var1} and {kw2}.") class ParentDefaults(object): diff --git a/tests/functional/a/arguments_renamed.txt b/tests/functional/a/arguments_renamed.txt index 7f6466ba8d..47d4188dd2 100644 --- a/tests/functional/a/arguments_renamed.txt +++ b/tests/functional/a/arguments_renamed.txt @@ -2,10 +2,9 @@ arguments-renamed:17:4:17:12:Orange.brew:Parameter 'fruit_name' has been renamed arguments-renamed:20:4:20:26:Orange.eat_with_condiment:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'Orange.eat_with_condiment' method:UNDEFINED arguments-differ:27:4:27:26:Banana.eat_with_condiment:Number of parameters was 3 in 'Fruit.eat_with_condiment' and is now 4 in overridden 'Banana.eat_with_condiment' method:UNDEFINED arguments-renamed:40:4:40:12:Child.test:Parameter 'arg' has been renamed to 'arg1' in overridden 'Child.test' method:UNDEFINED -arguments-renamed:43:4:43:19:Child.kwargs_test:Parameter 'var1' has been renamed to 'value1' in overridden 'Child.kwargs_test' method:UNDEFINED +arguments-differ:43:4:43:19:Child.kwargs_test:Number of parameters was 4 in 'Parent.kwargs_test' and is now 4 in overridden 'Child.kwargs_test' method:UNDEFINED arguments-renamed:48:4:48:12:Child2.test:Parameter 'arg' has been renamed to 'var' in overridden 'Child2.test' method:UNDEFINED arguments-differ:51:4:51:19:Child2.kwargs_test:Number of parameters was 4 in 'Parent.kwargs_test' and is now 3 in overridden 'Child2.kwargs_test' method:UNDEFINED -arguments-renamed:51:4:51:19:Child2.kwargs_test:Parameter 'var2' has been renamed to 'kw2' in overridden 'Child2.kwargs_test' method:UNDEFINED arguments-renamed:67:4:67:13:ChildDefaults.test1:Parameter 'barg' has been renamed to 'param2' in overridden 'ChildDefaults.test1' method:UNDEFINED arguments-renamed:95:8:95:16:FruitOverrideConditional.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overridden 'FruitOverrideConditional.brew' method:UNDEFINED arguments-differ:99:12:99:34:FruitOverrideConditional.eat_with_condiment:Number of parameters was 3 in 'FruitConditional.eat_with_condiment' and is now 4 in overridden 'FruitOverrideConditional.eat_with_condiment' method:UNDEFINED From fd91d04a2f946c6fe8b31b6f9217a61caab55c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:30:02 +0100 Subject: [PATCH 310/357] Create a ``TypeVar`` style for ``invalid-name`` (#5894) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- ChangeLog | 5 ++ doc/user_guide/options.rst | 17 ++++ doc/whatsnew/2.13.rst | 5 ++ pylint/checkers/base.py | 87 ++++++++++++------- pylint/constants.py | 1 + pylint/utils/linterstats.py | 7 ++ pylintrc | 3 + tests/checkers/unittest_base.py | 4 +- .../t/typevar_naming_style_default.py | 49 +++++++++++ .../t/typevar_naming_style_default.txt | 12 +++ .../functional/t/typevar_naming_style_rgx.py | 15 ++++ .../functional/t/typevar_naming_style_rgx.rc | 2 + .../functional/t/typevar_naming_style_rgx.txt | 3 + 13 files changed, 176 insertions(+), 34 deletions(-) create mode 100644 tests/functional/t/typevar_naming_style_default.py create mode 100644 tests/functional/t/typevar_naming_style_default.txt create mode 100644 tests/functional/t/typevar_naming_style_rgx.py create mode 100644 tests/functional/t/typevar_naming_style_rgx.rc create mode 100644 tests/functional/t/typevar_naming_style_rgx.txt diff --git a/ChangeLog b/ChangeLog index b75fe9515a..22cd0aed30 100644 --- a/ChangeLog +++ b/ChangeLog @@ -522,6 +522,11 @@ Release date: TBA Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) +* Improve ``invalid-name`` check for ``TypeVar`` names. + The accepted pattern can be customized with ``--typevar-rgx``. + + Closes #3401 + * Added new checker ``typevar-name-missing-variance``. Emitted when a covariant or contravariant ``TypeVar`` does not end with ``_co`` or ``_contra`` respectively or when a ``TypeVar`` is not either but has a suffix. diff --git a/doc/user_guide/options.rst b/doc/user_guide/options.rst index 898c7a60a7..6e1535e249 100644 --- a/doc/user_guide/options.rst +++ b/doc/user_guide/options.rst @@ -39,6 +39,8 @@ name is found in, and not the type of object assigned. +--------------------+---------------------------------------------------------------------------------------------------+ | ``inlinevar`` | Loop variables in list comprehensions and generator expressions. | +--------------------+---------------------------------------------------------------------------------------------------+ +| ``typevar`` | Type variable declared with ``TypeVar``. | ++--------------------+---------------------------------------------------------------------------------------------------+ Default behavior ~~~~~~~~~~~~~~~~ @@ -82,6 +84,19 @@ Following options are exposed: .. option:: --inlinevar-naming-style=