diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 730f3dd08b..87cdf278a9 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -6,7 +6,7 @@ on: env: # Also change CACHE_VERSION in the other workflows - CACHE_VERSION: 28 + CACHE_VERSION: 31 DEFAULT_PYTHON: "3.10" jobs: diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 6cdca8fda6..72c2ba1e5a 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -8,7 +8,7 @@ on: pull_request: ~ env: - CACHE_VERSION: 28 + CACHE_VERSION: 31 DEFAULT_PYTHON: "3.10" PRE_COMMIT_CACHE: ~/.cache/pre-commit diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index a865b18755..da1808abe3 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -13,7 +13,7 @@ on: - ".github/workflows/primer-test.yaml" env: - CACHE_VERSION: 28 + CACHE_VERSION: 31 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/primer_comment.yaml b/.github/workflows/primer_comment.yaml index a15c7aea32..1d4292ad0c 100644 --- a/.github/workflows/primer_comment.yaml +++ b/.github/workflows/primer_comment.yaml @@ -14,7 +14,7 @@ on: env: # This needs to be the SAME as in the Main and PR job - CACHE_VERSION: 28 + CACHE_VERSION: 31 permissions: contents: read diff --git a/.github/workflows/primer_run_main.yaml b/.github/workflows/primer_run_main.yaml index 7b8609a6f2..862ef1aa0d 100644 --- a/.github/workflows/primer_run_main.yaml +++ b/.github/workflows/primer_run_main.yaml @@ -16,7 +16,7 @@ concurrency: env: # This needs to be the SAME as in the PR and comment job - CACHE_VERSION: 28 + CACHE_VERSION: 31 jobs: run-primer: diff --git a/.github/workflows/primer_run_pr.yaml b/.github/workflows/primer_run_pr.yaml index 22ba26dd53..7124b1d166 100644 --- a/.github/workflows/primer_run_pr.yaml +++ b/.github/workflows/primer_run_pr.yaml @@ -25,7 +25,7 @@ concurrency: env: # This needs to be the SAME as in the Main and comment job - CACHE_VERSION: 28 + CACHE_VERSION: 31 jobs: run-primer: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 03643f90d6..f03f685e88 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -10,7 +10,7 @@ on: - doc/data/messages/** env: - CACHE_VERSION: 28 + CACHE_VERSION: 31 jobs: tests-linux: diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9464d72ef4..e7d4f8f3a1 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -185,7 +185,7 @@ contributors: - Marco Forte - Ionel Maries Cristian - Gergely Kalmár -- Drummond Ogilvie +- Drum Ogilvie - Daniel Harding - Damien Baty - Benjamin Drung : contributing Debian Developer @@ -499,6 +499,7 @@ contributors: - Daniele Procida - Daniela Plascencia - Daniel R. Neal (danrneal) +- Daniel Mouritzen - Daniel Draper - Daniel Dorani (doranid) - Daniel Brookman <53625739+dbrookman@users.noreply.github.com> diff --git a/doc/whatsnew/2/2.15/index.rst b/doc/whatsnew/2/2.15/index.rst index 5896de210f..48c4479ba9 100644 --- a/doc/whatsnew/2/2.15/index.rst +++ b/doc/whatsnew/2/2.15/index.rst @@ -29,6 +29,54 @@ Marc Byrne became a maintainer, welcome to the team ! .. towncrier release notes start +What's new in Pylint 2.15.5? +---------------------------- +Release date: 2022-10-21 + + +False Positives Fixed +--------------------- + +- Fix a false positive for ``simplify-boolean-expression`` when multiple values + are inferred for a constant. + + Closes #7626 (`#7626 `_) + + + +Other Bug Fixes +--------------- + +- Remove ``__index__`` dunder method call from ``unnecessary-dunder-call`` + check. + + Closes #6795 (`#6795 `_) + +- Fixed a multi-processing crash that prevents using any more than 1 thread on + MacOS. + + The returned module objects and errors that were cached by the linter plugin + loader + cannot be reliably pickled. This means that ``dill`` would throw an error + when + attempting to serialise the linter object for multi-processing use. + + Closes #7635. (`#7635 `_) + + + +Other Changes +------------- + +- Add a keyword-only ``compare_constants`` argument to ``safe_infer``. + + Refs #7626 (`#7626 `_) + +- Sort ``--generated-rcfile`` output. + + Refs #7655 (`#7655 `_) + + What's new in Pylint 2.15.4? ---------------------------- Release date: 2022-10-10 diff --git a/examples/pylintrc b/examples/pylintrc index a955c56ed6..a461b24d5f 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -101,196 +101,6 @@ unsafe-load-any-extension=no #verbose= -[REPORTS] - -# 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 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=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. -msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -#output-format= - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, -# UNDEFINED. -confidence=HIGH, - CONTROL_FLOW, - INFERENCE, - INFERENCE_FAILURE, - UNDEFINED - -# Disable the message, report, category or checker with the given id(s). You -# 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 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 -# --disable=W". -disable=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead - -# 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 (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[METHOD_ARGS] - -# List of qualified names (i.e., library.method) which require a timeout -# parameter e.g. 'requests.api.get,requests.api.post' -timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception - - -[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 - - -[DESIGN] - -# List of regular expressions of class ancestor names to ignore when counting -# public methods (see R0903) -exclude-too-few-public-methods= - -# List of qualified class names to ignore when counting class parents (see -# R0901) -ignored-parents= - -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[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 - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules= - -# Output a graph (.gv or any supported image format) of external dependencies -# to the given file (report RP0402 must not be disabled). -ext-import-graph= - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be -# disabled). -import-graph= - -# Output a graph (.gv or any supported image format) of internal dependencies -# to the given file (report RP0402 must 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= - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - [BASIC] # Naming style matching correct argument names. @@ -427,89 +237,78 @@ variable-naming-style=snake_case #variable-rgx= -[SIMILARITIES] +[CLASSES] -# Comments are removed from the similarity computation -ignore-comments=yes +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no -# Docstrings are removed from the similarity computation -ignore-docstrings=yes +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ -# Imports are removed from the similarity computation -ignore-imports=yes +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make -# Signatures are removed from the similarity computation -ignore-signatures=yes +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls -# Minimum lines number of a similarity. -min-similarity-lines=4 +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls -[LOGGING] +[DESIGN] -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= +# Maximum number of arguments for function / method. +max-args=5 -[VARIABLES] +# Maximum number of attributes for a class (see R0902). +max-attributes=7 -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes +# Maximum number of branch for function / method body. +max-branches=12 -# List of names allowed to shadow builtins -allowed-redefined-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io +# Maximum number of locals for function / method body. +max-locals=15 +# Maximum number of parents for a class (see R0901). +max-parents=7 -[SPELLING] +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 +# Maximum number of return / yield for function / method body. +max-returns=6 -# Spelling dictionary name. Available dictionaries: en (aspell), en_AU -# (aspell), en_CA (aspell), en_GB (aspell), en_US (aspell). -spelling-dict= +# Maximum number of statements in function / method body. +max-statements=50 -# List of comma separated words that should be considered directives if they -# appear at the beginning of a comment and should not be checked. -spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 -# List of comma separated words that should not be checked. -spelling-ignore-words= -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= +[EXCEPTIONS] -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=BaseException, + Exception [FORMAT] @@ -542,6 +341,96 @@ single-line-class-stmt=no single-line-if-stmt=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 + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must 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= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# 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 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 +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead + +# 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 (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. @@ -553,6 +442,96 @@ notes=FIXME, notes-rgx= +[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 + + +[REPORTS] + +# 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 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=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. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: en (aspell), en_AU +# (aspell), en_CA (aspell), en_GB (aspell), en_US (aspell). +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[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 + + [TYPECHECK] # List of decorators that produce context managers, such as @@ -607,12 +586,33 @@ mixin-class-rgx=.*[Mm]ixin signature-mutators= -[STRING] +[VARIABLES] -# 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 +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= -# 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 +# 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= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 189471a6db..07f0cd7c03 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "2.15.4" +__version__ = "2.15.5" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff --git a/pylint/checkers/dunder_methods.py b/pylint/checkers/dunder_methods.py index 1b61be7d4f..2e5e54a57c 100644 --- a/pylint/checkers/dunder_methods.py +++ b/pylint/checkers/dunder_methods.py @@ -100,7 +100,6 @@ "__complex__": "Use complex built-in function", "__int__": "Use int built-in function", "__float__": "Use float built-in function", - "__index__": "Use index method", "__round__": "Use round built-in function", "__trunc__": "Use math.trunc function", "__floor__": "Use math.floor function", @@ -125,7 +124,8 @@ class DunderCallChecker(BaseChecker): We exclude __new__, __subclasses__, __init_subclass__, __set_name__, __class_getitem__, __missing__, __exit__, __await__, __aexit__, __getnewargs_ex__, __getnewargs__, __getstate__, - __setstate__, __reduce__, __reduce_ex__ + __setstate__, __reduce__, __reduce_ex__, + and __index__ (see https://github.com/PyCQA/pylint/issues/6795) since these either have no alternative method of being called or have a genuine use case for being called manually. diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 3a59b3d820..005f0e03c9 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -21,7 +21,7 @@ from pylint import checkers from pylint.checkers import utils from pylint.checkers.utils import node_frame_class -from pylint.interfaces import HIGH +from pylint.interfaces import HIGH, INFERENCE if TYPE_CHECKING: from pylint.lint import PyLinter @@ -1493,11 +1493,10 @@ def visit_return(self, node: nodes.Return | nodes.Assign) -> None: ): return - inferred_truth_value = utils.safe_infer(truth_value) + inferred_truth_value = utils.safe_infer(truth_value, compare_constants=True) if inferred_truth_value is None or inferred_truth_value == astroid.Uninferable: - truth_boolean_value = True - else: - truth_boolean_value = inferred_truth_value.bool_value() + return + truth_boolean_value = inferred_truth_value.bool_value() if truth_boolean_value is False: message = "simplify-boolean-expression" @@ -1505,7 +1504,7 @@ def visit_return(self, node: nodes.Return | nodes.Assign) -> None: else: message = "consider-using-ternary" suggestion = f"{truth_value.as_string()} if {cond.as_string()} else {false_value.as_string()}" - self.add_message(message, node=node, args=(suggestion,)) + self.add_message(message, node=node, args=(suggestion,), confidence=INFERENCE) def _append_context_managers_to_stack(self, node: nodes.Assign) -> None: if _is_inside_context_manager(node): diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f2518beae6..d97f352f6c 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -6,7 +6,6 @@ from __future__ import annotations -import fnmatch import heapq import itertools import operator @@ -14,7 +13,6 @@ import shlex import sys import types -from collections import deque from collections.abc import Callable, Iterator, Sequence from functools import singledispatch from re import Pattern @@ -36,6 +34,7 @@ is_inside_abstract_class, is_iterable, is_mapping, + is_module_ignored, is_node_in_type_annotation_context, is_overload_stub, is_postponed_evaluation_enabled, @@ -116,32 +115,8 @@ def _is_owner_ignored(owner, attrname, ignored_classes, ignored_modules): matches any name from the *ignored_classes* or if its qualified name can be found in *ignored_classes*. """ - ignored_modules = set(ignored_modules) - module_name = owner.root().name - module_qname = owner.root().qname() - - for ignore in ignored_modules: - # Try to match the module name / fully qualified name directly - if module_qname in ignored_modules or module_name in ignored_modules: - return True - - # Try to see if the ignores pattern match against the module name. - if fnmatch.fnmatch(module_qname, ignore): - return True - - # 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 = "" - - while parts: - part = parts.popleft() - if not current_module: - current_module = part - else: - current_module += f".{part}" - if current_module in ignored_modules: - return True + if is_module_ignored(owner.root(), ignored_modules): + return True # Match against ignored classes. ignored_classes = set(ignored_classes) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index d00b8a6537..b7e0596d16 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -7,11 +7,13 @@ from __future__ import annotations import builtins +import fnmatch import itertools import numbers import re import string import warnings +from collections import deque from collections.abc import Iterable, Iterator from functools import lru_cache, partial from re import Match @@ -1331,12 +1333,18 @@ def _get_python_type_of_node(node: nodes.NodeNG) -> str | None: @lru_cache(maxsize=1024) def safe_infer( - node: nodes.NodeNG, context: InferenceContext | None = None + node: nodes.NodeNG, + context: InferenceContext | None = None, + *, + compare_constants: bool = False, ) -> InferenceResult | 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). + + If compare_constants is True and if multiple constants are inferred, + unequal inferred values are also considered ambiguous and return None. """ inferred_types: set[str | None] = set() try: @@ -1355,6 +1363,13 @@ def safe_infer( 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 ( + compare_constants + and isinstance(inferred, nodes.Const) + and isinstance(value, nodes.Const) + and inferred.value != value.value + ): + return None if ( isinstance(inferred, nodes.FunctionDef) and inferred.args.args is not None @@ -1952,3 +1967,37 @@ def is_hashable(node: nodes.NodeNG) -> bool: return False except astroid.InferenceError: return True + + +def is_module_ignored( + module: nodes.Module, + ignored_modules: Iterable[str], +) -> bool: + ignored_modules = set(ignored_modules) + module_name = module.name + module_qname = module.qname() + + for ignore in ignored_modules: + # Try to match the module name / fully qualified name directly + if module_qname in ignored_modules or module_name in ignored_modules: + return True + + # Try to see if the ignores pattern match against the module name. + if fnmatch.fnmatch(module_qname, ignore): + return True + + # 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 = "" + + while parts: + part = parts.popleft() + if not current_module: + current_module = part + else: + current_module += f".{part}" + if current_module in ignored_modules: + return True + + return False diff --git a/pylint/config/arguments_manager.py b/pylint/config/arguments_manager.py index eda1a583d4..40058071cc 100644 --- a/pylint/config/arguments_manager.py +++ b/pylint/config/arguments_manager.py @@ -437,7 +437,10 @@ def generate_config( ) options_by_section = {} sections = [] - for group in self._arg_parser._action_groups: + for group in sorted( + self._arg_parser._action_groups, + key=lambda x: (x.title != "Main", x.title), + ): group_name = group.title assert group_name if group_name in skipsections: @@ -449,7 +452,7 @@ def generate_config( for i in group._group_actions if not isinstance(i, argparse._SubParsersAction) ] - for opt in option_actions: + for opt in sorted(option_actions, key=lambda x: x.option_strings[0][2:]): if "--help" in opt.option_strings: continue diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index cb528c5df7..47d5baaf52 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -296,7 +296,7 @@ def __init__( str, list[checkers.BaseChecker] ] = collections.defaultdict(list) """Dictionary of registered and initialized checkers.""" - self._dynamic_plugins: dict[str, ModuleType | ModuleNotFoundError] = {} + self._dynamic_plugins: dict[str, ModuleType | ModuleNotFoundError | bool] = {} """Set of loaded plugin names.""" # Attributes related to registering messages and their handling @@ -400,7 +400,15 @@ def load_plugin_configuration(self) -> None: "bad-plugin-value", args=(modname, module_or_error), line=0 ) elif hasattr(module_or_error, "load_configuration"): - module_or_error.load_configuration(self) + module_or_error.load_configuration(self) # type: ignore[union-attr] + + # We re-set all the dictionary values to True here to make sure the dict + # is pickle-able. This is only a problem in multiprocessing/parallel mode. + # (e.g. invoking pylint -j 2) + self._dynamic_plugins = { + modname: not isinstance(val, ModuleNotFoundError) + for modname, val in self._dynamic_plugins.items() + } def _load_reporters(self, reporter_names: str) -> None: """Load the reporters if they are available on _reporters.""" diff --git a/pyproject.toml b/pyproject.toml index b6a494af81..6e542d48aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ # github actions if you are bumping astroid. # Pinned to dev of second minor update to allow editable installs and fix primer issues, # see https://github.com/PyCQA/astroid/issues/1341 - "astroid>=2.12.11,<=2.14.0-dev0", + "astroid>=2.12.12,<=2.14.0-dev0", "isort>=4.2.5,<6", "mccabe>=0.6,<0.8", "tomli>=1.1.0;python_version<'3.11'", diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 6b2dcdf2a4..2a3e043a46 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,7 +1,7 @@ -e .[testutils,spelling] # astroid dependency is also defined in pyproject.toml # You need to increment the CACHE_VERSION in github actions too -astroid==2.12.11 # Pinned to a specific version for tests +astroid==2.12.12 # Pinned to a specific version for tests typing-extensions~=4.3 pytest~=7.1 pytest-benchmark~=3.4 diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index 95808f651c..6563c71d46 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -261,6 +261,10 @@ "name": "Dimitri Prybysh", "team": "Maintainers" }, + "dmrtzn@gmail.com": { + "mails": ["dmrtzn@gmail.com"], + "name": "Daniel Mouritzen" + }, "drewrisinger@users.noreply.github.com": { "mails": ["drewrisinger@users.noreply.github.com"], "name": "Drew Risinger" @@ -438,7 +442,7 @@ }, "me@daogilvie.com": { "mails": ["me@daogilvie.com", "drum.ogilvie@ovo.com"], - "name": "Drummond Ogilvie" + "name": "Drum Ogilvie" }, "me@the-compiler.org": { "mails": [ diff --git a/script/create_contributor_list.py b/script/create_contributor_list.py index 9b336a01b1..7db3923f54 100644 --- a/script/create_contributor_list.py +++ b/script/create_contributor_list.py @@ -6,9 +6,10 @@ 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" +CWD = Path(".").absolute() +BASE_DIRECTORY = Path(__file__).parent.parent.absolute() +ALIASES_FILE = (BASE_DIRECTORY / "script/.contributors_aliases.json").relative_to(CWD) +DEFAULT_CONTRIBUTOR_PATH = (BASE_DIRECTORY / "CONTRIBUTORS.txt").relative_to(CWD) def main(): diff --git a/tbump.toml b/tbump.toml index 3a7cfaa9b1..ed6d6d5f3f 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/PyCQA/pylint" [version] -current = "2.15.4" +current = "2.15.5" regex = ''' ^(?P0|[1-9]\d*) \. diff --git a/tests/functional/r/regression_02/regression_no_member_7631.py b/tests/functional/r/regression_02/regression_no_member_7631.py new file mode 100644 index 0000000000..758aad057a --- /dev/null +++ b/tests/functional/r/regression_02/regression_no_member_7631.py @@ -0,0 +1,16 @@ +"""Regression test from https://github.com/PyCQA/pylint/issues/7631 +The following code should NOT raise no-member. +""" +# pylint: disable=missing-docstring,too-few-public-methods + +class Base: + attr: int = 2 + +class Parent(Base): + attr: int + +class Child(Parent): + attr = 2 + + def __init__(self): + self.attr = self.attr | 4 diff --git a/tests/functional/t/ternary.py b/tests/functional/t/ternary.py index 58171942fb..48f97ffd97 100644 --- a/tests/functional/t/ternary.py +++ b/tests/functional/t/ternary.py @@ -1,18 +1,23 @@ """Test for old ternary constructs""" -from UNINFERABLE import condition, true_value, false_value, some_callable # pylint: disable=import-error +from UNINFERABLE import condition, some_callable, maybe_true, maybe_false # pylint: disable=import-error -SOME_VALUE1 = true_value if condition else false_value -SOME_VALUE2 = condition and true_value or false_value # [consider-using-ternary] +TRUE_VALUE = True +FALSE_VALUE = False + +SOME_VALUE1 = TRUE_VALUE if condition else FALSE_VALUE +SOME_VALUE2 = condition and TRUE_VALUE or FALSE_VALUE # [consider-using-ternary] +NOT_SIMPLIFIABLE_1 = maybe_true if condition else maybe_false +NOT_SIMPLIFIABLE_2 = condition and maybe_true or maybe_false SOME_VALUE3 = condition def func1(): """Ternary return value correct""" - return true_value if condition else false_value + return TRUE_VALUE if condition else FALSE_VALUE def func2(): """Ternary return value incorrect""" - return condition and true_value or false_value # [consider-using-ternary] + return condition and TRUE_VALUE or FALSE_VALUE # [consider-using-ternary] SOME_VALUE4 = some_callable(condition) and 'ERROR' or 'SUCCESS' # [consider-using-ternary] @@ -30,10 +35,23 @@ def func2(): def func4(): """"Using a Name as a condition but still emits""" truth_value = 42 - return condition and truth_value or false_value # [consider-using-ternary] + return condition and truth_value or FALSE_VALUE # [consider-using-ternary] def func5(): """"Using a Name that infers to False as a condition does not emit""" falsy_value = False - return condition and falsy_value or false_value # [simplify-boolean-expression] + return condition and falsy_value or FALSE_VALUE # [simplify-boolean-expression] + + +def func_control_flow(): + """Redefining variables should invalidate simplify-boolean-expression.""" + flag_a = False + flag_b = False + for num in range(2): + if num == 1: + flag_a = True + else: + flag_b = True + multiple = (flag_a and flag_b) or func5() + return multiple diff --git a/tests/functional/t/ternary.txt b/tests/functional/t/ternary.txt index bdec7bcc43..ca93acd2fa 100644 --- a/tests/functional/t/ternary.txt +++ b/tests/functional/t/ternary.txt @@ -1,8 +1,8 @@ -consider-using-ternary:5:0:5:53::Consider using ternary (true_value if condition else false_value):UNDEFINED -consider-using-ternary:15:4:15:50:func2:Consider using ternary (true_value if condition else false_value):UNDEFINED -consider-using-ternary:18:0:18:63::Consider using ternary ('ERROR' if some_callable(condition) else 'SUCCESS'):UNDEFINED -consider-using-ternary:19:0:19:60::Consider using ternary ('greater' if SOME_VALUE1 > 3 else 'not greater'):UNDEFINED -consider-using-ternary:20:0:20:67::Consider using ternary ('both' if SOME_VALUE2 > 4 and SOME_VALUE3 else 'not'):UNDEFINED -simplify-boolean-expression:23:0:23:50::Boolean expression may be simplified to SOME_VALUE2:UNDEFINED -consider-using-ternary:33:4:33:51:func4:Consider using ternary (truth_value if condition else false_value):UNDEFINED -simplify-boolean-expression:39:4:39:51:func5:Boolean expression may be simplified to false_value:UNDEFINED +consider-using-ternary:8:0:8:53::Consider using ternary (TRUE_VALUE if condition else FALSE_VALUE):INFERENCE +consider-using-ternary:20:4:20:50:func2:Consider using ternary (TRUE_VALUE if condition else FALSE_VALUE):INFERENCE +consider-using-ternary:23:0:23:63::Consider using ternary ('ERROR' if some_callable(condition) else 'SUCCESS'):INFERENCE +consider-using-ternary:24:0:24:60::Consider using ternary ('greater' if SOME_VALUE1 > 3 else 'not greater'):INFERENCE +consider-using-ternary:25:0:25:67::Consider using ternary ('both' if SOME_VALUE2 > 4 and SOME_VALUE3 else 'not'):INFERENCE +simplify-boolean-expression:28:0:28:50::Boolean expression may be simplified to SOME_VALUE2:INFERENCE +consider-using-ternary:38:4:38:51:func4:Consider using ternary (truth_value if condition else FALSE_VALUE):INFERENCE +simplify-boolean-expression:44:4:44:51:func5:Boolean expression may be simplified to FALSE_VALUE:INFERENCE diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call.py b/tests/functional/u/unnecessary/unnecessary_dunder_call.py index cd1f21286d..18e4ef855c 100644 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call.py +++ b/tests/functional/u/unnecessary/unnecessary_dunder_call.py @@ -122,3 +122,9 @@ def get_first_subclass(cls): # since we can't apply alternate operators/functions here. a = [1, 2, 3] assert super(type(a), a).__str__() == "[1, 2, 3]" + +class MyString(str): + """Custom str implementation""" + def rjust(self, width, fillchar= ' '): + """Acceptable call to __index__""" + width = width.__index__() diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 3b6d82e04b..a7fb5c158f 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -11,6 +11,7 @@ import argparse import multiprocessing import os +from pickle import PickleError import dill import pytest @@ -231,6 +232,24 @@ def test_worker_check_single_file_no_checkers(self) -> None: assert stats.statement == 18 assert stats.warning == 0 + def test_linter_with_unpickleable_plugins_is_pickleable(self) -> None: + """The linter needs to be pickle-able in order to be passed between workers""" + linter = PyLinter(reporter=Reporter()) + # We load an extension that we know is not pickle-safe + linter.load_plugin_modules(["pylint.extensions.overlapping_exceptions"]) + try: + dill.dumps(linter) + assert False, "Plugins loaded were pickle-safe! This test needs altering" + except (KeyError, TypeError, PickleError, NotImplementedError): + pass + + # And expect this call to make it pickle-able + linter.load_plugin_configuration() + try: + dill.dumps(linter) + except KeyError: + assert False, "Cannot pickle linter when using non-pickleable plugin" + def test_worker_check_sequential_checker(self) -> None: """Same as test_worker_check_single_file_no_checkers with SequentialTestChecker.""" linter = PyLinter(reporter=Reporter())