summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/CODEOWNERS44
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md6
-rw-r--r--.github/workflows/codeql-analysis.yml5
-rw-r--r--.github/workflows/primer_comment.yaml2
-rw-r--r--.github/workflows/primer_run_main.yaml22
-rw-r--r--.github/workflows/primer_run_pr.yaml23
-rw-r--r--.github/workflows/release.yml9
-rw-r--r--.pre-commit-config.yaml30
-rw-r--r--.pyenchant_pylint_custom_dict.txt5
-rw-r--r--Dockerfile2
-rw-r--r--README.rst28
-rw-r--r--doc/data/messages/a/anomalous-unicode-escape-in-string/bad.py2
-rw-r--r--doc/data/messages/a/anomalous-unicode-escape-in-string/good.py2
-rw-r--r--doc/data/messages/a/astroid-error/details.rst3
-rw-r--r--doc/data/messages/a/astroid-error/good.py1
-rw-r--r--doc/data/messages/b/bad-dunder-name/bad.py6
-rw-r--r--doc/data/messages/b/bad-dunder-name/good.py6
-rw-r--r--doc/data/messages/b/bad-dunder-name/pylintrc2
-rw-r--r--doc/data/messages/b/bad-format-string-key/bad.py1
-rw-r--r--doc/data/messages/b/bad-format-string-key/details.rst7
-rw-r--r--doc/data/messages/b/bad-format-string-key/good.py2
-rw-r--r--doc/data/messages/b/bad-thread-instantiation/bad.py9
-rw-r--r--doc/data/messages/b/bad-thread-instantiation/details.rst1
-rw-r--r--doc/data/messages/b/bad-thread-instantiation/good.py10
-rw-r--r--doc/data/messages/b/broad-except/bad.py4
-rw-r--r--doc/data/messages/b/broad-exception-caught/bad.py4
-rw-r--r--doc/data/messages/b/broad-exception-caught/good.py (renamed from doc/data/messages/b/broad-except/good.py)0
-rw-r--r--doc/data/messages/b/broad-exception-raised/bad.py4
-rw-r--r--doc/data/messages/b/broad-exception-raised/good.py4
-rw-r--r--doc/data/messages/b/broken-noreturn/bad.py5
-rw-r--r--doc/data/messages/b/broken-noreturn/details.rst1
-rw-r--r--doc/data/messages/b/broken-noreturn/good.py7
-rw-r--r--doc/data/messages/b/broken-noreturn/pylintrc3
-rw-r--r--doc/data/messages/c/condition-evals-to-constant/bad.py2
-rw-r--r--doc/data/messages/c/condition-evals-to-constant/details.rst1
-rw-r--r--doc/data/messages/c/condition-evals-to-constant/good.py3
-rw-r--r--doc/data/messages/c/config-parse-error/details.rst2
-rw-r--r--doc/data/messages/c/config-parse-error/good.py1
-rw-r--r--doc/data/messages/c/confusing-with-statement/bad.py2
-rw-r--r--doc/data/messages/c/confusing-with-statement/details.rst1
-rw-r--r--doc/data/messages/c/confusing-with-statement/good.py4
-rw-r--r--doc/data/messages/c/consider-using-assignment-expr/bad.py4
-rw-r--r--doc/data/messages/c/consider-using-assignment-expr/details.rst1
-rw-r--r--doc/data/messages/c/consider-using-assignment-expr/good.py3
-rw-r--r--doc/data/messages/c/consider-using-assignment-expr/pylintrc3
-rw-r--r--doc/data/messages/c/consider-using-augmented-assign/bad.py2
-rw-r--r--doc/data/messages/c/consider-using-augmented-assign/good.py2
-rw-r--r--doc/data/messages/c/consider-using-augmented-assign/pylintrc3
-rw-r--r--doc/data/messages/d/dict-init-mutate/bad.py3
-rw-r--r--doc/data/messages/d/dict-init-mutate/good.py1
-rw-r--r--doc/data/messages/d/dict-init-mutate/pylintrc2
-rw-r--r--doc/data/messages/e/exec-used/details.rst11
-rw-r--r--doc/data/messages/f/fatal/details.rst2
-rw-r--r--doc/data/messages/f/fatal/good.py1
-rw-r--r--doc/data/messages/i/invalid-bool-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-bool-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-bool-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-bytes-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-bytes-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-bytes-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-characters-in-docstring/details.rst3
-rw-r--r--doc/data/messages/i/invalid-characters-in-docstring/good.py1
-rw-r--r--doc/data/messages/i/invalid-class-object/bad.py5
-rw-r--r--doc/data/messages/i/invalid-class-object/details.rst1
-rw-r--r--doc/data/messages/i/invalid-class-object/good.py10
-rw-r--r--doc/data/messages/i/invalid-format-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-format-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-format-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-getnewargs-ex-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-getnewargs-ex-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-getnewargs-ex-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-getnewargs-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-getnewargs-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-getnewargs-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-hash-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-hash-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-hash-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-index-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-index-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-index-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-length-hint-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-length-hint-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-length-hint-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-metaclass/bad.py2
-rw-r--r--doc/data/messages/i/invalid-metaclass/details.rst1
-rw-r--r--doc/data/messages/i/invalid-metaclass/good.py6
-rw-r--r--doc/data/messages/i/invalid-repr-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-repr-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-repr-returned/good.py6
-rw-r--r--doc/data/messages/i/invalid-slice-step/bad.py3
-rw-r--r--doc/data/messages/i/invalid-slice-step/good.py3
-rw-r--r--doc/data/messages/i/invalid-str-returned/bad.py5
-rw-r--r--doc/data/messages/i/invalid-str-returned/details.rst1
-rw-r--r--doc/data/messages/i/invalid-str-returned/good.py6
-rw-r--r--doc/data/messages/i/isinstance-second-argument-not-valid-type/bad.py1
-rw-r--r--doc/data/messages/i/isinstance-second-argument-not-valid-type/details.rst1
-rw-r--r--doc/data/messages/i/isinstance-second-argument-not-valid-type/good.py2
-rw-r--r--doc/data/messages/m/magic-value-comparison/bad.py10
-rw-r--r--doc/data/messages/m/magic-value-comparison/good.py15
-rw-r--r--doc/data/messages/m/magic-value-comparison/pylintrc2
-rw-r--r--doc/data/messages/m/method-check-failed/details.rst2
-rw-r--r--doc/data/messages/m/method-check-failed/good.py1
-rw-r--r--doc/data/messages/m/misplaced-comparison-constant/bad.py8
-rw-r--r--doc/data/messages/m/misplaced-comparison-constant/details.rst1
-rw-r--r--doc/data/messages/m/misplaced-comparison-constant/good.py9
-rw-r--r--doc/data/messages/m/misplaced-comparison-constant/pylintrc2
-rw-r--r--doc/data/messages/m/missing-raises-doc/pylintrc3
-rw-r--r--doc/data/messages/m/modified-iterating-dict/bad.py6
-rw-r--r--doc/data/messages/m/modified-iterating-dict/details.rst1
-rw-r--r--doc/data/messages/m/modified-iterating-dict/good.py7
-rw-r--r--doc/data/messages/m/modified-iterating-list/bad.py3
-rw-r--r--doc/data/messages/m/modified-iterating-list/details.rst1
-rw-r--r--doc/data/messages/m/modified-iterating-list/good.py4
-rw-r--r--doc/data/messages/m/modified-iterating-set/bad.py3
-rw-r--r--doc/data/messages/m/modified-iterating-set/details.rst1
-rw-r--r--doc/data/messages/m/modified-iterating-set/good.py4
-rw-r--r--doc/data/messages/m/multiple-statements/bad.py5
-rw-r--r--doc/data/messages/m/multiple-statements/details.rst1
-rw-r--r--doc/data/messages/m/multiple-statements/good.py7
-rw-r--r--doc/data/messages/n/named-expr-without-context/bad.py1
-rw-r--r--doc/data/messages/n/named-expr-without-context/good.py2
-rw-r--r--doc/data/messages/n/nan-comparison/bad.py5
-rw-r--r--doc/data/messages/n/nan-comparison/details.rst1
-rw-r--r--doc/data/messages/n/nan-comparison/good.py6
-rw-r--r--doc/data/messages/n/nested-min-max/bad.py1
-rw-r--r--doc/data/messages/n/nested-min-max/good.py1
-rw-r--r--doc/data/messages/n/no-classmethod-decorator/bad.py11
-rw-r--r--doc/data/messages/n/no-classmethod-decorator/details.rst1
-rw-r--r--doc/data/messages/n/no-classmethod-decorator/good.py11
-rw-r--r--doc/data/messages/n/non-ascii-module-import/bad.py3
-rw-r--r--doc/data/messages/n/non-ascii-module-import/details.rst1
-rw-r--r--doc/data/messages/n/non-ascii-module-import/good.py4
-rw-r--r--doc/data/messages/n/non-ascii-name/bad.py1
-rw-r--r--doc/data/messages/n/non-ascii-name/details.rst1
-rw-r--r--doc/data/messages/n/non-ascii-name/good.py2
-rw-r--r--doc/data/messages/n/non-str-assignment-to-dunder-name/bad.py5
-rw-r--r--doc/data/messages/n/non-str-assignment-to-dunder-name/details.rst1
-rw-r--r--doc/data/messages/n/non-str-assignment-to-dunder-name/good.py6
-rw-r--r--doc/data/messages/n/nonlocal-without-binding/bad.py3
-rw-r--r--doc/data/messages/n/nonlocal-without-binding/details.rst1
-rw-r--r--doc/data/messages/n/nonlocal-without-binding/good.py6
-rw-r--r--doc/data/messages/n/not-a-mapping/bad.py5
-rw-r--r--doc/data/messages/n/not-a-mapping/details.rst1
-rw-r--r--doc/data/messages/n/not-a-mapping/good.py6
-rw-r--r--doc/data/messages/p/parse-error/details.rst2
-rw-r--r--doc/data/messages/p/possibly-unused-variable/bad.py4
-rw-r--r--doc/data/messages/p/possibly-unused-variable/details.rst1
-rw-r--r--doc/data/messages/p/possibly-unused-variable/good.py7
-rw-r--r--doc/data/messages/p/preferred-module/bad.py1
-rw-r--r--doc/data/messages/p/preferred-module/details.rst1
-rw-r--r--doc/data/messages/p/preferred-module/good.py2
-rw-r--r--doc/data/messages/p/preferred-module/pylintrc2
-rw-r--r--doc/data/messages/r/redefined-outer-name/bad.py6
-rw-r--r--doc/data/messages/r/redefined-outer-name/details.rst24
-rw-r--r--doc/data/messages/r/redefined-outer-name/good.py7
-rw-r--r--doc/data/messages/r/redundant-returns-doc/bad.py9
-rw-r--r--doc/data/messages/r/redundant-returns-doc/details.rst1
-rw-r--r--doc/data/messages/r/redundant-returns-doc/good.py10
-rw-r--r--doc/data/messages/r/redundant-returns-doc/pylintrc2
-rw-r--r--doc/data/messages/r/redundant-u-string-prefix/bad.py2
-rw-r--r--doc/data/messages/r/redundant-u-string-prefix/details.rst1
-rw-r--r--doc/data/messages/r/redundant-u-string-prefix/good.py3
-rw-r--r--doc/data/messages/r/redundant-yields-doc/bad.py9
-rw-r--r--doc/data/messages/r/redundant-yields-doc/details.rst1
-rw-r--r--doc/data/messages/r/redundant-yields-doc/good.py11
-rw-r--r--doc/data/messages/r/redundant-yields-doc/pylintrc2
-rw-r--r--doc/data/messages/s/self-cls-assignment/bad.py9
-rw-r--r--doc/data/messages/s/self-cls-assignment/details.rst1
-rw-r--r--doc/data/messages/s/self-cls-assignment/good.py10
-rw-r--r--doc/data/messages/s/simplifiable-condition/bad.py2
-rw-r--r--doc/data/messages/s/simplifiable-condition/details.rst1
-rw-r--r--doc/data/messages/s/simplifiable-condition/good.py3
-rw-r--r--doc/data/messages/s/simplify-boolean-expression/bad.py2
-rw-r--r--doc/data/messages/s/simplify-boolean-expression/details.rst1
-rw-r--r--doc/data/messages/s/simplify-boolean-expression/good.py3
-rw-r--r--doc/data/messages/s/singledispatch-method/bad.py19
-rw-r--r--doc/data/messages/s/singledispatch-method/details.rst0
-rw-r--r--doc/data/messages/s/singledispatch-method/good.py19
-rw-r--r--doc/data/messages/s/singledispatchmethod-function/bad.py19
-rw-r--r--doc/data/messages/s/singledispatchmethod-function/details.rst0
-rw-r--r--doc/data/messages/s/singledispatchmethod-function/good.py18
-rw-r--r--doc/data/messages/s/syntax-error/bad.py5
-rw-r--r--doc/data/messages/s/syntax-error/good.py5
-rw-r--r--doc/data/messages/t/too-many-function-args/bad.py6
-rw-r--r--doc/data/messages/t/too-many-function-args/details.rst1
-rw-r--r--doc/data/messages/t/too-many-function-args/good.py8
-rw-r--r--doc/data/messages/t/too-many-try-statements/bad.py10
-rw-r--r--doc/data/messages/t/too-many-try-statements/details.rst1
-rw-r--r--doc/data/messages/t/too-many-try-statements/good.py12
-rw-r--r--doc/data/messages/t/too-many-try-statements/pylintrc2
-rw-r--r--doc/data/messages/u/unbalanced-dict-unpacking/bad.py4
-rw-r--r--doc/data/messages/u/unbalanced-dict-unpacking/good.py4
-rw-r--r--doc/data/messages/u/unnecessary-dict-index-lookup/bad.py4
-rw-r--r--doc/data/messages/u/unnecessary-dict-index-lookup/details.rst1
-rw-r--r--doc/data/messages/u/unnecessary-dict-index-lookup/good.py5
-rw-r--r--doc/data/messages/u/unrecognized-inline-option/bad.py2
-rw-r--r--doc/data/messages/u/unrecognized-inline-option/details.rst1
-rw-r--r--doc/data/messages/u/unrecognized-inline-option/good.py2
-rw-r--r--doc/data/messages/u/unsubscriptable-object/bad.py5
-rw-r--r--doc/data/messages/u/unsubscriptable-object/details.rst1
-rw-r--r--doc/data/messages/u/unsubscriptable-object/good.py10
-rw-r--r--doc/data/messages/u/unsupported-assignment-operation/bad.py6
-rw-r--r--doc/data/messages/u/unsupported-assignment-operation/details.rst1
-rw-r--r--doc/data/messages/u/unsupported-assignment-operation/good.py9
-rw-r--r--doc/data/messages/u/unsupported-delete-operation/bad.py3
-rw-r--r--doc/data/messages/u/unsupported-delete-operation/details.rst1
-rw-r--r--doc/data/messages/u/unsupported-delete-operation/good.py4
-rw-r--r--doc/data/messages/u/unsupported-membership-test/bad.py5
-rw-r--r--doc/data/messages/u/unsupported-membership-test/details.rst1
-rw-r--r--doc/data/messages/u/unsupported-membership-test/good.py8
-rw-r--r--doc/data/messages/u/unused-private-member/bad.py5
-rw-r--r--doc/data/messages/u/unused-private-member/details.rst1
-rw-r--r--doc/data/messages/u/unused-private-member/good.py10
-rw-r--r--doc/data/messages/u/use-dict-literal/bad.py2
-rw-r--r--doc/data/messages/u/use-dict-literal/details.rst4
-rw-r--r--doc/data/messages/u/use-dict-literal/good.py6
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison/bad.py4
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison/details.rst1
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison/good.py5
-rw-r--r--doc/data/messages/w/wrong-spelling-in-comment/bad.py1
-rw-r--r--doc/data/messages/w/wrong-spelling-in-comment/details.rst1
-rw-r--r--doc/data/messages/w/wrong-spelling-in-comment/good.py2
-rw-r--r--doc/data/messages/w/wrong-spelling-in-comment/pylintrc3
-rw-r--r--doc/data/messages/w/wrong-spelling-in-docstring/bad.py1
-rw-r--r--doc/data/messages/w/wrong-spelling-in-docstring/details.rst1
-rw-r--r--doc/data/messages/w/wrong-spelling-in-docstring/good.py2
-rw-r--r--doc/data/messages/w/wrong-spelling-in-docstring/pylintrc3
-rw-r--r--doc/development_guide/api/index.rst3
-rw-r--r--doc/development_guide/contributor_guide/contribute.rst6
-rw-r--r--doc/development_guide/contributor_guide/release.md25
-rw-r--r--doc/development_guide/contributor_guide/tests/launching_test.rst19
-rw-r--r--doc/development_guide/how_tos/custom_checkers.rst26
-rw-r--r--doc/development_guide/how_tos/plugins.rst2
-rwxr-xr-xdoc/exts/pylint_extensions.py60
-rwxr-xr-xdoc/exts/pylint_features.py7
-rw-r--r--doc/exts/pylint_messages.py10
-rw-r--r--doc/requirements.txt6
-rw-r--r--doc/tutorial.rst272
-rw-r--r--doc/user_guide/checkers/extensions.rst59
-rw-r--r--doc/user_guide/checkers/features.rst42
-rw-r--r--doc/user_guide/configuration/all-options.rst76
-rw-r--r--doc/user_guide/configuration/index.rst9
-rw-r--r--doc/user_guide/installation/ide_integration/flymake-emacs.rst10
-rw-r--r--doc/user_guide/installation/ide_integration/index.rst2
-rw-r--r--doc/user_guide/messages/messages_overview.rst13
-rw-r--r--doc/user_guide/usage/output.rst2
-rw-r--r--doc/whatsnew/2/2.15/index.rst2
-rw-r--r--doc/whatsnew/fragments/2374.false_negative4
-rw-r--r--doc/whatsnew/fragments/2876.new_check4
-rw-r--r--doc/whatsnew/fragments/3038.new_check4
-rw-r--r--doc/whatsnew/fragments/3044.bugfix3
-rw-r--r--doc/whatsnew/fragments/3299.bugfix4
-rw-r--r--doc/whatsnew/fragments/3299.false_positive3
-rw-r--r--doc/whatsnew/fragments/3391.extension6
-rw-r--r--doc/whatsnew/fragments/3822.false_positive3
-rw-r--r--doc/whatsnew/fragments/4150.false_negative3
-rw-r--r--doc/whatsnew/fragments/4354.bugfix3
-rw-r--r--doc/whatsnew/fragments/4562.bugfix3
-rw-r--r--doc/whatsnew/fragments/4655.bugfix3
-rw-r--r--doc/whatsnew/fragments/4743.bugfix4
-rw-r--r--doc/whatsnew/fragments/4792.false_negative3
-rw-r--r--doc/whatsnew/fragments/4809.false_positive3
-rw-r--r--doc/whatsnew/fragments/4913.false_negative3
-rw-r--r--doc/whatsnew/fragments/519.false_negative3
-rw-r--r--doc/whatsnew/fragments/5478.bugfix3
-rw-r--r--doc/whatsnew/fragments/5797.new_check4
-rw-r--r--doc/whatsnew/fragments/5886.false_positive3
-rw-r--r--doc/whatsnew/fragments/5920.other3
-rw-r--r--doc/whatsnew/fragments/6242.bugfix4
-rw-r--r--doc/whatsnew/fragments/6543.feature5
-rw-r--r--doc/whatsnew/fragments/6592.false_positive3
-rw-r--r--doc/whatsnew/fragments/6639.false_negative3
-rw-r--r--doc/whatsnew/fragments/6795.bugfix3
-rw-r--r--doc/whatsnew/fragments/6917.new_check4
-rw-r--r--doc/whatsnew/fragments/7003.bugfix4
-rw-r--r--doc/whatsnew/fragments/7124.other3
-rw-r--r--doc/whatsnew/fragments/7169.bugfix3
-rw-r--r--doc/whatsnew/fragments/7208.user_action10
-rw-r--r--doc/whatsnew/fragments/7214.bugfix3
-rw-r--r--doc/whatsnew/fragments/7264.bugfix8
-rw-r--r--doc/whatsnew/fragments/7264.internal9
-rw-r--r--doc/whatsnew/fragments/7281.new_check4
-rw-r--r--doc/whatsnew/fragments/7290.false_positive3
-rw-r--r--doc/whatsnew/fragments/7311.false_positive4
-rw-r--r--doc/whatsnew/fragments/7346.other4
-rw-r--r--doc/whatsnew/fragments/7368.false_positive3
-rw-r--r--doc/whatsnew/fragments/7380.bugfix3
-rw-r--r--doc/whatsnew/fragments/7390.bugfix3
-rw-r--r--doc/whatsnew/fragments/7398.other4
-rw-r--r--doc/whatsnew/fragments/7401.bugfix3
-rw-r--r--doc/whatsnew/fragments/7453.bugfix3
-rw-r--r--doc/whatsnew/fragments/7457.bugfix3
-rw-r--r--doc/whatsnew/fragments/7461.bugfix3
-rw-r--r--doc/whatsnew/fragments/7463.other3
-rw-r--r--doc/whatsnew/fragments/7467.bugfix3
-rw-r--r--doc/whatsnew/fragments/7471.bugfix3
-rw-r--r--doc/whatsnew/fragments/7485.other3
-rw-r--r--doc/whatsnew/fragments/7494.new_check4
-rw-r--r--doc/whatsnew/fragments/7495.bugfix4
-rw-r--r--doc/whatsnew/fragments/7501.false_positive3
-rw-r--r--doc/whatsnew/fragments/7507.bugfix4
-rw-r--r--doc/whatsnew/fragments/7507.other3
-rw-r--r--doc/whatsnew/fragments/7522.bugfix3
-rw-r--r--doc/whatsnew/fragments/7524.bugfix4
-rw-r--r--doc/whatsnew/fragments/7528.bugfix3
-rw-r--r--doc/whatsnew/fragments/7529.false_positive4
-rw-r--r--doc/whatsnew/fragments/7544.other3
-rw-r--r--doc/whatsnew/fragments/7546.new_check4
-rw-r--r--doc/whatsnew/fragments/7547.false_negative3
-rw-r--r--doc/whatsnew/fragments/7563.false_positive3
-rw-r--r--doc/whatsnew/fragments/7569.bugfix3
-rw-r--r--doc/whatsnew/fragments/7570.bugfix4
-rw-r--r--doc/whatsnew/fragments/7609.false_positive4
-rw-r--r--doc/whatsnew/fragments/7610.bugfix3
-rw-r--r--doc/whatsnew/fragments/7626.false_positive4
-rw-r--r--doc/whatsnew/fragments/7626.other3
-rw-r--r--doc/whatsnew/fragments/7629.other4
-rw-r--r--doc/whatsnew/fragments/7635.bugfix7
-rw-r--r--doc/whatsnew/fragments/7655.other3
-rw-r--r--doc/whatsnew/fragments/7661.bugfix3
-rw-r--r--doc/whatsnew/fragments/7682.false_positive3
-rw-r--r--doc/whatsnew/fragments/7690.new_check3
-rw-r--r--doc/whatsnew/fragments/7699.false_negative3
-rw-r--r--doc/whatsnew/fragments/7737.other5
-rw-r--r--doc/whatsnew/fragments/7742.bugfix3
-rw-r--r--doc/whatsnew/fragments/7760.new_check5
-rw-r--r--doc/whatsnew/fragments/7762.false_negative4
-rw-r--r--doc/whatsnew/fragments/7762.new_check4
-rw-r--r--doc/whatsnew/fragments/7765.false_positive3
-rw-r--r--doc/whatsnew/fragments/7770.false_negative3
-rw-r--r--doc/whatsnew/fragments/7782.bugfix4
-rw-r--r--doc/whatsnew/fragments/7818.false_negative3
-rw-r--r--doc/whatsnew/fragments/7821.bugfix3
-rw-r--r--doc/whatsnew/fragments/7828.bugfix3
-rw-r--r--doc/whatsnew/fragments/7860.false_positive3
-rw-r--r--elisp/pylint-flymake.el15
-rw-r--r--elisp/pylint.el255
-rw-r--r--elisp/startup17
-rw-r--r--examples/pylintrc6
-rw-r--r--examples/pyproject.toml4
-rw-r--r--pylint/__init__.py9
-rw-r--r--pylint/__pkginfo__.py2
-rw-r--r--pylint/checkers/__init__.py2
-rw-r--r--pylint/checkers/base/basic_checker.py49
-rw-r--r--pylint/checkers/base/basic_error_checker.py5
-rw-r--r--pylint/checkers/base/comparison_checker.py31
-rw-r--r--pylint/checkers/base/docstring_checker.py6
-rw-r--r--pylint/checkers/base/name_checker/checker.py2
-rw-r--r--pylint/checkers/base_checker.py7
-rw-r--r--pylint/checkers/classes/class_checker.py48
-rw-r--r--pylint/checkers/design_analysis.py4
-rw-r--r--pylint/checkers/dunder_methods.py109
-rw-r--r--pylint/checkers/exceptions.py123
-rw-r--r--pylint/checkers/format.py66
-rw-r--r--pylint/checkers/imports.py62
-rw-r--r--pylint/checkers/mapreduce_checker.py1
-rw-r--r--pylint/checkers/modified_iterating_checker.py4
-rw-r--r--pylint/checkers/nested_min_max.py95
-rw-r--r--pylint/checkers/refactoring/implicit_booleaness_checker.py73
-rw-r--r--pylint/checkers/refactoring/recommendation_checker.py11
-rw-r--r--pylint/checkers/refactoring/refactoring_checker.py122
-rw-r--r--pylint/checkers/similar.py18
-rw-r--r--pylint/checkers/spelling.py20
-rw-r--r--pylint/checkers/stdlib.py73
-rw-r--r--pylint/checkers/strings.py23
-rw-r--r--pylint/checkers/typecheck.py205
-rw-r--r--pylint/checkers/utils.py211
-rw-r--r--pylint/checkers/variables.py435
-rw-r--r--pylint/config/__init__.py8
-rw-r--r--pylint/config/_pylint_config/__init__.py6
-rw-r--r--pylint/config/_pylint_config/generate_command.py3
-rw-r--r--pylint/config/_pylint_config/utils.py17
-rw-r--r--pylint/config/argument.py6
-rw-r--r--pylint/config/arguments_manager.py60
-rw-r--r--pylint/config/arguments_provider.py20
-rw-r--r--pylint/config/callback_actions.py2
-rw-r--r--pylint/config/configuration_mixin.py14
-rw-r--r--pylint/config/deprecation_actions.py4
-rw-r--r--pylint/config/find_default_config_files.py1
-rw-r--r--pylint/config/option.py70
-rw-r--r--pylint/config/option_manager_mixin.py94
-rw-r--r--pylint/config/option_parser.py3
-rw-r--r--pylint/config/options_provider_mixin.py3
-rw-r--r--pylint/config/utils.py8
-rw-r--r--pylint/constants.py130
-rwxr-xr-xpylint/epylint.py11
-rw-r--r--pylint/extensions/_check_docs_utils.py2
-rw-r--r--pylint/extensions/bad_builtin.py4
-rw-r--r--pylint/extensions/code_style.py28
-rw-r--r--pylint/extensions/comparetozero.py24
-rw-r--r--pylint/extensions/dict_init_mutate.py66
-rw-r--r--pylint/extensions/docparams.py12
-rw-r--r--pylint/extensions/dunder.py77
-rw-r--r--pylint/extensions/emptystring.py62
-rw-r--r--pylint/extensions/for_any_all.py100
-rw-r--r--pylint/extensions/magic_value.py88
-rw-r--r--pylint/extensions/mccabe.py4
-rw-r--r--pylint/extensions/private_import.py2
-rw-r--r--pylint/interfaces.py12
-rw-r--r--pylint/lint/base_options.py2
-rw-r--r--pylint/lint/caching.py8
-rw-r--r--pylint/lint/expand_modules.py2
-rw-r--r--pylint/lint/message_state_handler.py2
-rw-r--r--pylint/lint/parallel.py2
-rw-r--r--pylint/lint/pylinter.py43
-rw-r--r--pylint/message/message.py1
-rw-r--r--pylint/message/message_definition.py24
-rw-r--r--pylint/message/message_definition_store.py14
-rw-r--r--pylint/pyreverse/diadefslib.py4
-rw-r--r--pylint/pyreverse/diagrams.py39
-rw-r--r--pylint/pyreverse/dot_printer.py43
-rw-r--r--pylint/pyreverse/inspector.py67
-rw-r--r--pylint/pyreverse/main.py223
-rw-r--r--pylint/pyreverse/mermaidjs_printer.py2
-rw-r--r--pylint/pyreverse/plantuml_printer.py4
-rw-r--r--pylint/pyreverse/printer.py1
-rw-r--r--pylint/pyreverse/utils.py8
-rw-r--r--pylint/pyreverse/vcg_printer.py50
-rw-r--r--pylint/pyreverse/writer.py10
-rw-r--r--pylint/reporters/base_reporter.py2
-rw-r--r--pylint/reporters/text.py3
-rw-r--r--pylint/testutils/_primer/package_to_lint.py21
-rw-r--r--pylint/testutils/_primer/primer.py10
-rw-r--r--pylint/testutils/_primer/primer_compare_command.py11
-rw-r--r--pylint/testutils/_primer/primer_prepare_command.py11
-rw-r--r--pylint/testutils/_primer/primer_run_command.py2
-rw-r--r--pylint/testutils/checker_test_case.py2
-rw-r--r--pylint/testutils/functional/find_functional_tests.py4
-rw-r--r--pylint/testutils/functional_test_file.py1
-rw-r--r--pylint/testutils/lint_module_test.py2
-rw-r--r--pylint/testutils/output_line.py2
-rw-r--r--pylint/testutils/utils.py2
-rw-r--r--pylint/typing.py14
-rw-r--r--pylint/utils/ast_walker.py2
-rw-r--r--pylint/utils/file_state.py5
-rw-r--r--pylint/utils/pragma_parser.py6
-rw-r--r--pylint/utils/utils.py10
-rw-r--r--pylintrc9
-rw-r--r--pyproject.toml51
-rw-r--r--requirements_test.txt6
-rw-r--r--requirements_test_pre_commit.txt7
-rw-r--r--script/.contributors_aliases.json4
-rw-r--r--script/check_newsfragments.py12
-rw-r--r--script/create_contributor_list.py2
-rw-r--r--script/fix_documentation.py7
-rw-r--r--setup.cfg75
-rw-r--r--tbump.toml2
-rw-r--r--tests/benchmark/test_baseline_benchmarks.py39
-rw-r--r--tests/checkers/base/unittest_base.py2
-rw-r--r--tests/checkers/unittest_deprecated.py2
-rw-r--r--tests/checkers/unittest_non_ascii_name.py6
-rw-r--r--tests/checkers/unittest_spelling.py10
-rw-r--r--tests/checkers/unittest_stdlib.py15
-rw-r--r--tests/checkers/unittest_unicode/unittest_bad_chars.py8
-rw-r--r--tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py2
-rw-r--r--tests/checkers/unittest_unicode/unittest_functions.py18
-rw-r--r--tests/checkers/unittest_unicode/unittest_invalid_encoding.py10
-rw-r--r--tests/checkers/unittest_utils.py14
-rw-r--r--tests/config/pylint_config/test_pylint_config_generate.py40
-rw-r--r--tests/config/test_argparse_config.py5
-rw-r--r--tests/config/test_find_default_config_files.py2
-rw-r--r--tests/config/test_functional_config_loading.py4
-rw-r--r--tests/config/test_per_directory_config.py7
-rw-r--r--tests/config/unittest_config.py12
-rw-r--r--tests/conftest.py23
-rw-r--r--tests/extensions/test_check_docs_utils.py6
-rw-r--r--tests/extensions/test_private_import.py12
-rw-r--r--tests/functional/a/abstract/abstract_method.txt32
-rw-r--r--tests/functional/a/alternative/alternative_union_syntax.py2
-rw-r--r--tests/functional/a/arguments_differ.txt26
-rw-r--r--tests/functional/a/arguments_renamed.txt20
-rw-r--r--tests/functional/a/assert_on_tuple.py10
-rw-r--r--tests/functional/a/assert_on_tuple.txt8
-rw-r--r--tests/functional/b/bad_except_order.txt10
-rw-r--r--tests/functional/b/bad_exception_cause.py2
-rw-r--r--tests/functional/b/bad_exception_cause.txt1
-rw-r--r--tests/functional/b/bad_reversed_sequence_py37.txt4
-rw-r--r--tests/functional/b/bad_thread_instantiation.py20
-rw-r--r--tests/functional/b/bad_thread_instantiation.txt10
-rw-r--r--tests/functional/b/bare_except.txt2
-rw-r--r--tests/functional/b/boolean_datetime.py15
-rw-r--r--tests/functional/b/boolean_datetime.rc2
-rw-r--r--tests/functional/b/boolean_datetime.txt2
-rw-r--r--tests/functional/b/broad_except.py13
-rw-r--r--tests/functional/b/broad_except.txt2
-rw-r--r--tests/functional/b/broad_exception_caught.py39
-rw-r--r--tests/functional/b/broad_exception_caught.rc4
-rw-r--r--tests/functional/b/broad_exception_caught.txt3
-rw-r--r--tests/functional/b/broad_exception_raised.py52
-rw-r--r--tests/functional/b/broad_exception_raised.rc4
-rw-r--r--tests/functional/b/broad_exception_raised.txt8
-rw-r--r--tests/functional/c/class_members_py30.py2
-rw-r--r--tests/functional/c/comparison_with_callable.py2
-rw-r--r--tests/functional/c/consider/consider_iterating_dictionary.py22
-rw-r--r--tests/functional/c/consider/consider_iterating_dictionary.txt54
-rw-r--r--tests/functional/c/consider/consider_join.py35
-rw-r--r--tests/functional/c/consider/consider_join.txt19
-rw-r--r--tests/functional/c/consider/consider_using_dict_items.txt4
-rw-r--r--tests/functional/c/ctor_arguments.py4
-rw-r--r--tests/functional/d/dangerous_default_value.py11
-rw-r--r--tests/functional/d/dangerous_default_value.txt1
-rw-r--r--tests/functional/d/dataclass_kw_only.py26
-rw-r--r--tests/functional/d/dataclass_kw_only.rc2
-rw-r--r--tests/functional/d/dataclass_kw_only.txt4
-rw-r--r--tests/functional/d/duplicate_except.txt2
-rw-r--r--tests/functional/e/e1101_9588_base_attr_aug_assign.py2
-rw-r--r--tests/functional/e/enum_subclasses.py18
-rw-r--r--tests/functional/e/exception_is_binary_op.txt8
-rw-r--r--tests/functional/ext/bad_dunder/bad_dunder_name.py54
-rw-r--r--tests/functional/ext/bad_dunder/bad_dunder_name.rc4
-rw-r--r--tests/functional/ext/bad_dunder/bad_dunder_name.txt5
-rw-r--r--tests/functional/ext/code_style/cs_consider_using_augmented_assign.py77
-rw-r--r--tests/functional/ext/code_style/cs_consider_using_augmented_assign.rc3
-rw-r--r--tests/functional/ext/code_style/cs_consider_using_augmented_assign.txt20
-rw-r--r--tests/functional/ext/code_style/cs_default.py6
-rw-r--r--tests/functional/ext/code_style/cs_default.rc2
-rw-r--r--tests/functional/ext/comparetozero/compare_to_zero.py (renamed from tests/functional/ext/comparetozero/comparetozero.py)20
-rw-r--r--tests/functional/ext/comparetozero/compare_to_zero.rc (renamed from tests/functional/ext/comparetozero/comparetozero.rc)0
-rw-r--r--tests/functional/ext/comparetozero/compare_to_zero.txt6
-rw-r--r--tests/functional/ext/comparetozero/comparetozero.txt4
-rw-r--r--tests/functional/ext/dict_init_mutate.py38
-rw-r--r--tests/functional/ext/dict_init_mutate.rc2
-rw-r--r--tests/functional/ext/dict_init_mutate.txt3
-rw-r--r--tests/functional/ext/docparams/docparams.py2
-rw-r--r--tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.rc1
-rw-r--r--tests/functional/ext/docparams/raise/missing_raises_doc.txt2
-rw-r--r--tests/functional/ext/docparams/raise/missing_raises_doc_options.py15
-rw-r--r--tests/functional/ext/docparams/raise/missing_raises_doc_options.rc5
-rw-r--r--tests/functional/ext/emptystring/empty_string_comparison.py6
-rw-r--r--tests/functional/ext/emptystring/empty_string_comparison.txt10
-rw-r--r--tests/functional/ext/for_any_all/for_any_all.py92
-rw-r--r--tests/functional/ext/for_any_all/for_any_all.txt26
-rw-r--r--tests/functional/ext/magic_value_comparison/magic_value_comparison.py33
-rw-r--r--tests/functional/ext/magic_value_comparison/magic_value_comparison.rc2
-rw-r--r--tests/functional/ext/magic_value_comparison/magic_value_comparison.txt7
-rw-r--r--tests/functional/ext/mccabe/mccabe.py2
-rw-r--r--tests/functional/ext/set_membership/use_set_membership.py2
-rw-r--r--tests/functional/ext/typing/typing_broken_noreturn.py4
-rw-r--r--tests/functional/ext/typing/typing_broken_noreturn_future_import.py2
-rw-r--r--tests/functional/ext/typing/typing_broken_noreturn_py372.py2
-rw-r--r--tests/functional/f/first_arg.py2
-rw-r--r--tests/functional/f/first_arg.txt4
-rw-r--r--tests/functional/g/generic_alias/generic_alias_collections.txt22
-rw-r--r--tests/functional/g/generic_alias/generic_alias_collections_py37.txt10
-rw-r--r--tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.txt10
-rw-r--r--tests/functional/g/generic_alias/generic_alias_mixed_py37.txt10
-rw-r--r--tests/functional/g/generic_alias/generic_alias_mixed_py39.txt10
-rw-r--r--tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.txt10
-rw-r--r--tests/functional/g/generic_alias/generic_alias_related.txt2
-rw-r--r--tests/functional/g/generic_alias/generic_alias_related_py39.txt2
-rw-r--r--tests/functional/g/generic_alias/generic_alias_typing.txt24
-rw-r--r--tests/functional/i/implicit/implicit_str_concat.py2
-rw-r--r--tests/functional/i/inconsistent/inconsistent_returns.py6
-rw-r--r--tests/functional/i/invalid/invalid_all_format.py2
-rw-r--r--tests/functional/i/invalid/invalid_all_format.txt1
-rw-r--r--tests/functional/i/invalid/invalid_class_object.txt10
-rw-r--r--tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt22
-rw-r--r--tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py2
-rw-r--r--tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py2
-rw-r--r--tests/functional/i/invalid/invalid_name/invalid_name-module-disable.py6
-rw-r--r--tests/functional/i/invalid/invalid_sequence_index.py2
-rw-r--r--tests/functional/i/invalid/invalid_slice_index.py26
-rw-r--r--tests/functional/i/invalid/invalid_slice_index.txt18
-rw-r--r--tests/functional/i/iterable_context.py1
-rw-r--r--tests/functional/i/iterable_context.txt20
-rw-r--r--tests/functional/m/mapping_context.py2
-rw-r--r--tests/functional/m/misplaced_bare_raise.txt14
-rw-r--r--tests/functional/m/multiple_statements.py2
-rw-r--r--tests/functional/m/multiple_statements.txt1
-rw-r--r--tests/functional/m/multiple_statements_single_line.py2
-rw-r--r--tests/functional/m/multiple_statements_single_line.txt1
-rw-r--r--tests/functional/n/named_expr_without_context_py38.py6
-rw-r--r--tests/functional/n/named_expr_without_context_py38.rc2
-rw-r--r--tests/functional/n/named_expr_without_context_py38.txt1
-rw-r--r--tests/functional/n/nested_min_max.py21
-rw-r--r--tests/functional/n/nested_min_max.txt8
-rw-r--r--tests/functional/n/no/no_else_raise.py2
-rw-r--r--tests/functional/n/no/no_member_augassign.py25
-rw-r--r--tests/functional/n/no/no_member_augassign.txt4
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py2
-rw-r--r--tests/functional/n/not_callable.py11
-rw-r--r--tests/functional/r/raising/raising_bad_type.txt2
-rw-r--r--tests/functional/r/raising/raising_format_tuple.txt14
-rw-r--r--tests/functional/r/raising/raising_non_exception.txt2
-rw-r--r--tests/functional/r/redefined/redefined_except_handler.txt2
-rw-r--r--tests/functional/r/regression_02/regression_2964.py24
-rw-r--r--tests/functional/r/regression_02/regression_3976.py14
-rw-r--r--tests/functional/r/regression_02/regression_5048.py2
-rw-r--r--tests/functional/r/regression_02/regression_too_many_arguments_2335.py4
-rw-r--r--tests/functional/s/simplifiable/simplifiable_if_statement.py2
-rw-r--r--tests/functional/s/simplifiable/simplifiable_if_statement.txt2
-rw-r--r--tests/functional/s/singledispatch_method.txt3
-rw-r--r--tests/functional/s/singledispatch_method_py37.py23
-rw-r--r--tests/functional/s/singledispatch_method_py37.rc2
-rw-r--r--tests/functional/s/singledispatch_method_py37.txt3
-rw-r--r--tests/functional/s/singledispatch_method_py38.py40
-rw-r--r--tests/functional/s/singledispatch_method_py38.rc2
-rw-r--r--tests/functional/s/singledispatch_method_py38.txt3
-rw-r--r--tests/functional/s/singledispatchmethod_function_py38.py41
-rw-r--r--tests/functional/s/singledispatchmethod_function_py38.rc2
-rw-r--r--tests/functional/s/singledispatchmethod_function_py38.txt3
-rw-r--r--tests/functional/s/slots_checks.py2
-rw-r--r--tests/functional/s/slots_checks.txt1
-rw-r--r--tests/functional/s/star/star_needs_assignment_target_py37.txt2
-rw-r--r--tests/functional/s/stop_iteration_inside_generator.py4
-rw-r--r--tests/functional/s/superfluous_parens.py20
-rw-r--r--tests/functional/s/superfluous_parens.txt6
-rw-r--r--tests/functional/s/superfluous_parens_walrus_py38.py24
-rw-r--r--tests/functional/s/superfluous_parens_walrus_py38.txt5
-rw-r--r--tests/functional/t/trailing_whitespaces.py21
-rw-r--r--tests/functional/u/unbalanced_dict_unpacking.py91
-rw-r--r--tests/functional/u/unbalanced_dict_unpacking.txt16
-rw-r--r--tests/functional/u/unbalanced_tuple_unpacking.txt18
-rw-r--r--tests/functional/u/unbalanced_tuple_unpacking_py30.py3
-rw-r--r--tests/functional/u/undefined/undefined_loop_variable.py2
-rw-r--r--tests/functional/u/undefined/undefined_variable_py30.py4
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.py6
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.txt1
-rw-r--r--tests/functional/u/unidiomatic_typecheck.py2
-rw-r--r--tests/functional/u/unnecessary/unnecessary_lambda.py2
-rw-r--r--tests/functional/u/unnecessary/unnecessary_list_index_lookup.py19
-rw-r--r--tests/functional/u/unnecessary/unnecessary_list_index_lookup.txt2
-rw-r--r--tests/functional/u/unpacking/unpacking_non_sequence.txt4
-rw-r--r--tests/functional/u/unreachable.py49
-rw-r--r--tests/functional/u/unreachable.txt15
-rw-r--r--tests/functional/u/unsubscriptable_value.py1
-rw-r--r--tests/functional/u/unsubscriptable_value.txt30
-rw-r--r--tests/functional/u/unsupported/unsupported_assignment_operation.py2
-rw-r--r--tests/functional/u/unsupported/unsupported_delete_operation.py2
-rw-r--r--tests/functional/u/unused/unused_import.py34
-rw-r--r--tests/functional/u/unused/unused_import.txt18
-rw-r--r--tests/functional/u/unused/unused_import_py39.py10
-rw-r--r--tests/functional/u/unused/unused_import_py39.rc2
-rw-r--r--tests/functional/u/unused/unused_import_py39.txt1
-rw-r--r--tests/functional/u/unused/unused_variable.py2
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_comparison.txt64
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_len.txt46
-rw-r--r--tests/functional/u/use/use_literal_dict.py43
-rw-r--r--tests/functional/u/use/use_literal_dict.txt8
-rw-r--r--tests/functional/u/used/used_before_assignment.py81
-rw-r--r--tests/functional/u/used/used_before_assignment.txt4
-rw-r--r--tests/functional/u/used/used_before_assignment_comprehension_homonyms.py1
-rw-r--r--tests/functional/u/used/used_before_assignment_conditional.py1
-rw-r--r--tests/functional/u/used/used_before_assignment_conditional.txt4
-rw-r--r--tests/functional/u/used/used_before_assignment_else_return.py15
-rw-r--r--tests/functional/u/used/used_before_assignment_else_return.txt8
-rw-r--r--tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py4
-rw-r--r--tests/functional/u/used/used_before_assignment_issue626.txt4
-rw-r--r--tests/functional/u/used/used_before_assignment_nonlocal.py18
-rw-r--r--tests/functional/u/used/used_before_assignment_nonlocal.txt1
-rw-r--r--tests/lint/test_pylinter.py18
-rw-r--r--tests/lint/test_utils.py2
-rw-r--r--tests/lint/unittest_expand_modules.py18
-rw-r--r--tests/lint/unittest_lint.py199
-rw-r--r--tests/message/unittest_message_definition.py33
-rw-r--r--tests/message/unittest_message_definition_store.py9
-rw-r--r--tests/message/unittest_message_id_store.py2
-rw-r--r--tests/primer/packages_to_lint_batch_one.json6
-rw-r--r--tests/primer/packages_to_prime.json13
-rw-r--r--tests/primer/test_primer_stdlib.py12
-rw-r--r--tests/profile/test_profile_against_externals.py9
-rw-r--r--tests/pyreverse/conftest.py5
-rw-r--r--tests/pyreverse/data/classes_No_Name.dot18
-rw-r--r--tests/pyreverse/data/classes_No_Name.html6
-rw-r--r--tests/pyreverse/data/classes_No_Name.mmd6
-rw-r--r--tests/pyreverse/data/classes_No_Name.puml6
-rw-r--r--tests/pyreverse/data/classes_colorized.dot18
-rw-r--r--tests/pyreverse/data/classes_colorized.puml6
-rw-r--r--tests/pyreverse/data/packages_No_Name.dot8
-rw-r--r--tests/pyreverse/data/packages_colorized.dot8
-rw-r--r--tests/pyreverse/functional/class_diagrams/annotations/attributes_annotation.dot4
-rw-r--r--tests/pyreverse/test_diadefs.py17
-rw-r--r--tests/pyreverse/test_diagrams.py5
-rw-r--r--tests/pyreverse/test_inspector.py10
-rw-r--r--tests/pyreverse/test_main.py17
-rw-r--r--tests/pyreverse/test_printer.py2
-rw-r--r--tests/pyreverse/test_printer_factory.py7
-rw-r--r--tests/pyreverse/test_pyreverse_functional.py9
-rw-r--r--tests/pyreverse/test_utils.py22
-rw-r--r--tests/pyreverse/test_writer.py35
-rw-r--r--tests/regrtest_data/imported_module_in_typehint/module_a.py5
-rw-r--r--tests/regrtest_data/imported_module_in_typehint/module_b.py1
-rw-r--r--tests/reporters/unittest_json_reporter.py4
-rw-r--r--tests/reporters/unittest_reporting.py34
-rw-r--r--tests/test_check_parallel.py24
-rw-r--r--tests/test_epylint.py27
-rw-r--r--tests/test_func.py23
-rw-r--r--tests/test_functional.py31
-rw-r--r--tests/test_import_graph.py4
-rw-r--r--tests/test_numversion.py4
-rw-r--r--tests/test_pylint_runners.py70
-rw-r--r--tests/test_regr.py10
-rw-r--r--tests/test_self.py152
-rw-r--r--tests/test_similar.py4
-rw-r--r--tests/testutils/_primer/test_package_to_lint.py4
-rw-r--r--tests/testutils/test_functional_testutils.py4
-rw-r--r--tests/testutils/test_output_line.py18
-rw-r--r--tests/testutils/test_testutils_utils.py42
-rw-r--r--tests/utils/unittest_ast_walker.py17
-rw-r--r--towncrier.toml22
-rw-r--r--tox.ini2
701 files changed, 6814 insertions, 2571 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..7b08ae365
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,44 @@
+# Lines starting with '#' are comments.
+# Each line is a file pattern followed by one or more owners.
+
+# These owners will be the default owners for everything in the repo.
+# Right now there is not default owner to avoid spam
+# * @pierre-sassoulas @DanielNoord @cdce8p @jacobtylerwalls @hippo91
+
+# Order is important. The last matching pattern has the most precedence.
+
+### Core components
+
+# internal message handling
+pylint/message/* @pierre-sassoulas
+tests/message/* @pierre-sassoulas
+
+# typing
+pylint/typing.py @DanielNoord
+
+# multiprocessing (doublethefish is not yet a contributor with write access)
+# pylint/lint/parallel.py @doublethefish
+# tests/test_check_parallel.py @doublethefish
+
+### Pyreverse
+pylint/pyreverse/* @DudeNr33
+tests/pyreverse/* @DudeNr33
+
+### Extensions
+
+# For any all
+pylint/extensions/for_any_all.py @areveny
+tests/functional/ext/for_any_all/* @areveny
+
+# Private import
+pylint/extensions/private_import.py @areveny
+tests/extensions/test_private_import.py @areveny
+tests/functional/ext/private_import/* @areveny
+
+# CodeStyle
+pylint/extensions/code_style.* @cdce8p
+tests/functional/ext/code_style/* @cdce8p
+
+# Typing
+pylint/extensions/typing.* @cdce8p
+tests/functional/ext/typing/* @cdce8p
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 82bd53ea5..dfa06dbbb 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -5,9 +5,9 @@ To ease the process of reviewing your PR, do make sure to complete the following
- [ ] Write a good description on what the PR does.
- [ ] Create a news fragment with `towncrier create <IssueNumber>.<type>` which will be
- included in the changelog. `<type>` can be one of: new_check, removed_check, extension,
- false_positive, false_negative, bugfix, other, internal. If necessary you can write
- details or offer examples on how the new change is supposed to work.
+ included in the changelog. `<type>` can be one of: breaking, user_action, feature,
+ new_check, removed_check, extension, false_positive, false_negative, bugfix, other, internal.
+ If necessary you can write details or offer examples on how the new change is supposed to work.
- [ ] If you used multiple emails or multiple names when contributing, add your mails
and preferred name in ``script/.contributors_aliases.json``
-->
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1cce434b2..58f5c1d41 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -20,6 +20,9 @@ on:
schedule:
- cron: "44 16 * * 4"
+permissions:
+ contents: read
+
jobs:
analyze:
name: Analyze
@@ -39,7 +42,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3.0.2
+ uses: actions/checkout@v3.1.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/primer_comment.yaml b/.github/workflows/primer_comment.yaml
index 97d546ccb..3f901278f 100644
--- a/.github/workflows/primer_comment.yaml
+++ b/.github/workflows/primer_comment.yaml
@@ -16,7 +16,7 @@ env:
# This needs to be the SAME as in the Main and PR job
CACHE_VERSION: 1
KEY_PREFIX: venv-primer
- DEFAULT_PYTHON: "3.10"
+ DEFAULT_PYTHON: "3.11"
permissions:
contents: read
diff --git a/.github/workflows/primer_run_main.yaml b/.github/workflows/primer_run_main.yaml
index 4f89b9e6e..e8e8f983c 100644
--- a/.github/workflows/primer_run_main.yaml
+++ b/.github/workflows/primer_run_main.yaml
@@ -19,6 +19,9 @@ env:
CACHE_VERSION: 1
KEY_PREFIX: venv-primer
+permissions:
+ contents: read
+
jobs:
run-primer:
name: Run / ${{ matrix.python-version }}
@@ -26,13 +29,13 @@ jobs:
timeout-minutes: 60
strategy:
matrix:
- python-version: ["3.7", "3.10"]
+ python-version: ["3.7", "3.11"]
steps:
- name: Check out code from GitHub
- uses: actions/checkout@v3.0.2
+ uses: actions/checkout@v3.1.0
- name: Set up Python ${{ matrix.python-version }}
id: python
- uses: actions/setup-python@v4.2.0
+ uses: actions/setup-python@v4.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -40,7 +43,7 @@ jobs:
# Create a re-usable virtual environment
- name: Create Python virtual environment cache
id: cache-venv
- uses: actions/cache@v3.0.8
+ uses: actions/cache@v3.0.11
with:
path: venv
key:
@@ -62,10 +65,10 @@ jobs:
. venv/bin/activate
python tests/primer/__main__.py prepare --make-commit-string
output=$(python tests/primer/__main__.py prepare --read-commit-string)
- echo "::set-output name=commitstring::$output"
+ echo "commitstring=$output" >> $GITHUB_OUTPUT
- name: Restore projects cache
id: cache-projects
- uses: actions/cache@v3.0.8
+ uses: actions/cache@v3.0.11
with:
path: tests/.pylint_primer_tests/
key: >-
@@ -76,10 +79,11 @@ jobs:
. venv/bin/activate
python tests/primer/__main__.py prepare --clone
- name: Upload output diff
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v3.1.1
with:
name: primer_commitstring
- path: tests/.pylint_primer_tests/commit_string.txt
+ path:
+ tests/.pylint_primer_tests/commit_string_${{ matrix.python-version }}.txt
# Run primer
- name: Run pylint primer
@@ -92,7 +96,7 @@ jobs:
then echo "::warning ::$WARNINGS"
fi
- name: Upload output
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v3.1.1
with:
name: primer_output
path: >-
diff --git a/.github/workflows/primer_run_pr.yaml b/.github/workflows/primer_run_pr.yaml
index 4edfefc44..b8f0815dd 100644
--- a/.github/workflows/primer_run_pr.yaml
+++ b/.github/workflows/primer_run_pr.yaml
@@ -28,6 +28,9 @@ env:
CACHE_VERSION: 1
KEY_PREFIX: venv-primer
+permissions:
+ contents: read
+
jobs:
run-primer:
name: Run / ${{ matrix.python-version }}
@@ -35,15 +38,15 @@ jobs:
timeout-minutes: 120
strategy:
matrix:
- python-version: ["3.7", "3.10"]
+ python-version: ["3.7", "3.11"]
steps:
- name: Check out code from GitHub
- uses: actions/checkout@v3.0.2
+ uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
id: python
- uses: actions/setup-python@v4.2.0
+ uses: actions/setup-python@v4.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -55,7 +58,7 @@ jobs:
# Restore cached Python environment
- name: Restore Python virtual environment
id: cache-venv
- uses: actions/cache@v3.0.8
+ uses: actions/cache@v3.0.11
with:
path: venv
key:
@@ -123,7 +126,7 @@ jobs:
- name: Copy and unzip the commit string
run: |
unzip primer_commitstring.zip
- cp commit_string.txt tests/.pylint_primer_tests/commit_string.txt
+ cp commit_string_${{ matrix.python-version }}.txt tests/.pylint_primer_tests/commit_string_${{ matrix.python-version }}.txt
- name: Unzip the output of 'main'
run: unzip primer_output_main.zip
- name: Get commit string
@@ -131,10 +134,10 @@ jobs:
run: |
. venv/bin/activate
output=$(python tests/primer/__main__.py prepare --read-commit-string)
- echo "::set-output name=commitstring::$output"
+ echo "commitstring=$output" >> $GITHUB_OUTPUT
- name: Restore projects cache
id: cache-projects
- uses: actions/cache@v3.0.8
+ uses: actions/cache@v3.0.11
with:
path: tests/.pylint_primer_tests/
key: >-
@@ -164,14 +167,14 @@ jobs:
then echo "::warning ::$WARNINGS"
fi
- name: Upload output of PR
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v3.1.1
with:
name: primer_output_pr
path:
tests/.pylint_primer_tests/output_${{ steps.python.outputs.python-version
}}_pr.txt
- name: Upload output of 'main'
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v3.1.1
with:
name: primer_output_main
path: output_${{ steps.python.outputs.python-version }}_main.txt
@@ -181,7 +184,7 @@ jobs:
run: |
echo ${{ github.event.pull_request.number }} | tee pr_number.txt
- name: Upload PR number
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v3.1.1
with:
name: pr_number
path: pr_number.txt
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9708980d9..6dbe0562f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,7 +6,10 @@ on:
- published
env:
- DEFAULT_PYTHON: "3.10"
+ DEFAULT_PYTHON: "3.11"
+
+permissions:
+ contents: read
jobs:
release-pypi:
@@ -17,10 +20,10 @@ jobs:
url: https://pypi.org/project/pylint/
steps:
- name: Check out code from Github
- uses: actions/checkout@v3.0.2
+ uses: actions/checkout@v3.1.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
- uses: actions/setup-python@v4.2.0
+ uses: actions/setup-python@v4.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a440ec26d..b28eab353 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.3.0
+ rev: v4.4.0
hooks:
- id: trailing-whitespace
exclude: tests(/\w*)*/functional/t/trailing_whitespaces.py|tests/pyreverse/data/.*.html|doc/data/messages/t/trailing-whitespace/bad.py
@@ -14,8 +14,8 @@ repos:
tests/functional/t/trailing_newlines.py|
doc/data/messages/t/trailing-newlines/bad.py|
)$
- - repo: https://github.com/myint/autoflake
- rev: v1.4
+ - repo: https://github.com/PyCQA/autoflake
+ rev: v2.0.0
hooks:
- id: autoflake
exclude: &fixtures tests(/\w*)*/functional/|tests/input|doc/data/messages|tests(/\w*)*data/
@@ -33,7 +33,7 @@ repos:
exclude: tests(/\w*)*/functional/|tests/input|doc/data/messages|examples/|setup.py|tests(/\w*)*data/
types: [python]
- repo: https://github.com/asottile/pyupgrade
- rev: v2.37.3
+ rev: v3.2.2
hooks:
- id: pyupgrade
args: [--py37-plus]
@@ -44,7 +44,7 @@ repos:
- id: isort
exclude: doc/data/messages/(r/reimported|w/wrong-import-order|u/ungrouped-imports|m/misplaced-future|m/multiple-imports)/bad.py
- repo: https://github.com/psf/black
- rev: 22.6.0
+ rev: 22.10.0
hooks:
- id: black
args: [--safe, --quiet]
@@ -57,7 +57,8 @@ repos:
rev: 5.0.4
hooks:
- id: flake8
- additional_dependencies: [flake8-typing-imports==1.12.0]
+ additional_dependencies:
+ [flake8-bugbear==22.10.27, flake8-typing-imports==1.14.0]
exclude: *fixtures
- repo: local
hooks:
@@ -93,14 +94,14 @@ repos:
files: ^(doc/whatsnew/fragments)
exclude: doc/whatsnew/fragments/_.*.rst
- repo: https://github.com/rstcheck/rstcheck
- rev: "v6.1.0"
+ rev: "v6.1.1"
hooks:
- id: rstcheck
args: ["--report-level=warning"]
files: ^(doc/(.*/)*.*\.rst)
additional_dependencies: [Sphinx==5.0.1]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.971
+ rev: v0.991
hooks:
- id: mypy
name: mypy
@@ -109,16 +110,23 @@ repos:
types: [python]
args: []
require_serial: true
- additional_dependencies: ["platformdirs==2.2.0", "types-pkg_resources==0.1.3"]
+ additional_dependencies:
+ [
+ "isort>=5",
+ "platformdirs==2.2.0",
+ "py==1.11",
+ "tomlkit>=0.10.1",
+ "types-pkg_resources==0.1.3",
+ ]
exclude: tests(/\w*)*/functional/|tests/input|tests(/.*)+/conftest.py|doc/data/messages|tests(/\w*)*data/
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: v3.0.0-alpha.0
+ rev: v3.0.0-alpha.4
hooks:
- id: prettier
args: [--prose-wrap=always, --print-width=88]
exclude: tests(/\w*)*data/
- repo: https://github.com/DanielNoord/pydocstringformatter
- rev: v0.7.0
+ rev: v0.7.2
hooks:
- id: pydocstringformatter
exclude: *fixtures
diff --git a/.pyenchant_pylint_custom_dict.txt b/.pyenchant_pylint_custom_dict.txt
index 2aca384fa..29f42e332 100644
--- a/.pyenchant_pylint_custom_dict.txt
+++ b/.pyenchant_pylint_custom_dict.txt
@@ -31,6 +31,7 @@ bla
bom
bool
boolean
+booleaness
boolop
boundmethod
builtins
@@ -107,6 +108,7 @@ epytext
erroring
etree
expr
+falsey
favour
filepath
filestream
@@ -283,6 +285,7 @@ sep
setcomp
shortstrings
singledispatch
+singledispatchmethod
spammy
sqlalchemy
src
@@ -326,6 +329,7 @@ toplevel
towncrier
tp
truthness
+truthey
tryexcept
txt
typecheck
@@ -359,6 +363,7 @@ vcg's
vectorisation
virtualized
wc
+whitespaces
xfails
xml
xyz
diff --git a/Dockerfile b/Dockerfile
index 7bde0a292..976a56d47 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,6 +2,6 @@ FROM python:3.10.0-alpine3.15
COPY ./ /tmp/build
WORKDIR /tmp/build
-RUN python -m pip install . && rm -rf /tmp/build
+RUN python -m pip install --no-cache-dir . && rm -rf /tmp/build
ENTRYPOINT ["pylint"]
diff --git a/README.rst b/README.rst
index b432b4fb3..f41ad3e23 100644
--- a/README.rst
+++ b/README.rst
@@ -33,10 +33,15 @@
:target: https://bestpractices.coreinfrastructure.org/projects/6328
:alt: CII Best Practices
+.. image:: https://api.securityscorecards.dev/projects/github.com/PyCQA/pylint/badge
+ :target: https://api.securityscorecards.dev/projects/github.com/PyCQA/pylint
+ :alt: OpenSSF Scorecard
+
.. image:: https://img.shields.io/discord/825463413634891776.svg
:target: https://discord.gg/qYxpadCgkx
:alt: Discord
+
What is Pylint?
================
@@ -54,10 +59,19 @@ will know that ``argparse.error(...)`` is in fact a logging call and not an argp
.. _`code smells`: https://martinfowler.com/bliki/CodeSmell.html
Pylint is highly configurable and permits to write plugins in order to add your
-own checks (for example, for internal libraries or an internal rule). Pylint has an
-ecosystem of existing plugins for popular frameworks such as `pylint-django`_ or
-`pylint-sonarjson`_.
+own checks (for example, for internal libraries or an internal rule). Pylint also has an
+ecosystem of existing plugins for popular frameworks and third party libraries.
+
+.. note::
+ Pylint supports the Python standard library out of the box. Third-party
+ libraries are not always supported, so a plugin might be needed. A good place
+ to start is ``PyPI`` which often returns a plugin by searching for
+ ``pylint <library>``. `pylint-pydantic`_, `pylint-django`_ and
+ `pylint-sonarjson`_ are examples of such plugins. More information about plugins
+ and how to load them can be found at :ref:`plugins <plugins>`.
+
+.. _`pylint-pydantic`: https://pypi.org/project/pylint-pydantic
.. _`pylint-django`: https://github.com/PyCQA/pylint-django
.. _`pylint-sonarjson`: https://github.com/omegacen/pylint-sonarjson
@@ -72,11 +86,15 @@ Pylint ships with three additional tools:
- pyreverse_ (standalone tool that generates package and class diagrams.)
- symilar_ (duplicate code finder that is also integrated in pylint)
-- epylint_ (Emacs and Flymake compatible Pylint)
.. _pyreverse: https://pylint.pycqa.org/en/latest/pyreverse.html
.. _symilar: https://pylint.pycqa.org/en/latest/symilar.html
+
+The epylint_ Emacs package, which includes Flymake support, is now maintained
+in `its own repository`_.
+
.. _epylint: https://pylint.pycqa.org/en/latest/user_guide/ide_integration/flymake-emacs.html
+.. _its own repository: https://github.com/emacsorphanage/pylint
Projects that you might want to use alongside pylint include flake8_ (faster and simpler checks
with very few false positives), mypy_, pyright_ or pyre_ (typing checks), bandit_ (security
@@ -84,7 +102,7 @@ oriented checks), black_ and isort_ (auto-formatting), autoflake_ (automated rem
unused imports or variables), pyupgrade_ (automated upgrade to newer python syntax) and
pydocstringformatter_ (automated pep257).
-.. _flake8: https://gitlab.com/pycqa/flake8/
+.. _flake8: https://github.com/PyCQA/flake8
.. _bandit: https://github.com/PyCQA/bandit
.. _mypy: https://github.com/python/mypy
.. _pyright: https://github.com/microsoft/pyright
diff --git a/doc/data/messages/a/anomalous-unicode-escape-in-string/bad.py b/doc/data/messages/a/anomalous-unicode-escape-in-string/bad.py
index 40a2a0caf..40275f055 100644
--- a/doc/data/messages/a/anomalous-unicode-escape-in-string/bad.py
+++ b/doc/data/messages/a/anomalous-unicode-escape-in-string/bad.py
@@ -1 +1 @@
-print(b"\u{0}".format("0394")) # [anomalous-unicode-escape-in-string]
+print(b"\u%b" % b"0394") # [anomalous-unicode-escape-in-string]
diff --git a/doc/data/messages/a/anomalous-unicode-escape-in-string/good.py b/doc/data/messages/a/anomalous-unicode-escape-in-string/good.py
index f2285d70c..c5f4cf46b 100644
--- a/doc/data/messages/a/anomalous-unicode-escape-in-string/good.py
+++ b/doc/data/messages/a/anomalous-unicode-escape-in-string/good.py
@@ -1 +1 @@
-print(b"\\u{0}".format("0394"))
+print(b"\\u%b" % b"0394")
diff --git a/doc/data/messages/a/astroid-error/details.rst b/doc/data/messages/a/astroid-error/details.rst
index ab8204529..96e8f7ade 100644
--- a/doc/data/messages/a/astroid-error/details.rst
+++ b/doc/data/messages/a/astroid-error/details.rst
@@ -1 +1,2 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This is a message linked to an internal problem in pylint. There's nothing to change in your code,
+but maybe in pylint's configuration or installation.
diff --git a/doc/data/messages/a/astroid-error/good.py b/doc/data/messages/a/astroid-error/good.py
deleted file mode 100644
index c40beb573..000000000
--- a/doc/data/messages/a/astroid-error/good.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is a placeholder for correct code for this message.
diff --git a/doc/data/messages/b/bad-dunder-name/bad.py b/doc/data/messages/b/bad-dunder-name/bad.py
new file mode 100644
index 000000000..f01f65010
--- /dev/null
+++ b/doc/data/messages/b/bad-dunder-name/bad.py
@@ -0,0 +1,6 @@
+class Apples:
+ def _init_(self): # [bad-dunder-name]
+ pass
+
+ def __hello__(self): # [bad-dunder-name]
+ print("hello")
diff --git a/doc/data/messages/b/bad-dunder-name/good.py b/doc/data/messages/b/bad-dunder-name/good.py
new file mode 100644
index 000000000..4f0adb9b6
--- /dev/null
+++ b/doc/data/messages/b/bad-dunder-name/good.py
@@ -0,0 +1,6 @@
+class Apples:
+ def __init__(self):
+ pass
+
+ def hello(self):
+ print("hello")
diff --git a/doc/data/messages/b/bad-dunder-name/pylintrc b/doc/data/messages/b/bad-dunder-name/pylintrc
new file mode 100644
index 000000000..c70980544
--- /dev/null
+++ b/doc/data/messages/b/bad-dunder-name/pylintrc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.dunder
diff --git a/doc/data/messages/b/bad-format-string-key/bad.py b/doc/data/messages/b/bad-format-string-key/bad.py
new file mode 100644
index 000000000..346d02d14
--- /dev/null
+++ b/doc/data/messages/b/bad-format-string-key/bad.py
@@ -0,0 +1 @@
+print("%(one)d" % {"one": 1, 2: 2}) # [bad-format-string-key]
diff --git a/doc/data/messages/b/bad-format-string-key/details.rst b/doc/data/messages/b/bad-format-string-key/details.rst
index ab8204529..321b4a0ba 100644
--- a/doc/data/messages/b/bad-format-string-key/details.rst
+++ b/doc/data/messages/b/bad-format-string-key/details.rst
@@ -1 +1,6 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This check only works for old-style string formatting using the '%' operator.
+
+This check only works if the dictionary with the values to be formatted is defined inline.
+Passing a variable will not trigger the check as the other keys in this dictionary may be
+used in other contexts, while an inline defined dictionary is clearly only intended to hold
+the values that should be formatted.
diff --git a/doc/data/messages/b/bad-format-string-key/good.py b/doc/data/messages/b/bad-format-string-key/good.py
index c40beb573..db7cfde04 100644
--- a/doc/data/messages/b/bad-format-string-key/good.py
+++ b/doc/data/messages/b/bad-format-string-key/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+print("%(one)d, %(two)d" % {"one": 1, "two": 2})
diff --git a/doc/data/messages/b/bad-thread-instantiation/bad.py b/doc/data/messages/b/bad-thread-instantiation/bad.py
new file mode 100644
index 000000000..580786d85
--- /dev/null
+++ b/doc/data/messages/b/bad-thread-instantiation/bad.py
@@ -0,0 +1,9 @@
+import threading
+
+
+def thread_target(n):
+ print(n ** 2)
+
+
+thread = threading.Thread(lambda: None) # [bad-thread-instantiation]
+thread.start()
diff --git a/doc/data/messages/b/bad-thread-instantiation/details.rst b/doc/data/messages/b/bad-thread-instantiation/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/b/bad-thread-instantiation/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/b/bad-thread-instantiation/good.py b/doc/data/messages/b/bad-thread-instantiation/good.py
index c40beb573..735fa4da1 100644
--- a/doc/data/messages/b/bad-thread-instantiation/good.py
+++ b/doc/data/messages/b/bad-thread-instantiation/good.py
@@ -1 +1,9 @@
-# This is a placeholder for correct code for this message.
+import threading
+
+
+def thread_target(n):
+ print(n ** 2)
+
+
+thread = threading.Thread(target=thread_target, args=(10,))
+thread.start()
diff --git a/doc/data/messages/b/broad-except/bad.py b/doc/data/messages/b/broad-except/bad.py
deleted file mode 100644
index f4946093e..000000000
--- a/doc/data/messages/b/broad-except/bad.py
+++ /dev/null
@@ -1,4 +0,0 @@
-try:
- 1 / 0
-except Exception: # [broad-except]
- pass
diff --git a/doc/data/messages/b/broad-exception-caught/bad.py b/doc/data/messages/b/broad-exception-caught/bad.py
new file mode 100644
index 000000000..3423925a6
--- /dev/null
+++ b/doc/data/messages/b/broad-exception-caught/bad.py
@@ -0,0 +1,4 @@
+try:
+ 1 / 0
+except Exception: # [broad-exception-caught]
+ pass
diff --git a/doc/data/messages/b/broad-except/good.py b/doc/data/messages/b/broad-exception-caught/good.py
index b02b365b0..b02b365b0 100644
--- a/doc/data/messages/b/broad-except/good.py
+++ b/doc/data/messages/b/broad-exception-caught/good.py
diff --git a/doc/data/messages/b/broad-exception-raised/bad.py b/doc/data/messages/b/broad-exception-raised/bad.py
new file mode 100644
index 000000000..4c8ff3b5a
--- /dev/null
+++ b/doc/data/messages/b/broad-exception-raised/bad.py
@@ -0,0 +1,4 @@
+def small_apple(apple, length):
+ if len(apple) < length:
+ raise Exception("Apple is too small!") # [broad-exception-raised]
+ print(f"{apple} is proper size.")
diff --git a/doc/data/messages/b/broad-exception-raised/good.py b/doc/data/messages/b/broad-exception-raised/good.py
new file mode 100644
index 000000000..a63b1b356
--- /dev/null
+++ b/doc/data/messages/b/broad-exception-raised/good.py
@@ -0,0 +1,4 @@
+def small_apple(apple, length):
+ if len(apple) < length:
+ raise ValueError("Apple is too small!")
+ print(f"{apple} is proper size.")
diff --git a/doc/data/messages/b/broken-noreturn/bad.py b/doc/data/messages/b/broken-noreturn/bad.py
new file mode 100644
index 000000000..77baf763b
--- /dev/null
+++ b/doc/data/messages/b/broken-noreturn/bad.py
@@ -0,0 +1,5 @@
+from typing import NoReturn, Union
+
+
+def exploding_apple(apple) -> Union[None, NoReturn]: # [broken-noreturn]
+ print(f"{apple} is about to explode")
diff --git a/doc/data/messages/b/broken-noreturn/details.rst b/doc/data/messages/b/broken-noreturn/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/b/broken-noreturn/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/b/broken-noreturn/good.py b/doc/data/messages/b/broken-noreturn/good.py
index c40beb573..ce4dc6e98 100644
--- a/doc/data/messages/b/broken-noreturn/good.py
+++ b/doc/data/messages/b/broken-noreturn/good.py
@@ -1 +1,6 @@
-# This is a placeholder for correct code for this message.
+from typing import NoReturn
+
+
+def exploding_apple(apple) -> NoReturn:
+ print(f"{apple} is about to explode")
+ raise Exception("{apple} exploded !")
diff --git a/doc/data/messages/b/broken-noreturn/pylintrc b/doc/data/messages/b/broken-noreturn/pylintrc
new file mode 100644
index 000000000..eb28fc75b
--- /dev/null
+++ b/doc/data/messages/b/broken-noreturn/pylintrc
@@ -0,0 +1,3 @@
+[main]
+py-version=3.7
+load-plugins=pylint.extensions.typing
diff --git a/doc/data/messages/c/condition-evals-to-constant/bad.py b/doc/data/messages/c/condition-evals-to-constant/bad.py
new file mode 100644
index 000000000..f52b24fc0
--- /dev/null
+++ b/doc/data/messages/c/condition-evals-to-constant/bad.py
@@ -0,0 +1,2 @@
+def is_a_fruit(fruit):
+ return bool(fruit in {"apple", "orange"} or True) # [condition-evals-to-constant]
diff --git a/doc/data/messages/c/condition-evals-to-constant/details.rst b/doc/data/messages/c/condition-evals-to-constant/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/c/condition-evals-to-constant/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/c/condition-evals-to-constant/good.py b/doc/data/messages/c/condition-evals-to-constant/good.py
index c40beb573..37e975491 100644
--- a/doc/data/messages/c/condition-evals-to-constant/good.py
+++ b/doc/data/messages/c/condition-evals-to-constant/good.py
@@ -1 +1,2 @@
-# This is a placeholder for correct code for this message.
+def is_a_fruit(fruit):
+ return fruit in {"apple", "orange"}
diff --git a/doc/data/messages/c/config-parse-error/details.rst b/doc/data/messages/c/config-parse-error/details.rst
index ab8204529..4fc0fe076 100644
--- a/doc/data/messages/c/config-parse-error/details.rst
+++ b/doc/data/messages/c/config-parse-error/details.rst
@@ -1 +1 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This is a message linked to a problem in your configuration not your code.
diff --git a/doc/data/messages/c/config-parse-error/good.py b/doc/data/messages/c/config-parse-error/good.py
deleted file mode 100644
index c40beb573..000000000
--- a/doc/data/messages/c/config-parse-error/good.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is a placeholder for correct code for this message.
diff --git a/doc/data/messages/c/confusing-with-statement/bad.py b/doc/data/messages/c/confusing-with-statement/bad.py
new file mode 100644
index 000000000..d84288058
--- /dev/null
+++ b/doc/data/messages/c/confusing-with-statement/bad.py
@@ -0,0 +1,2 @@
+with open('file.txt', 'w') as fh1, fh2: # [confusing-with-statement]
+ pass
diff --git a/doc/data/messages/c/confusing-with-statement/details.rst b/doc/data/messages/c/confusing-with-statement/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/c/confusing-with-statement/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/c/confusing-with-statement/good.py b/doc/data/messages/c/confusing-with-statement/good.py
index c40beb573..e8b39d500 100644
--- a/doc/data/messages/c/confusing-with-statement/good.py
+++ b/doc/data/messages/c/confusing-with-statement/good.py
@@ -1 +1,3 @@
-# This is a placeholder for correct code for this message.
+with open('file.txt', 'w', encoding="utf8") as fh1:
+ with open('file.txt', 'w', encoding="utf8") as fh2:
+ pass
diff --git a/doc/data/messages/c/consider-using-assignment-expr/bad.py b/doc/data/messages/c/consider-using-assignment-expr/bad.py
new file mode 100644
index 000000000..a700537fa
--- /dev/null
+++ b/doc/data/messages/c/consider-using-assignment-expr/bad.py
@@ -0,0 +1,4 @@
+apples = 2
+
+if apples: # [consider-using-assignment-expr]
+ print("God apples!")
diff --git a/doc/data/messages/c/consider-using-assignment-expr/details.rst b/doc/data/messages/c/consider-using-assignment-expr/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/c/consider-using-assignment-expr/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/c/consider-using-assignment-expr/good.py b/doc/data/messages/c/consider-using-assignment-expr/good.py
index c40beb573..a1e402701 100644
--- a/doc/data/messages/c/consider-using-assignment-expr/good.py
+++ b/doc/data/messages/c/consider-using-assignment-expr/good.py
@@ -1 +1,2 @@
-# This is a placeholder for correct code for this message.
+if apples := 2:
+ print("God apples!")
diff --git a/doc/data/messages/c/consider-using-assignment-expr/pylintrc b/doc/data/messages/c/consider-using-assignment-expr/pylintrc
new file mode 100644
index 000000000..14b316d48
--- /dev/null
+++ b/doc/data/messages/c/consider-using-assignment-expr/pylintrc
@@ -0,0 +1,3 @@
+[MAIN]
+py-version=3.8
+load-plugins=pylint.extensions.code_style
diff --git a/doc/data/messages/c/consider-using-augmented-assign/bad.py b/doc/data/messages/c/consider-using-augmented-assign/bad.py
new file mode 100644
index 000000000..90b8931a6
--- /dev/null
+++ b/doc/data/messages/c/consider-using-augmented-assign/bad.py
@@ -0,0 +1,2 @@
+x = 1
+x = x + 1 # [consider-using-augmented-assign]
diff --git a/doc/data/messages/c/consider-using-augmented-assign/good.py b/doc/data/messages/c/consider-using-augmented-assign/good.py
new file mode 100644
index 000000000..3e34f6b26
--- /dev/null
+++ b/doc/data/messages/c/consider-using-augmented-assign/good.py
@@ -0,0 +1,2 @@
+x = 1
+x += 1
diff --git a/doc/data/messages/c/consider-using-augmented-assign/pylintrc b/doc/data/messages/c/consider-using-augmented-assign/pylintrc
new file mode 100644
index 000000000..584602294
--- /dev/null
+++ b/doc/data/messages/c/consider-using-augmented-assign/pylintrc
@@ -0,0 +1,3 @@
+[MAIN]
+load-plugins=pylint.extensions.code_style
+enable=consider-using-augmented-assign
diff --git a/doc/data/messages/d/dict-init-mutate/bad.py b/doc/data/messages/d/dict-init-mutate/bad.py
new file mode 100644
index 000000000..d6d1cfe18
--- /dev/null
+++ b/doc/data/messages/d/dict-init-mutate/bad.py
@@ -0,0 +1,3 @@
+fruit_prices = {} # [dict-init-mutate]
+fruit_prices['apple'] = 1
+fruit_prices['banana'] = 10
diff --git a/doc/data/messages/d/dict-init-mutate/good.py b/doc/data/messages/d/dict-init-mutate/good.py
new file mode 100644
index 000000000..02137f287
--- /dev/null
+++ b/doc/data/messages/d/dict-init-mutate/good.py
@@ -0,0 +1 @@
+fruit_prices = {"apple": 1, "banana": 10}
diff --git a/doc/data/messages/d/dict-init-mutate/pylintrc b/doc/data/messages/d/dict-init-mutate/pylintrc
new file mode 100644
index 000000000..bbe6bd1f7
--- /dev/null
+++ b/doc/data/messages/d/dict-init-mutate/pylintrc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.dict_init_mutate,
diff --git a/doc/data/messages/e/exec-used/details.rst b/doc/data/messages/e/exec-used/details.rst
index 2a61975f2..246857f32 100644
--- a/doc/data/messages/e/exec-used/details.rst
+++ b/doc/data/messages/e/exec-used/details.rst
@@ -1 +1,10 @@
-The available methods and variables used in ``exec()`` may introduce a security hole. You can restrict the use of these variables and methods by passing optional globals and locals parameters (dictionaries) to the ``exec()`` method.
+The available methods and variables used in ``exec()`` may introduce a security hole.
+You can restrict the use of these variables and methods by passing optional globals
+and locals parameters (dictionaries) to the ``exec()`` method.
+
+However, use of ``exec`` is still insecure. For example, consider the following call
+that writes a file to the user's system:
+
+.. code-block:: python
+
+ exec("""\nwith open("file.txt", "w", encoding="utf-8") as file:\n file.write("# code as nefarious as imaginable")\n""")
diff --git a/doc/data/messages/f/fatal/details.rst b/doc/data/messages/f/fatal/details.rst
index ab8204529..1c4303137 100644
--- a/doc/data/messages/f/fatal/details.rst
+++ b/doc/data/messages/f/fatal/details.rst
@@ -1 +1 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This is a message linked to an internal problem in pylint. There's nothing to change in your code.
diff --git a/doc/data/messages/f/fatal/good.py b/doc/data/messages/f/fatal/good.py
deleted file mode 100644
index c40beb573..000000000
--- a/doc/data/messages/f/fatal/good.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is a placeholder for correct code for this message.
diff --git a/doc/data/messages/i/invalid-bool-returned/bad.py b/doc/data/messages/i/invalid-bool-returned/bad.py
new file mode 100644
index 000000000..8e2df42d9
--- /dev/null
+++ b/doc/data/messages/i/invalid-bool-returned/bad.py
@@ -0,0 +1,5 @@
+class BadBool:
+ """__bool__ returns an int"""
+
+ def __bool__(self): # [invalid-bool-returned]
+ return 1
diff --git a/doc/data/messages/i/invalid-bool-returned/details.rst b/doc/data/messages/i/invalid-bool-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-bool-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-bool-returned/good.py b/doc/data/messages/i/invalid-bool-returned/good.py
index c40beb573..33e00c0e3 100644
--- a/doc/data/messages/i/invalid-bool-returned/good.py
+++ b/doc/data/messages/i/invalid-bool-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodBool:
+ """__bool__ returns `bool`"""
+
+ def __bool__(self):
+ return True
diff --git a/doc/data/messages/i/invalid-bytes-returned/bad.py b/doc/data/messages/i/invalid-bytes-returned/bad.py
new file mode 100644
index 000000000..5068c85f9
--- /dev/null
+++ b/doc/data/messages/i/invalid-bytes-returned/bad.py
@@ -0,0 +1,5 @@
+class BadBytes:
+ """__bytes__ returns <type 'str'>"""
+
+ def __bytes__(self): # [invalid-bytes-returned]
+ return "123"
diff --git a/doc/data/messages/i/invalid-bytes-returned/details.rst b/doc/data/messages/i/invalid-bytes-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-bytes-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-bytes-returned/good.py b/doc/data/messages/i/invalid-bytes-returned/good.py
index c40beb573..3bc95489f 100644
--- a/doc/data/messages/i/invalid-bytes-returned/good.py
+++ b/doc/data/messages/i/invalid-bytes-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodBytes:
+ """__bytes__ returns <type 'bytes'>"""
+
+ def __bytes__(self):
+ return b"some bytes"
diff --git a/doc/data/messages/i/invalid-characters-in-docstring/details.rst b/doc/data/messages/i/invalid-characters-in-docstring/details.rst
index ab8204529..9977db144 100644
--- a/doc/data/messages/i/invalid-characters-in-docstring/details.rst
+++ b/doc/data/messages/i/invalid-characters-in-docstring/details.rst
@@ -1 +1,2 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This is a message linked to an internal problem in enchant. There's nothing to change in your code,
+but maybe in pylint's configuration or the way you installed the 'enchant' system library.
diff --git a/doc/data/messages/i/invalid-characters-in-docstring/good.py b/doc/data/messages/i/invalid-characters-in-docstring/good.py
deleted file mode 100644
index c40beb573..000000000
--- a/doc/data/messages/i/invalid-characters-in-docstring/good.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is a placeholder for correct code for this message.
diff --git a/doc/data/messages/i/invalid-class-object/bad.py b/doc/data/messages/i/invalid-class-object/bad.py
new file mode 100644
index 000000000..5c6a6f8df
--- /dev/null
+++ b/doc/data/messages/i/invalid-class-object/bad.py
@@ -0,0 +1,5 @@
+class Apple:
+ pass
+
+
+Apple.__class__ = 1 # [invalid-class-object]
diff --git a/doc/data/messages/i/invalid-class-object/details.rst b/doc/data/messages/i/invalid-class-object/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-class-object/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-class-object/good.py b/doc/data/messages/i/invalid-class-object/good.py
index c40beb573..3b50097f1 100644
--- a/doc/data/messages/i/invalid-class-object/good.py
+++ b/doc/data/messages/i/invalid-class-object/good.py
@@ -1 +1,9 @@
-# This is a placeholder for correct code for this message.
+class Apple:
+ pass
+
+
+class RedDelicious:
+ pass
+
+
+Apple.__class__ = RedDelicious
diff --git a/doc/data/messages/i/invalid-format-returned/bad.py b/doc/data/messages/i/invalid-format-returned/bad.py
new file mode 100644
index 000000000..21412d91b
--- /dev/null
+++ b/doc/data/messages/i/invalid-format-returned/bad.py
@@ -0,0 +1,5 @@
+class BadFormat:
+ """__format__ returns <type 'int'>"""
+
+ def __format__(self, format_spec): # [invalid-format-returned]
+ return 1
diff --git a/doc/data/messages/i/invalid-format-returned/details.rst b/doc/data/messages/i/invalid-format-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-format-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-format-returned/good.py b/doc/data/messages/i/invalid-format-returned/good.py
index c40beb573..69ab6fc07 100644
--- a/doc/data/messages/i/invalid-format-returned/good.py
+++ b/doc/data/messages/i/invalid-format-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodFormat:
+ """__format__ returns <type 'str'>"""
+
+ def __format__(self, format_spec):
+ return "hello!"
diff --git a/doc/data/messages/i/invalid-getnewargs-ex-returned/bad.py b/doc/data/messages/i/invalid-getnewargs-ex-returned/bad.py
new file mode 100644
index 000000000..2f5c5742e
--- /dev/null
+++ b/doc/data/messages/i/invalid-getnewargs-ex-returned/bad.py
@@ -0,0 +1,5 @@
+class BadGetNewArgsEx:
+ """__getnewargs_ex__ returns tuple with incorrect arg length"""
+
+ def __getnewargs_ex__(self): # [invalid-getnewargs-ex-returned]
+ return (tuple(1), dict(x="y"), 1)
diff --git a/doc/data/messages/i/invalid-getnewargs-ex-returned/details.rst b/doc/data/messages/i/invalid-getnewargs-ex-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-getnewargs-ex-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-getnewargs-ex-returned/good.py b/doc/data/messages/i/invalid-getnewargs-ex-returned/good.py
index c40beb573..b9cbb0288 100644
--- a/doc/data/messages/i/invalid-getnewargs-ex-returned/good.py
+++ b/doc/data/messages/i/invalid-getnewargs-ex-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodGetNewArgsEx:
+ """__getnewargs_ex__ returns <type 'tuple'>"""
+
+ def __getnewargs_ex__(self):
+ return ((1,), {"2": 2})
diff --git a/doc/data/messages/i/invalid-getnewargs-returned/bad.py b/doc/data/messages/i/invalid-getnewargs-returned/bad.py
new file mode 100644
index 000000000..0864f7deb
--- /dev/null
+++ b/doc/data/messages/i/invalid-getnewargs-returned/bad.py
@@ -0,0 +1,5 @@
+class BadGetNewArgs:
+ """__getnewargs__ returns an integer"""
+
+ def __getnewargs__(self): # [invalid-getnewargs-returned]
+ return 1
diff --git a/doc/data/messages/i/invalid-getnewargs-returned/details.rst b/doc/data/messages/i/invalid-getnewargs-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-getnewargs-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-getnewargs-returned/good.py b/doc/data/messages/i/invalid-getnewargs-returned/good.py
index c40beb573..bdc547d4d 100644
--- a/doc/data/messages/i/invalid-getnewargs-returned/good.py
+++ b/doc/data/messages/i/invalid-getnewargs-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodGetNewArgs:
+ """__getnewargs__ returns <type 'tuple'>"""
+
+ def __getnewargs__(self):
+ return (1, 2)
diff --git a/doc/data/messages/i/invalid-hash-returned/bad.py b/doc/data/messages/i/invalid-hash-returned/bad.py
new file mode 100644
index 000000000..ef0a9cb3f
--- /dev/null
+++ b/doc/data/messages/i/invalid-hash-returned/bad.py
@@ -0,0 +1,5 @@
+class BadHash:
+ """__hash__ returns dict"""
+
+ def __hash__(self): # [invalid-hash-returned]
+ return {}
diff --git a/doc/data/messages/i/invalid-hash-returned/details.rst b/doc/data/messages/i/invalid-hash-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-hash-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-hash-returned/good.py b/doc/data/messages/i/invalid-hash-returned/good.py
index c40beb573..c912bf5a4 100644
--- a/doc/data/messages/i/invalid-hash-returned/good.py
+++ b/doc/data/messages/i/invalid-hash-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodHash:
+ """__hash__ returns `int`"""
+
+ def __hash__(self):
+ return 19
diff --git a/doc/data/messages/i/invalid-index-returned/bad.py b/doc/data/messages/i/invalid-index-returned/bad.py
new file mode 100644
index 000000000..197de0104
--- /dev/null
+++ b/doc/data/messages/i/invalid-index-returned/bad.py
@@ -0,0 +1,5 @@
+class BadIndex:
+ """__index__ returns a dict"""
+
+ def __index__(self): # [invalid-index-returned]
+ return {"19": "19"}
diff --git a/doc/data/messages/i/invalid-index-returned/details.rst b/doc/data/messages/i/invalid-index-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-index-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-index-returned/good.py b/doc/data/messages/i/invalid-index-returned/good.py
index c40beb573..3455ac278 100644
--- a/doc/data/messages/i/invalid-index-returned/good.py
+++ b/doc/data/messages/i/invalid-index-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodIndex:
+ """__index__ returns <type 'int'>"""
+
+ def __index__(self):
+ return 19
diff --git a/doc/data/messages/i/invalid-length-hint-returned/bad.py b/doc/data/messages/i/invalid-length-hint-returned/bad.py
new file mode 100644
index 000000000..9ec400ccc
--- /dev/null
+++ b/doc/data/messages/i/invalid-length-hint-returned/bad.py
@@ -0,0 +1,5 @@
+class BadLengthHint:
+ """__length_hint__ returns non-int"""
+
+ def __length_hint__(self): # [invalid-length-hint-returned]
+ return 3.0
diff --git a/doc/data/messages/i/invalid-length-hint-returned/details.rst b/doc/data/messages/i/invalid-length-hint-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-length-hint-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-length-hint-returned/good.py b/doc/data/messages/i/invalid-length-hint-returned/good.py
index c40beb573..ec294183a 100644
--- a/doc/data/messages/i/invalid-length-hint-returned/good.py
+++ b/doc/data/messages/i/invalid-length-hint-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class GoodLengthHint:
+ """__length_hint__ returns <type 'int'>"""
+
+ def __length_hint__(self):
+ return 10
diff --git a/doc/data/messages/i/invalid-metaclass/bad.py b/doc/data/messages/i/invalid-metaclass/bad.py
new file mode 100644
index 000000000..301b4f20e
--- /dev/null
+++ b/doc/data/messages/i/invalid-metaclass/bad.py
@@ -0,0 +1,2 @@
+class Apple(metaclass=int): # [invalid-metaclass]
+ pass
diff --git a/doc/data/messages/i/invalid-metaclass/details.rst b/doc/data/messages/i/invalid-metaclass/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-metaclass/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-metaclass/good.py b/doc/data/messages/i/invalid-metaclass/good.py
index c40beb573..e8b90fc01 100644
--- a/doc/data/messages/i/invalid-metaclass/good.py
+++ b/doc/data/messages/i/invalid-metaclass/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class Plant:
+ pass
+
+class Apple(Plant):
+ pass
diff --git a/doc/data/messages/i/invalid-repr-returned/bad.py b/doc/data/messages/i/invalid-repr-returned/bad.py
new file mode 100644
index 000000000..33d22256c
--- /dev/null
+++ b/doc/data/messages/i/invalid-repr-returned/bad.py
@@ -0,0 +1,5 @@
+class Repr:
+ """__repr__ returns <type 'int'>"""
+
+ def __repr__(self): # [invalid-repr-returned]
+ return 1
diff --git a/doc/data/messages/i/invalid-repr-returned/details.rst b/doc/data/messages/i/invalid-repr-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-repr-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-repr-returned/good.py b/doc/data/messages/i/invalid-repr-returned/good.py
index c40beb573..120fbc4d7 100644
--- a/doc/data/messages/i/invalid-repr-returned/good.py
+++ b/doc/data/messages/i/invalid-repr-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class Repr:
+ """__repr__ returns <type 'str'>"""
+
+ def __repr__(self):
+ return "apples"
diff --git a/doc/data/messages/i/invalid-slice-step/bad.py b/doc/data/messages/i/invalid-slice-step/bad.py
new file mode 100644
index 000000000..a860ce14a
--- /dev/null
+++ b/doc/data/messages/i/invalid-slice-step/bad.py
@@ -0,0 +1,3 @@
+LETTERS = ["a", "b", "c", "d"]
+
+LETTERS[::0] # [invalid-slice-step]
diff --git a/doc/data/messages/i/invalid-slice-step/good.py b/doc/data/messages/i/invalid-slice-step/good.py
new file mode 100644
index 000000000..c81d80331
--- /dev/null
+++ b/doc/data/messages/i/invalid-slice-step/good.py
@@ -0,0 +1,3 @@
+LETTERS = ["a", "b", "c", "d"]
+
+LETTERS[::2]
diff --git a/doc/data/messages/i/invalid-str-returned/bad.py b/doc/data/messages/i/invalid-str-returned/bad.py
new file mode 100644
index 000000000..6826ce325
--- /dev/null
+++ b/doc/data/messages/i/invalid-str-returned/bad.py
@@ -0,0 +1,5 @@
+class Str:
+ """__str__ returns int"""
+
+ def __str__(self): # [invalid-str-returned]
+ return 1
diff --git a/doc/data/messages/i/invalid-str-returned/details.rst b/doc/data/messages/i/invalid-str-returned/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/invalid-str-returned/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/invalid-str-returned/good.py b/doc/data/messages/i/invalid-str-returned/good.py
index c40beb573..bf2682b23 100644
--- a/doc/data/messages/i/invalid-str-returned/good.py
+++ b/doc/data/messages/i/invalid-str-returned/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class Str:
+ """__str__ returns <type 'str'>"""
+
+ def __str__(self):
+ return "oranges"
diff --git a/doc/data/messages/i/isinstance-second-argument-not-valid-type/bad.py b/doc/data/messages/i/isinstance-second-argument-not-valid-type/bad.py
new file mode 100644
index 000000000..5fb3b8375
--- /dev/null
+++ b/doc/data/messages/i/isinstance-second-argument-not-valid-type/bad.py
@@ -0,0 +1 @@
+isinstance("apples and oranges", hex) # [isinstance-second-argument-not-valid-type]
diff --git a/doc/data/messages/i/isinstance-second-argument-not-valid-type/details.rst b/doc/data/messages/i/isinstance-second-argument-not-valid-type/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/i/isinstance-second-argument-not-valid-type/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/i/isinstance-second-argument-not-valid-type/good.py b/doc/data/messages/i/isinstance-second-argument-not-valid-type/good.py
index c40beb573..c1df5fca8 100644
--- a/doc/data/messages/i/isinstance-second-argument-not-valid-type/good.py
+++ b/doc/data/messages/i/isinstance-second-argument-not-valid-type/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+isinstance("apples and oranges", str)
diff --git a/doc/data/messages/m/magic-value-comparison/bad.py b/doc/data/messages/m/magic-value-comparison/bad.py
new file mode 100644
index 000000000..536659abe
--- /dev/null
+++ b/doc/data/messages/m/magic-value-comparison/bad.py
@@ -0,0 +1,10 @@
+import random
+
+measurement = random.randint(0, 200)
+above_threshold = False
+i = 0
+while i < 5: # [magic-value-comparison]
+ above_threshold = measurement > 100 # [magic-value-comparison]
+ if above_threshold:
+ break
+ measurement = random.randint(0, 200)
diff --git a/doc/data/messages/m/magic-value-comparison/good.py b/doc/data/messages/m/magic-value-comparison/good.py
new file mode 100644
index 000000000..4b8960906
--- /dev/null
+++ b/doc/data/messages/m/magic-value-comparison/good.py
@@ -0,0 +1,15 @@
+import random
+
+MAX_NUM_OF_ITERATIONS = 5
+THRESHOLD_VAL = 100
+MIN_MEASUREMENT_VAL = 0
+MAX_MEASUREMENT_VAL = 200
+
+measurement = random.randint(MIN_MEASUREMENT_VAL, MAX_MEASUREMENT_VAL)
+above_threshold = False
+i = 0
+while i < MAX_NUM_OF_ITERATIONS:
+ above_threshold = measurement > THRESHOLD_VAL
+ if above_threshold:
+ break
+ measurement = random.randint(MIN_MEASUREMENT_VAL, MAX_MEASUREMENT_VAL)
diff --git a/doc/data/messages/m/magic-value-comparison/pylintrc b/doc/data/messages/m/magic-value-comparison/pylintrc
new file mode 100644
index 000000000..c4980c135
--- /dev/null
+++ b/doc/data/messages/m/magic-value-comparison/pylintrc
@@ -0,0 +1,2 @@
+[main]
+load-plugins=pylint.extensions.magic_value
diff --git a/doc/data/messages/m/method-check-failed/details.rst b/doc/data/messages/m/method-check-failed/details.rst
index ab8204529..1c4303137 100644
--- a/doc/data/messages/m/method-check-failed/details.rst
+++ b/doc/data/messages/m/method-check-failed/details.rst
@@ -1 +1 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This is a message linked to an internal problem in pylint. There's nothing to change in your code.
diff --git a/doc/data/messages/m/method-check-failed/good.py b/doc/data/messages/m/method-check-failed/good.py
deleted file mode 100644
index c40beb573..000000000
--- a/doc/data/messages/m/method-check-failed/good.py
+++ /dev/null
@@ -1 +0,0 @@
-# This is a placeholder for correct code for this message.
diff --git a/doc/data/messages/m/misplaced-comparison-constant/bad.py b/doc/data/messages/m/misplaced-comparison-constant/bad.py
new file mode 100644
index 000000000..1a5712a32
--- /dev/null
+++ b/doc/data/messages/m/misplaced-comparison-constant/bad.py
@@ -0,0 +1,8 @@
+def compare_apples(apples=20):
+ for i in range(10):
+ if 5 <= i: # [misplaced-comparison-constant]
+ pass
+ if 1 == i: # [misplaced-comparison-constant]
+ pass
+ if 20 < len(apples): # [misplaced-comparison-constant]
+ pass
diff --git a/doc/data/messages/m/misplaced-comparison-constant/details.rst b/doc/data/messages/m/misplaced-comparison-constant/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/m/misplaced-comparison-constant/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/m/misplaced-comparison-constant/good.py b/doc/data/messages/m/misplaced-comparison-constant/good.py
index c40beb573..ba00a7f23 100644
--- a/doc/data/messages/m/misplaced-comparison-constant/good.py
+++ b/doc/data/messages/m/misplaced-comparison-constant/good.py
@@ -1 +1,8 @@
-# This is a placeholder for correct code for this message.
+def compare_apples(apples=20):
+ for i in range(10):
+ if i >= 5:
+ pass
+ if i == 1:
+ pass
+ if len(apples) > 20:
+ pass
diff --git a/doc/data/messages/m/misplaced-comparison-constant/pylintrc b/doc/data/messages/m/misplaced-comparison-constant/pylintrc
new file mode 100644
index 000000000..aa3b1f8eb
--- /dev/null
+++ b/doc/data/messages/m/misplaced-comparison-constant/pylintrc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.comparison_placement
diff --git a/doc/data/messages/m/missing-raises-doc/pylintrc b/doc/data/messages/m/missing-raises-doc/pylintrc
index 4547f9811..7bdd1242d 100644
--- a/doc/data/messages/m/missing-raises-doc/pylintrc
+++ b/doc/data/messages/m/missing-raises-doc/pylintrc
@@ -1,2 +1,5 @@
[MAIN]
load-plugins = pylint.extensions.docparams
+
+[BASIC]
+accept-no-raise-doc = no
diff --git a/doc/data/messages/m/modified-iterating-dict/bad.py b/doc/data/messages/m/modified-iterating-dict/bad.py
new file mode 100644
index 000000000..cd31a62db
--- /dev/null
+++ b/doc/data/messages/m/modified-iterating-dict/bad.py
@@ -0,0 +1,6 @@
+fruits = {"apple": 1, "orange": 2, "mango": 3}
+
+i = 0
+for fruit in fruits:
+ fruits["apple"] = i # [modified-iterating-dict]
+ i += 1
diff --git a/doc/data/messages/m/modified-iterating-dict/details.rst b/doc/data/messages/m/modified-iterating-dict/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/m/modified-iterating-dict/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/m/modified-iterating-dict/good.py b/doc/data/messages/m/modified-iterating-dict/good.py
index c40beb573..8755a6c45 100644
--- a/doc/data/messages/m/modified-iterating-dict/good.py
+++ b/doc/data/messages/m/modified-iterating-dict/good.py
@@ -1 +1,6 @@
-# This is a placeholder for correct code for this message.
+fruits = {"apple": 1, "orange": 2, "mango": 3}
+
+i = 0
+for fruit in fruits.copy():
+ fruits["apple"] = i
+ i += 1
diff --git a/doc/data/messages/m/modified-iterating-list/bad.py b/doc/data/messages/m/modified-iterating-list/bad.py
new file mode 100644
index 000000000..57d77150e
--- /dev/null
+++ b/doc/data/messages/m/modified-iterating-list/bad.py
@@ -0,0 +1,3 @@
+fruits = ["apple", "orange", "mango"]
+for fruit in fruits:
+ fruits.append("pineapple") # [modified-iterating-list]
diff --git a/doc/data/messages/m/modified-iterating-list/details.rst b/doc/data/messages/m/modified-iterating-list/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/m/modified-iterating-list/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/m/modified-iterating-list/good.py b/doc/data/messages/m/modified-iterating-list/good.py
index c40beb573..0132f8b64 100644
--- a/doc/data/messages/m/modified-iterating-list/good.py
+++ b/doc/data/messages/m/modified-iterating-list/good.py
@@ -1 +1,3 @@
-# This is a placeholder for correct code for this message.
+fruits = ["apple", "orange", "mango"]
+for fruit in fruits.copy():
+ fruits.append("pineapple")
diff --git a/doc/data/messages/m/modified-iterating-set/bad.py b/doc/data/messages/m/modified-iterating-set/bad.py
new file mode 100644
index 000000000..bd82a564f
--- /dev/null
+++ b/doc/data/messages/m/modified-iterating-set/bad.py
@@ -0,0 +1,3 @@
+fruits = {"apple", "orange", "mango"}
+for fruit in fruits:
+ fruits.add(fruit + "yum") # [modified-iterating-set]
diff --git a/doc/data/messages/m/modified-iterating-set/details.rst b/doc/data/messages/m/modified-iterating-set/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/m/modified-iterating-set/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/m/modified-iterating-set/good.py b/doc/data/messages/m/modified-iterating-set/good.py
index c40beb573..0af8e2426 100644
--- a/doc/data/messages/m/modified-iterating-set/good.py
+++ b/doc/data/messages/m/modified-iterating-set/good.py
@@ -1 +1,3 @@
-# This is a placeholder for correct code for this message.
+fruits = {"apple", "orange", "mango"}
+for fruit in fruits.copy():
+ fruits.add(fruit + "yum")
diff --git a/doc/data/messages/m/multiple-statements/bad.py b/doc/data/messages/m/multiple-statements/bad.py
new file mode 100644
index 000000000..754eede36
--- /dev/null
+++ b/doc/data/messages/m/multiple-statements/bad.py
@@ -0,0 +1,5 @@
+fruits = ["apple", "orange", "mango"]
+
+if "apple" in fruits: pass # [multiple-statements]
+else:
+ print("no apples!")
diff --git a/doc/data/messages/m/multiple-statements/details.rst b/doc/data/messages/m/multiple-statements/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/m/multiple-statements/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/m/multiple-statements/good.py b/doc/data/messages/m/multiple-statements/good.py
index c40beb573..9328c83fd 100644
--- a/doc/data/messages/m/multiple-statements/good.py
+++ b/doc/data/messages/m/multiple-statements/good.py
@@ -1 +1,6 @@
-# This is a placeholder for correct code for this message.
+fruits = ["apple", "orange", "mango"]
+
+if "apple" in fruits:
+ pass
+else:
+ print("no apples!")
diff --git a/doc/data/messages/n/named-expr-without-context/bad.py b/doc/data/messages/n/named-expr-without-context/bad.py
new file mode 100644
index 000000000..c5d2ffba7
--- /dev/null
+++ b/doc/data/messages/n/named-expr-without-context/bad.py
@@ -0,0 +1 @@
+(a := 42) # [named-expr-without-context]
diff --git a/doc/data/messages/n/named-expr-without-context/good.py b/doc/data/messages/n/named-expr-without-context/good.py
new file mode 100644
index 000000000..50f6b2621
--- /dev/null
+++ b/doc/data/messages/n/named-expr-without-context/good.py
@@ -0,0 +1,2 @@
+if (a := 42):
+ print('Success')
diff --git a/doc/data/messages/n/nan-comparison/bad.py b/doc/data/messages/n/nan-comparison/bad.py
new file mode 100644
index 000000000..911686520
--- /dev/null
+++ b/doc/data/messages/n/nan-comparison/bad.py
@@ -0,0 +1,5 @@
+import numpy as np
+
+
+def both_nan(x, y) -> bool:
+ return x == np.NaN and y == float("nan") # [nan-comparison, nan-comparison]
diff --git a/doc/data/messages/n/nan-comparison/details.rst b/doc/data/messages/n/nan-comparison/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/nan-comparison/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/nan-comparison/good.py b/doc/data/messages/n/nan-comparison/good.py
index c40beb573..31f54edf4 100644
--- a/doc/data/messages/n/nan-comparison/good.py
+++ b/doc/data/messages/n/nan-comparison/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+import numpy as np
+
+
+def both_nan(x, y) -> bool:
+ return np.isnan(x) and np.isnan(y)
diff --git a/doc/data/messages/n/nested-min-max/bad.py b/doc/data/messages/n/nested-min-max/bad.py
new file mode 100644
index 000000000..b3e13db3a
--- /dev/null
+++ b/doc/data/messages/n/nested-min-max/bad.py
@@ -0,0 +1 @@
+print(min(1, min(2, 3))) # [nested-min-max]
diff --git a/doc/data/messages/n/nested-min-max/good.py b/doc/data/messages/n/nested-min-max/good.py
new file mode 100644
index 000000000..2d348b224
--- /dev/null
+++ b/doc/data/messages/n/nested-min-max/good.py
@@ -0,0 +1 @@
+print(min(1, 2, 3))
diff --git a/doc/data/messages/n/no-classmethod-decorator/bad.py b/doc/data/messages/n/no-classmethod-decorator/bad.py
new file mode 100644
index 000000000..55c4f4d0f
--- /dev/null
+++ b/doc/data/messages/n/no-classmethod-decorator/bad.py
@@ -0,0 +1,11 @@
+class Fruit:
+ COLORS = []
+
+ def __init__(self, color):
+ self.color = color
+
+ def pick_colors(cls, *args):
+ """classmethod to pick fruit colors"""
+ cls.COLORS = args
+
+ pick_colors = classmethod(pick_colors) # [no-classmethod-decorator]
diff --git a/doc/data/messages/n/no-classmethod-decorator/details.rst b/doc/data/messages/n/no-classmethod-decorator/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/no-classmethod-decorator/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/no-classmethod-decorator/good.py b/doc/data/messages/n/no-classmethod-decorator/good.py
index c40beb573..9b70c769d 100644
--- a/doc/data/messages/n/no-classmethod-decorator/good.py
+++ b/doc/data/messages/n/no-classmethod-decorator/good.py
@@ -1 +1,10 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ COLORS = []
+
+ def __init__(self, color):
+ self.color = color
+
+ @classmethod
+ def pick_colors(cls, *args):
+ """classmethod to pick fruit colors"""
+ cls.COLORS = args
diff --git a/doc/data/messages/n/non-ascii-module-import/bad.py b/doc/data/messages/n/non-ascii-module-import/bad.py
new file mode 100644
index 000000000..ce2e811c6
--- /dev/null
+++ b/doc/data/messages/n/non-ascii-module-import/bad.py
@@ -0,0 +1,3 @@
+from os.path import join as łos # [non-ascii-module-import]
+
+foo = łos("a", "b")
diff --git a/doc/data/messages/n/non-ascii-module-import/details.rst b/doc/data/messages/n/non-ascii-module-import/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/non-ascii-module-import/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/non-ascii-module-import/good.py b/doc/data/messages/n/non-ascii-module-import/good.py
index c40beb573..388a5c78e 100644
--- a/doc/data/messages/n/non-ascii-module-import/good.py
+++ b/doc/data/messages/n/non-ascii-module-import/good.py
@@ -1 +1,3 @@
-# This is a placeholder for correct code for this message.
+from os.path import join as os_join
+
+foo = os_join("a", "b")
diff --git a/doc/data/messages/n/non-ascii-name/bad.py b/doc/data/messages/n/non-ascii-name/bad.py
new file mode 100644
index 000000000..954532794
--- /dev/null
+++ b/doc/data/messages/n/non-ascii-name/bad.py
@@ -0,0 +1 @@
+ápple_count = 4444 # [non-ascii-name]
diff --git a/doc/data/messages/n/non-ascii-name/details.rst b/doc/data/messages/n/non-ascii-name/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/non-ascii-name/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/non-ascii-name/good.py b/doc/data/messages/n/non-ascii-name/good.py
index c40beb573..bbddb08d5 100644
--- a/doc/data/messages/n/non-ascii-name/good.py
+++ b/doc/data/messages/n/non-ascii-name/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+apple_count = 4444
diff --git a/doc/data/messages/n/non-str-assignment-to-dunder-name/bad.py b/doc/data/messages/n/non-str-assignment-to-dunder-name/bad.py
new file mode 100644
index 000000000..59f13a13c
--- /dev/null
+++ b/doc/data/messages/n/non-str-assignment-to-dunder-name/bad.py
@@ -0,0 +1,5 @@
+class Fruit:
+ pass
+
+
+Fruit.__name__ = 1 # [non-str-assignment-to-dunder-name]
diff --git a/doc/data/messages/n/non-str-assignment-to-dunder-name/details.rst b/doc/data/messages/n/non-str-assignment-to-dunder-name/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/non-str-assignment-to-dunder-name/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/non-str-assignment-to-dunder-name/good.py b/doc/data/messages/n/non-str-assignment-to-dunder-name/good.py
index c40beb573..ff55f1800 100644
--- a/doc/data/messages/n/non-str-assignment-to-dunder-name/good.py
+++ b/doc/data/messages/n/non-str-assignment-to-dunder-name/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ pass
+
+
+Fruit.__name__ = "FRUIT"
diff --git a/doc/data/messages/n/nonlocal-without-binding/bad.py b/doc/data/messages/n/nonlocal-without-binding/bad.py
new file mode 100644
index 000000000..6a166e09f
--- /dev/null
+++ b/doc/data/messages/n/nonlocal-without-binding/bad.py
@@ -0,0 +1,3 @@
+class Fruit:
+ def get_color(self):
+ nonlocal colors # [nonlocal-without-binding]
diff --git a/doc/data/messages/n/nonlocal-without-binding/details.rst b/doc/data/messages/n/nonlocal-without-binding/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/nonlocal-without-binding/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/nonlocal-without-binding/good.py b/doc/data/messages/n/nonlocal-without-binding/good.py
index c40beb573..cce884ac8 100644
--- a/doc/data/messages/n/nonlocal-without-binding/good.py
+++ b/doc/data/messages/n/nonlocal-without-binding/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ colors = ["red", "green"]
+
+ def get_color(self):
+ nonlocal colors
diff --git a/doc/data/messages/n/not-a-mapping/bad.py b/doc/data/messages/n/not-a-mapping/bad.py
new file mode 100644
index 000000000..79ca9215e
--- /dev/null
+++ b/doc/data/messages/n/not-a-mapping/bad.py
@@ -0,0 +1,5 @@
+def print_colors(**colors):
+ print(colors)
+
+
+print_colors(**list("red", "black")) # [not-a-mapping]
diff --git a/doc/data/messages/n/not-a-mapping/details.rst b/doc/data/messages/n/not-a-mapping/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/n/not-a-mapping/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/n/not-a-mapping/good.py b/doc/data/messages/n/not-a-mapping/good.py
index c40beb573..3de53ac5d 100644
--- a/doc/data/messages/n/not-a-mapping/good.py
+++ b/doc/data/messages/n/not-a-mapping/good.py
@@ -1 +1,5 @@
-# This is a placeholder for correct code for this message.
+def print_colors(**colors):
+ print(colors)
+
+
+print_colors(**dict(red=1, black=2))
diff --git a/doc/data/messages/p/parse-error/details.rst b/doc/data/messages/p/parse-error/details.rst
index ab8204529..1c4303137 100644
--- a/doc/data/messages/p/parse-error/details.rst
+++ b/doc/data/messages/p/parse-error/details.rst
@@ -1 +1 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+This is a message linked to an internal problem in pylint. There's nothing to change in your code.
diff --git a/doc/data/messages/p/possibly-unused-variable/bad.py b/doc/data/messages/p/possibly-unused-variable/bad.py
new file mode 100644
index 000000000..b64aee88b
--- /dev/null
+++ b/doc/data/messages/p/possibly-unused-variable/bad.py
@@ -0,0 +1,4 @@
+def choose_fruits(fruits):
+ print(fruits)
+ color = "red" # [possibly-unused-variable]
+ return locals()
diff --git a/doc/data/messages/p/possibly-unused-variable/details.rst b/doc/data/messages/p/possibly-unused-variable/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/p/possibly-unused-variable/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/p/possibly-unused-variable/good.py b/doc/data/messages/p/possibly-unused-variable/good.py
index c40beb573..095118328 100644
--- a/doc/data/messages/p/possibly-unused-variable/good.py
+++ b/doc/data/messages/p/possibly-unused-variable/good.py
@@ -1 +1,6 @@
-# This is a placeholder for correct code for this message.
+def choose_fruits(fruits):
+ current_locals = locals()
+ print(fruits)
+ color = "red"
+ print(color)
+ return current_locals
diff --git a/doc/data/messages/p/preferred-module/bad.py b/doc/data/messages/p/preferred-module/bad.py
new file mode 100644
index 000000000..a047ff36d
--- /dev/null
+++ b/doc/data/messages/p/preferred-module/bad.py
@@ -0,0 +1 @@
+import urllib # [preferred-module]
diff --git a/doc/data/messages/p/preferred-module/details.rst b/doc/data/messages/p/preferred-module/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/p/preferred-module/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/p/preferred-module/good.py b/doc/data/messages/p/preferred-module/good.py
index c40beb573..20b15530d 100644
--- a/doc/data/messages/p/preferred-module/good.py
+++ b/doc/data/messages/p/preferred-module/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+import requests
diff --git a/doc/data/messages/p/preferred-module/pylintrc b/doc/data/messages/p/preferred-module/pylintrc
new file mode 100644
index 000000000..00ee49930
--- /dev/null
+++ b/doc/data/messages/p/preferred-module/pylintrc
@@ -0,0 +1,2 @@
+[IMPORTS]
+preferred-modules=urllib:requests,
diff --git a/doc/data/messages/r/redefined-outer-name/bad.py b/doc/data/messages/r/redefined-outer-name/bad.py
new file mode 100644
index 000000000..3d03c9cd5
--- /dev/null
+++ b/doc/data/messages/r/redefined-outer-name/bad.py
@@ -0,0 +1,6 @@
+count = 10
+
+
+def count_it(count): # [redefined-outer-name]
+ for i in range(count):
+ print(i)
diff --git a/doc/data/messages/r/redefined-outer-name/details.rst b/doc/data/messages/r/redefined-outer-name/details.rst
index ab8204529..475e6a344 100644
--- a/doc/data/messages/r/redefined-outer-name/details.rst
+++ b/doc/data/messages/r/redefined-outer-name/details.rst
@@ -1 +1,23 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
+A common issue is that this message is triggered when using `pytest` `fixtures <https://docs.pytest.org/en/7.1.x/how-to/fixtures.html>`_:
+
+.. code-block:: python
+
+ import pytest
+
+ @pytest.fixture
+ def setup():
+ ...
+
+
+ def test_something(setup): # [redefined-outer-name]
+ ...
+
+One solution to this problem is to explicitly name the fixture:
+
+.. code-block:: python
+
+ @pytest.fixture(name="setup")
+ def setup_fixture():
+ ...
+
+Alternatively `pylint` plugins like `pylint-pytest <https://pypi.org/project/pylint-pytest/>`_ can be used.
diff --git a/doc/data/messages/r/redefined-outer-name/good.py b/doc/data/messages/r/redefined-outer-name/good.py
index c40beb573..135059838 100644
--- a/doc/data/messages/r/redefined-outer-name/good.py
+++ b/doc/data/messages/r/redefined-outer-name/good.py
@@ -1 +1,6 @@
-# This is a placeholder for correct code for this message.
+count = 10
+
+
+def count_it(limit):
+ for i in range(limit):
+ print(i)
diff --git a/doc/data/messages/r/redundant-returns-doc/bad.py b/doc/data/messages/r/redundant-returns-doc/bad.py
new file mode 100644
index 000000000..5d018db4c
--- /dev/null
+++ b/doc/data/messages/r/redundant-returns-doc/bad.py
@@ -0,0 +1,9 @@
+def print_fruits(fruits): # [redundant-returns-doc]
+ """Print list of fruits
+
+ Returns
+ -------
+ str
+ """
+ print(fruits)
+ return None
diff --git a/doc/data/messages/r/redundant-returns-doc/details.rst b/doc/data/messages/r/redundant-returns-doc/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/r/redundant-returns-doc/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/r/redundant-returns-doc/good.py b/doc/data/messages/r/redundant-returns-doc/good.py
index c40beb573..7f3eeebbb 100644
--- a/doc/data/messages/r/redundant-returns-doc/good.py
+++ b/doc/data/messages/r/redundant-returns-doc/good.py
@@ -1 +1,9 @@
-# This is a placeholder for correct code for this message.
+def print_fruits(fruits):
+ """Print list of fruits
+
+ Returns
+ -------
+ str
+ """
+ print(fruits)
+ return ",".join(fruits)
diff --git a/doc/data/messages/r/redundant-returns-doc/pylintrc b/doc/data/messages/r/redundant-returns-doc/pylintrc
new file mode 100644
index 000000000..4547f9811
--- /dev/null
+++ b/doc/data/messages/r/redundant-returns-doc/pylintrc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins = pylint.extensions.docparams
diff --git a/doc/data/messages/r/redundant-u-string-prefix/bad.py b/doc/data/messages/r/redundant-u-string-prefix/bad.py
new file mode 100644
index 000000000..bae9738a5
--- /dev/null
+++ b/doc/data/messages/r/redundant-u-string-prefix/bad.py
@@ -0,0 +1,2 @@
+def print_fruit():
+ print(u"Apple") # [redundant-u-string-prefix]
diff --git a/doc/data/messages/r/redundant-u-string-prefix/details.rst b/doc/data/messages/r/redundant-u-string-prefix/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/r/redundant-u-string-prefix/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/r/redundant-u-string-prefix/good.py b/doc/data/messages/r/redundant-u-string-prefix/good.py
index c40beb573..64b2d500d 100644
--- a/doc/data/messages/r/redundant-u-string-prefix/good.py
+++ b/doc/data/messages/r/redundant-u-string-prefix/good.py
@@ -1 +1,2 @@
-# This is a placeholder for correct code for this message.
+def print_fruit():
+ print("Apple")
diff --git a/doc/data/messages/r/redundant-yields-doc/bad.py b/doc/data/messages/r/redundant-yields-doc/bad.py
new file mode 100644
index 000000000..c2d1e6875
--- /dev/null
+++ b/doc/data/messages/r/redundant-yields-doc/bad.py
@@ -0,0 +1,9 @@
+def give_fruits(fruits): # [redundant-yields-doc]
+ """Something about fruits
+
+ Yields
+ -------
+ list
+ fruits
+ """
+ return fruits
diff --git a/doc/data/messages/r/redundant-yields-doc/details.rst b/doc/data/messages/r/redundant-yields-doc/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/r/redundant-yields-doc/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/r/redundant-yields-doc/good.py b/doc/data/messages/r/redundant-yields-doc/good.py
index c40beb573..1055a0c60 100644
--- a/doc/data/messages/r/redundant-yields-doc/good.py
+++ b/doc/data/messages/r/redundant-yields-doc/good.py
@@ -1 +1,10 @@
-# This is a placeholder for correct code for this message.
+def give_fruits(fruits):
+ """Something about fruits
+
+ Yields
+ -------
+ str
+ fruit
+ """
+ for fruit in fruits:
+ yield fruit
diff --git a/doc/data/messages/r/redundant-yields-doc/pylintrc b/doc/data/messages/r/redundant-yields-doc/pylintrc
new file mode 100644
index 000000000..4547f9811
--- /dev/null
+++ b/doc/data/messages/r/redundant-yields-doc/pylintrc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins = pylint.extensions.docparams
diff --git a/doc/data/messages/s/self-cls-assignment/bad.py b/doc/data/messages/s/self-cls-assignment/bad.py
new file mode 100644
index 000000000..64541405f
--- /dev/null
+++ b/doc/data/messages/s/self-cls-assignment/bad.py
@@ -0,0 +1,9 @@
+class Fruit:
+ @classmethod
+ def list_fruits(cls):
+ cls = 'apple' # [self-cls-assignment]
+
+ def print_color(self, *colors):
+ self = "red" # [self-cls-assignment]
+ color = colors[1]
+ print(color)
diff --git a/doc/data/messages/s/self-cls-assignment/details.rst b/doc/data/messages/s/self-cls-assignment/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/s/self-cls-assignment/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/s/self-cls-assignment/good.py b/doc/data/messages/s/self-cls-assignment/good.py
index c40beb573..ae8b172fa 100644
--- a/doc/data/messages/s/self-cls-assignment/good.py
+++ b/doc/data/messages/s/self-cls-assignment/good.py
@@ -1 +1,9 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ @classmethod
+ def list_fruits(cls):
+ fruit = 'apple'
+ print(fruit)
+
+ def print_color(self, *colors):
+ color = colors[1]
+ print(color)
diff --git a/doc/data/messages/s/simplifiable-condition/bad.py b/doc/data/messages/s/simplifiable-condition/bad.py
new file mode 100644
index 000000000..e3ffe5de9
--- /dev/null
+++ b/doc/data/messages/s/simplifiable-condition/bad.py
@@ -0,0 +1,2 @@
+def has_apples(apples) -> bool:
+ return bool(apples or False) # [simplifiable-condition]
diff --git a/doc/data/messages/s/simplifiable-condition/details.rst b/doc/data/messages/s/simplifiable-condition/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/s/simplifiable-condition/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/s/simplifiable-condition/good.py b/doc/data/messages/s/simplifiable-condition/good.py
index c40beb573..400a2788c 100644
--- a/doc/data/messages/s/simplifiable-condition/good.py
+++ b/doc/data/messages/s/simplifiable-condition/good.py
@@ -1 +1,2 @@
-# This is a placeholder for correct code for this message.
+def has_apples(apples) -> bool:
+ return bool(apples)
diff --git a/doc/data/messages/s/simplify-boolean-expression/bad.py b/doc/data/messages/s/simplify-boolean-expression/bad.py
new file mode 100644
index 000000000..06806d350
--- /dev/null
+++ b/doc/data/messages/s/simplify-boolean-expression/bad.py
@@ -0,0 +1,2 @@
+def has_oranges(oranges, apples=None) -> bool:
+ return apples and False or oranges # [simplify-boolean-expression]
diff --git a/doc/data/messages/s/simplify-boolean-expression/details.rst b/doc/data/messages/s/simplify-boolean-expression/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/s/simplify-boolean-expression/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/s/simplify-boolean-expression/good.py b/doc/data/messages/s/simplify-boolean-expression/good.py
index c40beb573..ca1c8a26f 100644
--- a/doc/data/messages/s/simplify-boolean-expression/good.py
+++ b/doc/data/messages/s/simplify-boolean-expression/good.py
@@ -1 +1,2 @@
-# This is a placeholder for correct code for this message.
+def has_oranges(oranges, apples=None) -> bool:
+ return oranges
diff --git a/doc/data/messages/s/singledispatch-method/bad.py b/doc/data/messages/s/singledispatch-method/bad.py
new file mode 100644
index 000000000..49e545b92
--- /dev/null
+++ b/doc/data/messages/s/singledispatch-method/bad.py
@@ -0,0 +1,19 @@
+from functools import singledispatch
+
+
+class Board:
+ @singledispatch # [singledispatch-method]
+ @classmethod
+ def convert_position(cls, position):
+ pass
+
+ @convert_position.register # [singledispatch-method]
+ @classmethod
+ def _(cls, position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register # [singledispatch-method]
+ @classmethod
+ def _(cls, position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/doc/data/messages/s/singledispatch-method/details.rst b/doc/data/messages/s/singledispatch-method/details.rst
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/data/messages/s/singledispatch-method/details.rst
diff --git a/doc/data/messages/s/singledispatch-method/good.py b/doc/data/messages/s/singledispatch-method/good.py
new file mode 100644
index 000000000..f38047cd1
--- /dev/null
+++ b/doc/data/messages/s/singledispatch-method/good.py
@@ -0,0 +1,19 @@
+from functools import singledispatch
+
+
+class Board:
+ @singledispatch
+ @staticmethod
+ def convert_position(position):
+ pass
+
+ @convert_position.register
+ @staticmethod
+ def _(position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register
+ @staticmethod
+ def _(position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/doc/data/messages/s/singledispatchmethod-function/bad.py b/doc/data/messages/s/singledispatchmethod-function/bad.py
new file mode 100644
index 000000000..d2255f865
--- /dev/null
+++ b/doc/data/messages/s/singledispatchmethod-function/bad.py
@@ -0,0 +1,19 @@
+from functools import singledispatchmethod
+
+
+class Board:
+ @singledispatchmethod # [singledispatchmethod-function]
+ @staticmethod
+ def convert_position(position):
+ pass
+
+ @convert_position.register # [singledispatchmethod-function]
+ @staticmethod
+ def _(position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register # [singledispatchmethod-function]
+ @staticmethod
+ def _(position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/doc/data/messages/s/singledispatchmethod-function/details.rst b/doc/data/messages/s/singledispatchmethod-function/details.rst
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/doc/data/messages/s/singledispatchmethod-function/details.rst
diff --git a/doc/data/messages/s/singledispatchmethod-function/good.py b/doc/data/messages/s/singledispatchmethod-function/good.py
new file mode 100644
index 000000000..1bc3570b5
--- /dev/null
+++ b/doc/data/messages/s/singledispatchmethod-function/good.py
@@ -0,0 +1,18 @@
+from functools import singledispatchmethod
+
+
+class Board:
+ @singledispatchmethod
+ def convert_position(cls, position):
+ pass
+
+ @singledispatchmethod
+ @classmethod
+ def _(cls, position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @singledispatchmethod
+ @classmethod
+ def _(cls, position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/doc/data/messages/s/syntax-error/bad.py b/doc/data/messages/s/syntax-error/bad.py
new file mode 100644
index 000000000..6a34478e1
--- /dev/null
+++ b/doc/data/messages/s/syntax-error/bad.py
@@ -0,0 +1,5 @@
+fruit_stock = {
+ 'apple': 42,
+ 'orange': 21 # [syntax-error]
+ 'banana': 12
+}
diff --git a/doc/data/messages/s/syntax-error/good.py b/doc/data/messages/s/syntax-error/good.py
new file mode 100644
index 000000000..eccab8746
--- /dev/null
+++ b/doc/data/messages/s/syntax-error/good.py
@@ -0,0 +1,5 @@
+fruit_stock = {
+ 'apple': 42,
+ 'orange': 21,
+ 'banana': 12
+}
diff --git a/doc/data/messages/t/too-many-function-args/bad.py b/doc/data/messages/t/too-many-function-args/bad.py
new file mode 100644
index 000000000..97eedb944
--- /dev/null
+++ b/doc/data/messages/t/too-many-function-args/bad.py
@@ -0,0 +1,6 @@
+class Fruit:
+ def __init__(self, color):
+ self.color = color
+
+
+apple = Fruit("red", "apple", [1, 2, 3]) # [too-many-function-args]
diff --git a/doc/data/messages/t/too-many-function-args/details.rst b/doc/data/messages/t/too-many-function-args/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/t/too-many-function-args/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/t/too-many-function-args/good.py b/doc/data/messages/t/too-many-function-args/good.py
index c40beb573..338b8e1e8 100644
--- a/doc/data/messages/t/too-many-function-args/good.py
+++ b/doc/data/messages/t/too-many-function-args/good.py
@@ -1 +1,7 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ def __init__(self, color, name):
+ self.color = color
+ self.name = name
+
+
+apple = Fruit("red", "apple")
diff --git a/doc/data/messages/t/too-many-try-statements/bad.py b/doc/data/messages/t/too-many-try-statements/bad.py
new file mode 100644
index 000000000..4e816ad39
--- /dev/null
+++ b/doc/data/messages/t/too-many-try-statements/bad.py
@@ -0,0 +1,10 @@
+FRUITS = {"apple": 1, "orange": 10}
+
+
+def pick_fruit(name):
+ try: # [too-many-try-statements]
+ count = FRUITS[name]
+ count += 1
+ print(f"Got fruit count {count}")
+ except KeyError:
+ return
diff --git a/doc/data/messages/t/too-many-try-statements/details.rst b/doc/data/messages/t/too-many-try-statements/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/t/too-many-try-statements/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/t/too-many-try-statements/good.py b/doc/data/messages/t/too-many-try-statements/good.py
index c40beb573..faea966a1 100644
--- a/doc/data/messages/t/too-many-try-statements/good.py
+++ b/doc/data/messages/t/too-many-try-statements/good.py
@@ -1 +1,11 @@
-# This is a placeholder for correct code for this message.
+FRUITS = {"apple": 1, "orange": 10}
+
+
+def pick_fruit(name):
+ try:
+ count = FRUITS[name]
+ except KeyError:
+ return
+
+ count += 1
+ print(f"Got fruit count {count}")
diff --git a/doc/data/messages/t/too-many-try-statements/pylintrc b/doc/data/messages/t/too-many-try-statements/pylintrc
new file mode 100644
index 000000000..438a80b6d
--- /dev/null
+++ b/doc/data/messages/t/too-many-try-statements/pylintrc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.broad_try_clause,
diff --git a/doc/data/messages/u/unbalanced-dict-unpacking/bad.py b/doc/data/messages/u/unbalanced-dict-unpacking/bad.py
new file mode 100644
index 000000000..9162ccc45
--- /dev/null
+++ b/doc/data/messages/u/unbalanced-dict-unpacking/bad.py
@@ -0,0 +1,4 @@
+FRUITS = {"apple": 2, "orange": 3, "mellon": 10}
+
+for fruit, price in FRUITS.values(): # [unbalanced-dict-unpacking]
+ print(fruit)
diff --git a/doc/data/messages/u/unbalanced-dict-unpacking/good.py b/doc/data/messages/u/unbalanced-dict-unpacking/good.py
new file mode 100644
index 000000000..450e03489
--- /dev/null
+++ b/doc/data/messages/u/unbalanced-dict-unpacking/good.py
@@ -0,0 +1,4 @@
+FRUITS = {"apple": 2, "orange": 3, "mellon": 10}
+
+for fruit, price in FRUITS.items():
+ print(fruit)
diff --git a/doc/data/messages/u/unnecessary-dict-index-lookup/bad.py b/doc/data/messages/u/unnecessary-dict-index-lookup/bad.py
new file mode 100644
index 000000000..047175861
--- /dev/null
+++ b/doc/data/messages/u/unnecessary-dict-index-lookup/bad.py
@@ -0,0 +1,4 @@
+FRUITS = {"apple": 1, "orange": 10, "berry": 22}
+
+for fruit_name, fruit_count in FRUITS.items():
+ print(FRUITS[fruit_name]) # [unnecessary-dict-index-lookup]
diff --git a/doc/data/messages/u/unnecessary-dict-index-lookup/details.rst b/doc/data/messages/u/unnecessary-dict-index-lookup/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unnecessary-dict-index-lookup/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unnecessary-dict-index-lookup/good.py b/doc/data/messages/u/unnecessary-dict-index-lookup/good.py
index c40beb573..c2ee5ed57 100644
--- a/doc/data/messages/u/unnecessary-dict-index-lookup/good.py
+++ b/doc/data/messages/u/unnecessary-dict-index-lookup/good.py
@@ -1 +1,4 @@
-# This is a placeholder for correct code for this message.
+FRUITS = {"apple": 1, "orange": 10, "berry": 22}
+
+for fruit_name, fruit_count in FRUITS.items():
+ print(fruit_count)
diff --git a/doc/data/messages/u/unrecognized-inline-option/bad.py b/doc/data/messages/u/unrecognized-inline-option/bad.py
new file mode 100644
index 000000000..ff49aa920
--- /dev/null
+++ b/doc/data/messages/u/unrecognized-inline-option/bad.py
@@ -0,0 +1,2 @@
+# +1: [unrecognized-inline-option]
+# pylint:applesoranges=1
diff --git a/doc/data/messages/u/unrecognized-inline-option/details.rst b/doc/data/messages/u/unrecognized-inline-option/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unrecognized-inline-option/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unrecognized-inline-option/good.py b/doc/data/messages/u/unrecognized-inline-option/good.py
index c40beb573..2fdb3780a 100644
--- a/doc/data/messages/u/unrecognized-inline-option/good.py
+++ b/doc/data/messages/u/unrecognized-inline-option/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+# pylint: enable=too-many-public-methods
diff --git a/doc/data/messages/u/unsubscriptable-object/bad.py b/doc/data/messages/u/unsubscriptable-object/bad.py
new file mode 100644
index 000000000..8b168a0af
--- /dev/null
+++ b/doc/data/messages/u/unsubscriptable-object/bad.py
@@ -0,0 +1,5 @@
+class Fruit:
+ pass
+
+
+Fruit()[1] # [unsubscriptable-object]
diff --git a/doc/data/messages/u/unsubscriptable-object/details.rst b/doc/data/messages/u/unsubscriptable-object/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unsubscriptable-object/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unsubscriptable-object/good.py b/doc/data/messages/u/unsubscriptable-object/good.py
index c40beb573..56e91444e 100644
--- a/doc/data/messages/u/unsubscriptable-object/good.py
+++ b/doc/data/messages/u/unsubscriptable-object/good.py
@@ -1 +1,9 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ def __init__(self):
+ self.colors = ["red", "orange", "yellow"]
+
+ def __getitem__(self, idx):
+ return self.colors[idx]
+
+
+Fruit()[1]
diff --git a/doc/data/messages/u/unsupported-assignment-operation/bad.py b/doc/data/messages/u/unsupported-assignment-operation/bad.py
new file mode 100644
index 000000000..26ee1a993
--- /dev/null
+++ b/doc/data/messages/u/unsupported-assignment-operation/bad.py
@@ -0,0 +1,6 @@
+def pick_fruits(fruits):
+ for fruit in fruits:
+ print(fruit)
+
+
+pick_fruits(["apple"])[0] = "orange" # [unsupported-assignment-operation]
diff --git a/doc/data/messages/u/unsupported-assignment-operation/details.rst b/doc/data/messages/u/unsupported-assignment-operation/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unsupported-assignment-operation/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unsupported-assignment-operation/good.py b/doc/data/messages/u/unsupported-assignment-operation/good.py
index c40beb573..13fe34c05 100644
--- a/doc/data/messages/u/unsupported-assignment-operation/good.py
+++ b/doc/data/messages/u/unsupported-assignment-operation/good.py
@@ -1 +1,8 @@
-# This is a placeholder for correct code for this message.
+def pick_fruits(fruits):
+ for fruit in fruits:
+ print(fruit)
+
+ return []
+
+
+pick_fruits(["apple"])[0] = "orange"
diff --git a/doc/data/messages/u/unsupported-delete-operation/bad.py b/doc/data/messages/u/unsupported-delete-operation/bad.py
new file mode 100644
index 000000000..a7870e3a8
--- /dev/null
+++ b/doc/data/messages/u/unsupported-delete-operation/bad.py
@@ -0,0 +1,3 @@
+FRUITS = ("apple", "orange", "berry")
+
+del FRUITS[0] # [unsupported-delete-operation]
diff --git a/doc/data/messages/u/unsupported-delete-operation/details.rst b/doc/data/messages/u/unsupported-delete-operation/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unsupported-delete-operation/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unsupported-delete-operation/good.py b/doc/data/messages/u/unsupported-delete-operation/good.py
index c40beb573..8143c4fee 100644
--- a/doc/data/messages/u/unsupported-delete-operation/good.py
+++ b/doc/data/messages/u/unsupported-delete-operation/good.py
@@ -1 +1,3 @@
-# This is a placeholder for correct code for this message.
+FRUITS = ["apple", "orange", "berry"]
+
+del FRUITS[0]
diff --git a/doc/data/messages/u/unsupported-membership-test/bad.py b/doc/data/messages/u/unsupported-membership-test/bad.py
new file mode 100644
index 000000000..37502ecd3
--- /dev/null
+++ b/doc/data/messages/u/unsupported-membership-test/bad.py
@@ -0,0 +1,5 @@
+class Fruit:
+ pass
+
+
+apple = "apple" in Fruit() # [unsupported-membership-test]
diff --git a/doc/data/messages/u/unsupported-membership-test/details.rst b/doc/data/messages/u/unsupported-membership-test/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unsupported-membership-test/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unsupported-membership-test/good.py b/doc/data/messages/u/unsupported-membership-test/good.py
index c40beb573..96b96d4d5 100644
--- a/doc/data/messages/u/unsupported-membership-test/good.py
+++ b/doc/data/messages/u/unsupported-membership-test/good.py
@@ -1 +1,7 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ FRUITS = ["apple", "orange"]
+ def __contains__(self, name):
+ return name in self.FRUITS
+
+
+apple = "apple" in Fruit()
diff --git a/doc/data/messages/u/unused-private-member/bad.py b/doc/data/messages/u/unused-private-member/bad.py
new file mode 100644
index 000000000..b56bcaad3
--- /dev/null
+++ b/doc/data/messages/u/unused-private-member/bad.py
@@ -0,0 +1,5 @@
+class Fruit:
+ FRUITS = {"apple": "red", "orange": "orange"}
+
+ def __print_color(self): # [unused-private-member]
+ pass
diff --git a/doc/data/messages/u/unused-private-member/details.rst b/doc/data/messages/u/unused-private-member/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/unused-private-member/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/unused-private-member/good.py b/doc/data/messages/u/unused-private-member/good.py
index c40beb573..02df36c44 100644
--- a/doc/data/messages/u/unused-private-member/good.py
+++ b/doc/data/messages/u/unused-private-member/good.py
@@ -1 +1,9 @@
-# This is a placeholder for correct code for this message.
+class Fruit:
+ FRUITS = {"apple": "red", "orange": "orange"}
+
+ def __print_color(self, name, color):
+ print(f"{name}: {color}")
+
+ def print(self):
+ for fruit, color in self.FRUITS.items():
+ self.__print_color(fruit, color)
diff --git a/doc/data/messages/u/use-dict-literal/bad.py b/doc/data/messages/u/use-dict-literal/bad.py
index 6c3056b6f..2d90a91e8 100644
--- a/doc/data/messages/u/use-dict-literal/bad.py
+++ b/doc/data/messages/u/use-dict-literal/bad.py
@@ -1 +1,3 @@
empty_dict = dict() # [use-dict-literal]
+new_dict = dict(foo="bar") # [use-dict-literal]
+new_dict = dict(**another_dict) # [use-dict-literal]
diff --git a/doc/data/messages/u/use-dict-literal/details.rst b/doc/data/messages/u/use-dict-literal/details.rst
new file mode 100644
index 000000000..f07532ead
--- /dev/null
+++ b/doc/data/messages/u/use-dict-literal/details.rst
@@ -0,0 +1,4 @@
+https://gist.github.com/hofrob/ad143aaa84c096f42489c2520a3875f9
+
+This example script shows an 18% increase in performance when using a literal over the
+constructor in python version 3.10.6.
diff --git a/doc/data/messages/u/use-dict-literal/good.py b/doc/data/messages/u/use-dict-literal/good.py
index 5f7d64deb..237d2c881 100644
--- a/doc/data/messages/u/use-dict-literal/good.py
+++ b/doc/data/messages/u/use-dict-literal/good.py
@@ -1 +1,7 @@
empty_dict = {}
+
+# create using a literal dict
+new_dict = {"foo": "bar"}
+
+# shallow copy a dict
+new_dict = {**another_dict}
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison/bad.py b/doc/data/messages/u/use-implicit-booleaness-not-comparison/bad.py
new file mode 100644
index 000000000..78411ec2a
--- /dev/null
+++ b/doc/data/messages/u/use-implicit-booleaness-not-comparison/bad.py
@@ -0,0 +1,4 @@
+z = []
+
+if z != []: # [use-implicit-booleaness-not-comparison]
+ print("z is not an empty sequence")
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison/details.rst b/doc/data/messages/u/use-implicit-booleaness-not-comparison/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/u/use-implicit-booleaness-not-comparison/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison/good.py b/doc/data/messages/u/use-implicit-booleaness-not-comparison/good.py
index c40beb573..6801d91eb 100644
--- a/doc/data/messages/u/use-implicit-booleaness-not-comparison/good.py
+++ b/doc/data/messages/u/use-implicit-booleaness-not-comparison/good.py
@@ -1 +1,4 @@
-# This is a placeholder for correct code for this message.
+z = []
+
+if z:
+ print("z is not an empty sequence")
diff --git a/doc/data/messages/w/wrong-spelling-in-comment/bad.py b/doc/data/messages/w/wrong-spelling-in-comment/bad.py
new file mode 100644
index 000000000..becaf40e5
--- /dev/null
+++ b/doc/data/messages/w/wrong-spelling-in-comment/bad.py
@@ -0,0 +1 @@
+# There's a mistkae in this string # [wrong-spelling-in-comment]
diff --git a/doc/data/messages/w/wrong-spelling-in-comment/details.rst b/doc/data/messages/w/wrong-spelling-in-comment/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/w/wrong-spelling-in-comment/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/w/wrong-spelling-in-comment/good.py b/doc/data/messages/w/wrong-spelling-in-comment/good.py
index c40beb573..84af67f5a 100644
--- a/doc/data/messages/w/wrong-spelling-in-comment/good.py
+++ b/doc/data/messages/w/wrong-spelling-in-comment/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+# There's no mistake in this string
diff --git a/doc/data/messages/w/wrong-spelling-in-comment/pylintrc b/doc/data/messages/w/wrong-spelling-in-comment/pylintrc
new file mode 100644
index 000000000..dd11bf811
--- /dev/null
+++ b/doc/data/messages/w/wrong-spelling-in-comment/pylintrc
@@ -0,0 +1,3 @@
+[main]
+# This might not run in your env if you don't have the en_US dict installed.
+spelling-dict=en_US
diff --git a/doc/data/messages/w/wrong-spelling-in-docstring/bad.py b/doc/data/messages/w/wrong-spelling-in-docstring/bad.py
new file mode 100644
index 000000000..b5c9149ae
--- /dev/null
+++ b/doc/data/messages/w/wrong-spelling-in-docstring/bad.py
@@ -0,0 +1 @@
+"""There's a mistkae in this string""" # [wrong-spelling-in-docstring]
diff --git a/doc/data/messages/w/wrong-spelling-in-docstring/details.rst b/doc/data/messages/w/wrong-spelling-in-docstring/details.rst
deleted file mode 100644
index ab8204529..000000000
--- a/doc/data/messages/w/wrong-spelling-in-docstring/details.rst
+++ /dev/null
@@ -1 +0,0 @@
-You can help us make the doc better `by contributing <https://github.com/PyCQA/pylint/issues/5953>`_ !
diff --git a/doc/data/messages/w/wrong-spelling-in-docstring/good.py b/doc/data/messages/w/wrong-spelling-in-docstring/good.py
index c40beb573..10d19e91e 100644
--- a/doc/data/messages/w/wrong-spelling-in-docstring/good.py
+++ b/doc/data/messages/w/wrong-spelling-in-docstring/good.py
@@ -1 +1 @@
-# This is a placeholder for correct code for this message.
+"""There's no mistake in this string"""
diff --git a/doc/data/messages/w/wrong-spelling-in-docstring/pylintrc b/doc/data/messages/w/wrong-spelling-in-docstring/pylintrc
new file mode 100644
index 000000000..dd11bf811
--- /dev/null
+++ b/doc/data/messages/w/wrong-spelling-in-docstring/pylintrc
@@ -0,0 +1,3 @@
+[main]
+# This might not run in your env if you don't have the en_US dict installed.
+spelling-dict=en_US
diff --git a/doc/development_guide/api/index.rst b/doc/development_guide/api/index.rst
index 373c49866..00e6e1a9f 100644
--- a/doc/development_guide/api/index.rst
+++ b/doc/development_guide/api/index.rst
@@ -7,10 +7,9 @@ Python program thanks to their APIs:
.. sourcecode:: python
- from pylint import run_pylint, run_epylint, run_pyreverse, run_symilar
+ from pylint import run_pylint, run_pyreverse, run_symilar
run_pylint("--disable=C", "myfile.py")
- run_epylint(...)
run_pyreverse(...)
run_symilar(...)
diff --git a/doc/development_guide/contributor_guide/contribute.rst b/doc/development_guide/contributor_guide/contribute.rst
index 32de20106..492f8966b 100644
--- a/doc/development_guide/contributor_guide/contribute.rst
+++ b/doc/development_guide/contributor_guide/contribute.rst
@@ -67,9 +67,9 @@ your patch gets accepted:
.. keep this in sync with the description of PULL_REQUEST_TEMPLATE.md!
- Create a news fragment with `towncrier create <IssueNumber>.<type>` which will be
- included in the changelog. `<type>` can be one of: new_check, removed_check, extension,
- false_positive, false_negative, bugfix, other, internal. If necessary you can write
- details or offer examples on how the new change is supposed to work.
+ included in the changelog. `<type>` can be one of: breaking, user_action, feature,
+ new_check, removed_check, extension, false_positive, false_negative, bugfix, other, internal.
+ If necessary you can write details or offer examples on how the new change is supposed to work.
- Document your change, if it is a non-trivial one.
diff --git a/doc/development_guide/contributor_guide/release.md b/doc/development_guide/contributor_guide/release.md
index ee5fa2b7a..a076838fd 100644
--- a/doc/development_guide/contributor_guide/release.md
+++ b/doc/development_guide/contributor_guide/release.md
@@ -49,17 +49,20 @@ branch
## Back-porting 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
- `release-branch-X.Y-1.Z'` branch created from `maintenance/X.Y-1.x` then cherry-pick
- the commit from the `main` branch. (For example: `release-branch-2.3.5` from
- `maintenance/2.3.x`)
-- Remove the "need backport" label from cherry-picked issues
-
+Whenever a PR on `main` should be released in a patch release on the current maintenance
+branch:
+
+- Label the PR with `backport maintenance/X.Y-1.x`. (For example
+ `backport maintenance/2.3.x`)
+- Squash the PR before merging (alternatively rebase if there's a single commit)
+- (If the automated cherry-pick has conflicts)
+ - Add a `Needs backport` label and do it manually.
+ - You might alternatively also:
+ - Cherry-pick the changes that create the conflict if it's not a new feature before
+ doing the original PR cherry-pick manually.
+ - Decide to wait for the next minor to release the PR
+ - In any case upgrade the milestones in the original PR and newly cherry-picked PR
+ to match reality.
- Release a patch version
## Releasing a patch version
diff --git a/doc/development_guide/contributor_guide/tests/launching_test.rst b/doc/development_guide/contributor_guide/tests/launching_test.rst
index b982bbbeb..02114f01f 100644
--- a/doc/development_guide/contributor_guide/tests/launching_test.rst
+++ b/doc/development_guide/contributor_guide/tests/launching_test.rst
@@ -57,16 +57,25 @@ Primer tests
Pylint also uses what we refer to as ``primer`` tests. These are tests that are run automatically
in our Continuous Integration and check whether any changes in Pylint lead to crashes or fatal errors
-on the ``stdlib`` and a selection of external repositories.
+on the ``stdlib``, and also assess a pull request's impact on the linting of a selection of external
+repositories by posting the diff against ``pylint``'s current output as a comment.
-To run the ``primer`` tests you can add either ``--primer-stdlib`` or ``--primer-external`` to the
-pytest_ command. If you want to only run the ``primer`` you can add either of their marks, for example::
+To run the primer test for the ``stdlib``, which only checks for crashes and fatal errors, you can add
+``--primer-stdlib`` to the pytest_ command. For example::
pytest -m primer_stdlib --primer-stdlib
-The external ``primer`` can be run with::
+To produce the output generated on Continuous Integration for the linting of external repositories,
+run these commands::
- pytest -m primer_external_batch_one --primer-external # Runs batch one
+ python tests/primer/__main__.py prepare --clone
+ python tests/primer/__main__.py run --type=pr
+
+To fully simulate the process on Continuous Integration, you should then checkout ``main``, and
+then run these commands::
+
+ python tests/primer/__main__.py run --type=main
+ python tests/primer/__main__.py compare
The list of repositories is created on the basis of three criteria: 1) projects need to use a diverse
range of language features, 2) projects need to be well maintained and 3) projects should not have a codebase
diff --git a/doc/development_guide/how_tos/custom_checkers.rst b/doc/development_guide/how_tos/custom_checkers.rst
index 7a9c567db..6d36b0ec6 100644
--- a/doc/development_guide/how_tos/custom_checkers.rst
+++ b/doc/development_guide/how_tos/custom_checkers.rst
@@ -218,10 +218,28 @@ Now we can debug our checker!
.. Note::
``my_plugin`` refers to a module called ``my_plugin.py``.
- This module can be made available to pylint by putting this
- module's parent directory in your ``PYTHONPATH``
- environment variable or by adding the ``my_plugin.py``
- file to the ``pylint/checkers`` directory if running from source.
+ The preferred way of making this plugin available to pylint is
+ by installing it as a package. This can be done either from a packaging index like
+ ``PyPI`` or by installing it from a local source such as with ``pip install``.
+
+ Alternatively, the plugin module can be made available to pylint by
+ putting this module's parent directory in your ``PYTHONPATH``
+ environment variable.
+
+ If your pylint config has an ``init-hook`` that modifies
+ ``sys.path`` to include the module's parent directory, this
+ will also work, but only if either:
+
+ * the ``init-hook`` and the ``load-plugins`` list are both
+ defined in a configuration file, or...
+ * the ``init-hook`` is passed as a command-line argument and
+ the ``load-plugins`` list is in the configuration file
+
+ So, you cannot load a custom plugin by modifying ``sys.path`` if you
+ supply the ``init-hook`` in a configuration file, but pass the module name
+ in via ``--load-plugins`` on the command line.
+ This is because pylint loads plugins specified on command
+ line before loading any configuration from other sources.
Defining a Message
------------------
diff --git a/doc/development_guide/how_tos/plugins.rst b/doc/development_guide/how_tos/plugins.rst
index bc2c0f14c..3940f2481 100644
--- a/doc/development_guide/how_tos/plugins.rst
+++ b/doc/development_guide/how_tos/plugins.rst
@@ -1,5 +1,7 @@
.. -*- coding: utf-8 -*-
+.. _plugins:
+
How To Write a Pylint Plugin
============================
diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py
index a973e1b13..406d2d39d 100755
--- a/doc/exts/pylint_extensions.py
+++ b/doc/exts/pylint_extensions.py
@@ -6,22 +6,42 @@
"""Script used to generate the extensions file before building the actual documentation."""
+from __future__ import annotations
+
import os
import re
import sys
import warnings
-from typing import Optional
+from typing import Any
import sphinx
from sphinx.application import Sphinx
+from pylint.checkers import BaseChecker
from pylint.constants import MAIN_CHECKER_NAME
from pylint.lint import PyLinter
+from pylint.typing import MessageDefinitionTuple, OptionDict, ReportsCallable
from pylint.utils import get_rst_title
+if sys.version_info >= (3, 8):
+ from typing import TypedDict
+else:
+ from typing_extensions import TypedDict
+
+
+class _CheckerInfo(TypedDict):
+ """Represents data about a checker."""
+
+ checker: BaseChecker
+ options: list[tuple[str, OptionDict, Any]]
+ msgs: dict[str, MessageDefinitionTuple]
+ reports: list[tuple[str, str, ReportsCallable]]
+ doc: str
+ module: str
+
# pylint: disable-next=unused-argument
-def builder_inited(app: Optional[Sphinx]) -> None:
+def builder_inited(app: Sphinx | None) -> None:
"""Output full documentation in ReST format for all extension modules."""
# PACKAGE/docs/exts/pylint_extensions.py --> PACKAGE/
base_path = os.path.dirname(
@@ -30,7 +50,7 @@ def builder_inited(app: Optional[Sphinx]) -> None:
# PACKAGE/ --> PACKAGE/pylint/extensions
ext_path = os.path.join(base_path, "pylint", "extensions")
modules = []
- doc_files = {}
+ doc_files: dict[str, str] = {}
for filename in os.listdir(ext_path):
name, ext = os.path.splitext(filename)
if name[0] == "_":
@@ -79,18 +99,26 @@ def builder_inited(app: Optional[Sphinx]) -> None:
checker, information = checker_information
j = -1
checker = information["checker"]
- del information["checker"]
if i == max_len - 1:
# Remove the \n\n at the end of the file
j = -3
print(
- checker.get_full_documentation(**information, show_options=False)[:j],
+ checker.get_full_documentation(
+ msgs=information["msgs"],
+ options=information["options"],
+ reports=information["reports"],
+ doc=information["doc"],
+ module=information["module"],
+ show_options=False,
+ )[:j],
file=stream,
)
-def get_plugins_info(linter, doc_files):
- by_checker = {}
+def get_plugins_info(
+ linter: PyLinter, doc_files: dict[str, str]
+) -> dict[BaseChecker, _CheckerInfo]:
+ by_checker: dict[BaseChecker, _CheckerInfo] = {}
for checker in linter.get_checkers():
if checker.name == MAIN_CHECKER_NAME:
continue
@@ -116,18 +144,18 @@ def get_plugins_info(linter, doc_files):
except KeyError:
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
- by_checker[checker] = {
- "checker": checker,
- "options": list(checker.options_and_values()),
- "msgs": dict(checker.msgs),
- "reports": list(checker.reports),
- "doc": doc,
- "module": module,
- }
+ by_checker[checker] = _CheckerInfo(
+ checker=checker,
+ options=list(checker.options_and_values()),
+ msgs=dict(checker.msgs),
+ reports=list(checker.reports),
+ doc=doc,
+ module=module,
+ )
return by_checker
-def setup(app):
+def setup(app: Sphinx) -> dict[str, str]:
app.connect("builder-inited", builder_inited)
return {"version": sphinx.__display_version__}
diff --git a/doc/exts/pylint_features.py b/doc/exts/pylint_features.py
index 8654046d3..fcf4f01c8 100755
--- a/doc/exts/pylint_features.py
+++ b/doc/exts/pylint_features.py
@@ -8,8 +8,9 @@
documentation.
"""
+from __future__ import annotations
+
import os
-from typing import Optional
import sphinx
from sphinx.application import Sphinx
@@ -19,7 +20,7 @@ from pylint.utils import get_rst_title, print_full_documentation
# pylint: disable-next=unused-argument
-def builder_inited(app: Optional[Sphinx]) -> None:
+def builder_inited(app: Sphinx | None) -> None:
# PACKAGE/docs/exts/pylint_extensions.py --> PACKAGE/
base_path = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -39,7 +40,7 @@ def builder_inited(app: Optional[Sphinx]) -> None:
print_full_documentation(linter, stream, False)
-def setup(app):
+def setup(app: Sphinx) -> dict[str, str]:
app.connect("builder-inited", builder_inited)
return {"version": sphinx.__display_version__}
diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py
index 0fcacf804..cef7c83a4 100644
--- a/doc/exts/pylint_messages.py
+++ b/doc/exts/pylint_messages.py
@@ -44,6 +44,7 @@ class MessageData(NamedTuple):
checker_module_name: str
checker_module_path: str
shared: bool = False
+ default_enabled: bool = True
MessagesDict = Dict[str, List[MessageData]]
@@ -194,6 +195,7 @@ def _get_all_messages(
checker_module.__name__,
checker_module.__file__,
message.shared,
+ message.default_enabled,
)
msg_type = MSG_TYPES_DOC[message.msgid[0]]
messages_dict[msg_type].append(message_data)
@@ -271,7 +273,15 @@ def _generate_single_message_body(message: MessageData) -> str:
**Description:**
*{message.definition.description}*
+"""
+ if not message.default_enabled:
+ body += f"""
+.. caution::
+ This message is disabled by default. To enable it, add ``{message.name}`` to the ``enable`` option.
+
+"""
+ body += f"""
{message.bad_code}
{message.good_code}
{message.details}
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 512d311aa..e9c849eb7 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,6 +1,6 @@
-Sphinx==5.1.1
+Sphinx==5.3.0
sphinx-reredirects<1
myst-parser~=0.18
-towncrier~=21.9
-furo==2022.6.21
+towncrier~=22.8
+furo==2022.9.29
-e .
diff --git a/doc/tutorial.rst b/doc/tutorial.rst
index 173b255cd..fce472234 100644
--- a/doc/tutorial.rst
+++ b/doc/tutorial.rst
@@ -4,55 +4,27 @@
Tutorial
========
-:Author: Robert Kirkpatrick
-
-
-Intro
------
-
-Beginner to coding standards? Pylint can be your guide to reveal what's really
-going on behind the scenes and help you to become a more aware programmer.
-
-Sharing code is a rewarding endeavor. Putting your code ``out there`` can be
-either an act of philanthropy, ``coming of age``, or a basic extension of belief
-in open source. Whatever the motivation, your good intentions may not have the
-desired outcome if people find your code hard to use or understand. The Python
-community has formalized some recommended programming styles to help everyone
-write code in a common, agreed-upon style that makes the most sense for shared
-code. This style is captured in `PEP 8`_, the "Style Guide for Python Code".
-Pylint can be a quick and easy way of
-seeing if your code has captured the essence of `PEP 8`_ and is therefore
-``friendly`` to other potential users.
-
-Perhaps you're not ready to share your code but you'd like to learn a bit more
-about writing better code and don't know where to start. Pylint can tell you
-where you may have run astray and point you in the direction to figure out what
-you have done and how to do better.
-
This tutorial is all about approaching coding standards with little or no
knowledge of in-depth programming or the code standards themselves. It's the
equivalent of skipping the manual and jumping right in.
-My command line prompt for these examples is:
+The command line prompt for these examples is:
.. sourcecode:: console
- robertk01 Desktop$
+ tutor Desktop$
.. _PEP 8: https://peps.python.org/pep-0008/
Getting Started
---------------
-Running Pylint with no arguments will invoke the help dialogue and give you an
-idea of the arguments available to you. Do that now, i.e.:
+Running Pylint with the ``--help`` arguments will give you an idea of the arguments
+available. Do that now, i.e.:
.. sourcecode:: console
- robertk01 Desktop$ pylint
- ...
- a bunch of stuff
- ...
+ pylint --help
A couple of the options that we'll focus on here are: ::
@@ -66,17 +38,12 @@ A couple of the options that we'll focus on here are: ::
--reports=<y or n>
--output-format=<format>
-If you need more detail, you can also ask for an even longer help message,
-like so: ::
+If you need more detail, you can also ask for an even longer help message: ::
- robertk01 Desktop$ pylint --long-help
- ...
- Even more stuff
- ...
+ pylint --long-help
-Pay attention to the last bit of this longer help output. This gives you a
-hint of what
-Pylint is going to ``pick on``: ::
+Pay attention to the last bit of this longer help output. This gives you a
+hint of what Pylint is going to ``pick on``: ::
Output:
Using the default text output, the message format is :
@@ -90,155 +57,148 @@ Pylint is going to ``pick on``: ::
further processing.
When Pylint is first run on a fresh piece of code, a common complaint is that it
-is too ``noisy``. The current default configuration is set to enforce all possible
-warnings. We'll use some of the options I noted above to make it suit your
-preferences a bit better (and thus make it emit messages only when needed).
-
+is too ``noisy``. The default configuration enforce a lot of warnings.
+We'll use some of the options we noted above to make it suit your
+preferences a bit better.
Your First Pylint'ing
---------------------
-We'll use a basic Python script as fodder for our tutorial.
-The starting code we will use is called simplecaesar.py and is here in its
-entirety:
+We'll use a basic Python script with ``black`` already applied on it,
+as fodder for our tutorial. The starting code we will use is called
+``simplecaesar.py`` and is here in its entirety:
.. sourcecode:: python
- #!/usr/bin/env python3
-
- import string;
+ #!/usr/bin/env python3
- shift = 3
- choice = input("would you like to encode or decode?")
- word = input("Please enter text")
- letters = string.ascii_letters + string.punctuation + string.digits
- encoded = ''
- if choice == "encode":
- for letter in word:
- if letter == ' ':
- encoded = encoded + ' '
- else:
- x = letters.index(letter) + shift
- encoded = encoded + letters[x]
- if choice == "decode":
- for letter in word:
- if letter == ' ':
- encoded = encoded + ' '
- else:
- x = letters.index(letter) - shift
- encoded = encoded + letters[x]
+ import string
- print(encoded)
+ shift = 3
+ choice = input("would you like to encode or decode?")
+ word = input("Please enter text")
+ letters = string.ascii_letters + string.punctuation + string.digits
+ encoded = ""
+ if choice == "encode":
+ for letter in word:
+ if letter == " ":
+ encoded = encoded + " "
+ else:
+ x = letters.index(letter) + shift
+ encoded = encoded + letters[x]
+ if choice == "decode":
+ for letter in word:
+ if letter == " ":
+ encoded = encoded + " "
+ else:
+ x = letters.index(letter) - shift
+ encoded = encoded + letters[x]
+ print(encoded)
-Let's get started.
-If we run this:
+Let's get started. If we run this:
.. sourcecode:: console
- robertk01 Desktop$ pylint simplecaesar.py
- ************* Module simplecaesar
- simplecaesar.py:3:0: W0301: Unnecessary semicolon (unnecessary-semicolon)
- simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)
- simplecaesar.py:5:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
- simplecaesar.py:9:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
- simplecaesar.py:13:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ tutor Desktop$ pylint simplecaesar.py
+ ************* Module simplecaesar
+ simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)
+ simplecaesar.py:5:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:8:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:9:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:13:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:15:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:16:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:20:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:22:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:23:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
- -----------------------------------
- Your code has been rated at 7.37/10
+ -----------------------------------
+ Your code has been rated at 4.74/10
-Previous experience taught me that the default output for the messages
-needed a bit more info. We can see the second line is: ::
+We can see the second line is: ::
"simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)"
-This basically means that line 1 violates a convention ``C0114``. It's telling me I really should have a docstring.
-I agree, but what if I didn't fully understand what rule I violated. Knowing only that I violated a convention
-isn't much help if I'm a newbie. Another piece of information there is the
-message symbol between parens, ``missing-module-docstring`` here.
+This basically means that line 1 at column 0 violates the convention ``C0114``.
+Another piece of information is the message symbol between parens,
+``missing-module-docstring``.
-If I want to read up a bit more about that, I can go back to the
+If we want to read up a bit more about that, we can go back to the
command line and try this:
.. sourcecode:: console
- robertk01 Desktop$ pylint --help-msg=missing-module-docstring
+ tutor Desktop$ pylint --help-msg=missing-module-docstring
:missing-module-docstring (C0114): *Missing module docstring*
Used when a module has no docstring.Empty modules do not require a docstring.
This message belongs to the basic checker.
-
-Yeah, ok. That one was a bit of a no-brainer, but I have run into error messages
-that left me with no clue about what went wrong, simply because I was unfamiliar
-with the underlying mechanism of code theory. One error that puzzled my newbie
-mind was: ::
-
- :too-many-instance-attributes (R0902): *Too many instance attributes (%s/%s)*
-
-I get it now thanks to Pylint pointing it out to me. If you don't get that one,
-pour a fresh cup of coffee and look into it - let your programmer mind grow!
-
+That one was a bit of a no-brainer, but we can also run into error messages
+where we are unfamiliar with the underlying code theory.
The Next Step
-------------
Now that we got some configuration stuff out of the way, let's see what we can
-do with the remaining warnings.
-
-If we add a docstring to describe what the code is meant to do that will help.
-There are 5 ``invalid-name`` messages that we will get to later. Lastly, I
-put an unnecessary semicolon at the end of the import line so I'll
-fix that too. To sum up, I'll add a docstring to line 2, and remove the ``;``
-from line 3.
-
-Here is the updated code:
+do with the remaining warnings. If we add a docstring to describe what the code
+is meant to do that will help. There are ``invalid-name`` messages that we will
+get to later. Here is the updated code:
.. sourcecode:: python
- #!/usr/bin/env python3
- """This script prompts a user to enter a message to encode or decode
- using a classic Caesar shift substitution (3 letter shift)"""
-
- import string
-
- shift = 3
- choice = input("would you like to encode or decode?")
- word = input("Please enter text")
- letters = string.ascii_letters + string.punctuation + string.digits
- encoded = ''
- if choice == "encode":
- for letter in word:
- if letter == ' ':
- encoded = encoded + ' '
- else:
- x = letters.index(letter) + shift
- encoded = encoded + letters[x]
- if choice == "decode":
- for letter in word:
- if letter == ' ':
- encoded = encoded + ' '
- else:
- x = letters.index(letter) - shift
- encoded = encoded + letters[x]
-
- print(encoded)
+ #!/usr/bin/env python3
+
+ """This script prompts a user to enter a message to encode or decode
+ using a classic Caesar shift substitution (3 letter shift)"""
+
+ import string
+
+ shift = 3
+ choice = input("would you like to encode or decode?")
+ word = input("Please enter text")
+ letters = string.ascii_letters + string.punctuation + string.digits
+ encoded = ""
+ if choice == "encode":
+ for letter in word:
+ if letter == " ":
+ encoded = encoded + " "
+ else:
+ x = letters.index(letter) + shift
+ encoded = encoded + letters[x]
+ if choice == "decode":
+ for letter in word:
+ if letter == " ":
+ encoded = encoded + " "
+ else:
+ x = letters.index(letter) - shift
+ encoded = encoded + letters[x]
+
+ print(encoded)
Here is what happens when we run it:
.. sourcecode:: console
- robertk01 Desktop$ pylint simplecaesar.py
- ************* Module simplecaesar
- simplecaesar.py:7:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
- simplecaesar.py:11:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
- simplecaesar.py:15:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ tutor Desktop$ pylint simplecaesar.py
+ ************* Module simplecaesar
+ simplecaesar.py:8:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:11:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:12:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:16:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:18:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:19:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:23:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:25:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
+ simplecaesar.py:26:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
- ------------------------------------------------------------------
- Your code has been rated at 8.42/10 (previous run: 7.37/10, +1.05)
+ ------------------------------------------------------------------
+ Your code has been rated at 5.26/10 (previous run: 4.74/10, +0.53)
-Nice! Pylint told us how much our code rating has improved since our last run, and we're down to just the ``invalid-name`` messages.
+Nice! Pylint told us how much our code rating has improved since our last run,
+and we're down to just the ``invalid-name`` messages.
There are fairly well defined conventions around naming things like instance
variables, functions, classes, etc. The conventions focus on the use of
@@ -246,11 +206,11 @@ UPPERCASE and lowercase as well as the characters that separate multiple words
in the name. This lends itself well to checking via a regular expression, thus
the **should match (([A-Z\_][A-Z1-9\_]*)|(__.*__))$**.
-In this case Pylint is telling me that those variables appear to be constants
+In this case Pylint is telling us that those variables appear to be constants
and should be all UPPERCASE. This is an in-house convention that has lived with Pylint
since its inception. You too can create your own in-house naming
conventions but for the purpose of this tutorial, we want to stick to the `PEP 8`_
-standard. In this case, the variables I declared should follow the convention
+standard. In this case, the variables we declared should follow the convention
of all lowercase. The appropriate rule would be something like:
"should match [a-z\_][a-z0-9\_]{2,30}$". Notice the lowercase letters in the
regular expression (a-z versus A-Z).
@@ -260,14 +220,15 @@ will now be quite quiet:
.. sourcecode:: console
- robertk01 Desktop$ pylint --const-rgx='[a-z_][a-z0-9_]{2,30}$' simplecaesar.py
+ tutor Desktop$ pylint simplecaesar.py --const-rgx='[a-z\_][a-z0-9\_]{2,30}$'
+ ************* Module simplecaesar
+ simplecaesar.py:18:12: C0103: Constant name "x" doesn't conform to '[a-z\\_][a-z0-9\\_]{2,30}$' pattern (invalid-name)
+ simplecaesar.py:25:12: C0103: Constant name "x" doesn't conform to '[a-z\\_][a-z0-9\\_]{2,30}$' pattern (invalid-name)
- -------------------------------------------------------------------
- Your code has been rated at 10.00/10 (previous run: 8.42/10, +1.58)
+ ------------------------------------------------------------------
+ Your code has been rated at 8.95/10 (previous run: 5.26/10, +3.68)
-
-Regular expressions can be quite a beast so take my word on this particular
-example but go ahead and `read up`_ on them if you want.
+You can `read up`_ on regular expressions or use `a website to help you`_.
.. tip::
It would really be a pain to specify that regex on the command line all the time, particularly if we're using many other options.
@@ -276,6 +237,5 @@ example but go ahead and `read up`_ on them if you want.
quickly sharing them with others. Invoking ``pylint --generate-toml-config`` will create a sample ``.toml`` section with all the options set and explained in comments.
This can then be added to your ``pyproject.toml`` file or any other ``.toml`` file pointed to with the ``--rcfile`` option.
-That's it for the basic intro. More tutorials will follow.
-
.. _`read up`: https://docs.python.org/library/re.html
+.. _`a website to help you`: https://regex101.com/
diff --git a/doc/user_guide/checkers/extensions.rst b/doc/user_guide/checkers/extensions.rst
index d52c9c704..0eaf22792 100644
--- a/doc/user_guide/checkers/extensions.rst
+++ b/doc/user_guide/checkers/extensions.rst
@@ -14,12 +14,15 @@ Pylint provides the following optional plugins:
- :ref:`pylint.extensions.comparison_placement`
- :ref:`pylint.extensions.confusing_elif`
- :ref:`pylint.extensions.consider_ternary_expression`
+- :ref:`pylint.extensions.dict_init_mutate`
- :ref:`pylint.extensions.docparams`
- :ref:`pylint.extensions.docstyle`
+- :ref:`pylint.extensions.dunder`
- :ref:`pylint.extensions.empty_comment`
- :ref:`pylint.extensions.emptystring`
- :ref:`pylint.extensions.eq_without_hash`
- :ref:`pylint.extensions.for_any_all`
+- :ref:`pylint.extensions.magic_value`
- :ref:`pylint.extensions.mccabe`
- :ref:`pylint.extensions.no_self_use`
- :ref:`pylint.extensions.overlapping_exceptions`
@@ -79,6 +82,9 @@ Code Style checker Messages
Emitted when an if assignment is directly followed by an if statement and
both can be combined by using an assignment expression ``:=``. Requires
Python 3.8 and ``py-version >= 3.8``.
+:consider-using-augmented-assign (R6104): *Use '%s' to do an augmented assign directly*
+ Emitted when an assignment is referring to the object that it is assigning
+ to. This can be changed to be an augmented assign. Disabled by default!
.. _pylint.extensions.emptystring:
@@ -91,7 +97,7 @@ Verbatim name of the checker is ``compare-to-empty-string``.
Compare-To-Empty-String checker Messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-:compare-to-empty-string (C1901): *Avoid comparisons to empty string*
+:compare-to-empty-string (C1901): *"%s" can be simplified to "%s" as an empty string is falsey*
Used when Pylint detects comparison to an empty string constant.
@@ -105,7 +111,7 @@ Verbatim name of the checker is ``compare-to-zero``.
Compare-To-Zero checker Messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-:compare-to-zero (C2001): *Avoid comparisons to zero*
+:compare-to-zero (C2001): *"%s" can be simplified to "%s" as 0 is falsey*
Used when Pylint detects comparison to a 0 constant.
@@ -259,6 +265,21 @@ Design checker Messages
Cyclomatic
+.. _pylint.extensions.dict_init_mutate:
+
+Dict-Init-Mutate checker
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+This checker is provided by ``pylint.extensions.dict_init_mutate``.
+Verbatim name of the checker is ``dict-init-mutate``.
+
+Dict-Init-Mutate checker Messages
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+:dict-init-mutate (C3401): *Dictionary mutated immediately after initialization*
+ Dictionaries can be initialized with a single statement using dictionary
+ literal syntax.
+
+
.. _pylint.extensions.docstyle:
Docstyle checker
@@ -275,6 +296,23 @@ Docstyle checker Messages
Used when a blank line is found at the beginning of a docstring.
+.. _pylint.extensions.dunder:
+
+Dunder checker
+~~~~~~~~~~~~~~
+
+This checker is provided by ``pylint.extensions.dunder``.
+Verbatim name of the checker is ``dunder``.
+
+See also :ref:`dunder checker's options' documentation <dunder-options>`
+
+Dunder checker Messages
+^^^^^^^^^^^^^^^^^^^^^^^
+:bad-dunder-name (W3201): *Bad or misspelled dunder method name %s.*
+ Used when a dunder method is misspelled or defined with a name not within the
+ predefined list of dunder names.
+
+
.. _pylint.extensions.check_elif:
Else If Used checker
@@ -335,6 +373,23 @@ Import-Private-Name checker Messages
underscores should be considered private.
+.. _pylint.extensions.magic_value:
+
+Magic-Value checker
+~~~~~~~~~~~~~~~~~~~
+
+This checker is provided by ``pylint.extensions.magic_value``.
+Verbatim name of the checker is ``magic-value``.
+
+See also :ref:`magic-value checker's options' documentation <magic-value-options>`
+
+Magic-Value checker Messages
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+:magic-value-comparison (R2004): *Consider using a named constant or an enum instead of '%s'.*
+ Using named constants instead of magic values helps improve readability and
+ maintainability of your code, try to avoid them in comparisons.
+
+
.. _pylint.extensions.redefined_variable_type:
Multiple Types checker
diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst
index bff61f93b..8b166855a 100644
--- a/doc/user_guide/checkers/features.rst
+++ b/doc/user_guide/checkers/features.rst
@@ -95,7 +95,7 @@ Basic checker Messages
Used when a break or a return statement is found inside the finally clause of
a try...finally block: the exceptions raised in the try clause will be
silently swallowed instead of being re-raised.
-:assert-on-tuple (W0199): *Assert called on a 2-item-tuple. Did you mean 'assert x,y'?*
+:assert-on-tuple (W0199): *Assert called on a populated tuple. Did you mean 'assert x,y'?*
A call of assert on a tuple will always evaluate to true if the tuple is not
empty, and will always evaluate to false if it is.
:assert-on-string-literal (W0129): *Assert statement has a string literal as its first argument. The assert will %s fail.*
@@ -135,6 +135,9 @@ Basic checker Messages
argument list as the lambda itself; such lambda expressions are in all but a
few cases replaceable with the function being called in the body of the
lambda.
+:named-expr-without-context (W0131): *Named expression used without context*
+ Emitted if named expression is used to do a regular assignment outside a
+ context like if, for, while, or a comprehension.
:redeclared-assigned-name (W0128): *Redeclared variable %r in assignment*
Emitted when we detect that a variable was redeclared in the same assignment.
:pointless-statement (W0104): *Statement seems to have no effect*
@@ -241,12 +244,12 @@ Classes checker Messages
Used when a class has an inconsistent method resolution order.
:inherit-non-class (E0239): *Inheriting %r, which is not a class.*
Used when a class inherits from something which is not a class.
-:invalid-class-object (E0243): *Invalid __class__ object*
- Used when an invalid object is assigned to a __class__ property. Only a class
- is permitted.
:invalid-slots (E0238): *Invalid __slots__ object*
Used when an invalid __slots__ is found in class. Only a string, an iterable
or a sequence is permitted.
+:invalid-class-object (E0243): *Invalid assignment to '__class__'. Should be a class definition but got a '%s'*
+ Used when an invalid object is assigned to a __class__ property. Only a class
+ is permitted.
:invalid-slots-object (E0236): *Invalid object %r in __slots__, must contain only non empty strings*
Used when an invalid (non-string) object occurs in __slots__.
:no-method-argument (E0211): *Method %r has no argument*
@@ -305,7 +308,7 @@ Classes checker Messages
Used when an instance attribute is defined outside the __init__ method.
:subclassed-final-class (W0240): *Class %r is a subclass of a class decorated with typing.final: %r*
Used when a class decorated with typing.final has been subclassed.
-:abstract-method (W0223): *Method %r is abstract in class %r but is not overridden*
+:abstract-method (W0223): *Method %r is abstract in class %r but is not overridden in child class %r*
Used when an abstract method (i.e. raise NotImplementedError) is not
overridden in concrete class.
:overridden-final-method (W0239): *Method %r overrides a method decorated with typing.final which is defined in class %r*
@@ -440,7 +443,7 @@ Exceptions checker Messages
:duplicate-except (W0705): *Catching previously caught exception type %s*
Used when an except catches a type that was already caught by a previous
handler.
-:broad-except (W0703): *Catching too general exception %s*
+:broad-exception-caught (W0718): *Catching too general exception %s*
Used when an except catches a too general exception, possibly burying
unrelated errors.
:raise-missing-from (W0707): *Consider explicitly re-raising using %s'%s from %s'*
@@ -462,6 +465,8 @@ Exceptions checker Messages
operations between exceptions in except handlers.
:bare-except (W0702): *No exception type(s) specified*
Used when an except clause doesn't specify exceptions type to catch.
+:broad-exception-raised (W0719): *Raising too general exception: %s*
+ Used when an except raises a too general exception.
:try-except-raise (W0706): *The except handler raises immediately*
Used when an except handler uses raise as its first or only operator. This is
useless because it raises back the exception immediately. Remove the raise
@@ -737,6 +742,9 @@ Refactoring checker Messages
verbose.
:consider-merging-isinstance (R1701): *Consider merging these isinstance calls to isinstance(%s, (%s))*
Used when multiple consecutive isinstance calls can be merged into one.
+:use-dict-literal (R1735): *Consider using '%s' instead of a call to 'dict'.*
+ Emitted when using dict() to create a dictionary instead of a literal '{ ...
+ }'. The literal is faster as it avoids an additional function call.
:consider-using-max-builtin (R1731): *Consider using '%s' instead of unnecessary if block*
Using the max builtin instead of a conditional improves readability and
conciseness.
@@ -779,9 +787,6 @@ Refactoring checker Messages
:consider-swap-variables (R1712): *Consider using tuple unpacking for swapping variables*
You do not have to use a temporary variable in order to swap variables. Using
"tuple unpacking" to directly swap variables makes the intention more clear.
-:use-dict-literal (R1735): *Consider using {} instead of dict()*
- Emitted when using dict() to create an empty dictionary instead of the
- literal {}. The literal is faster as it avoids an additional function call.
:trailing-comma-tuple (R1707): *Disallow trailing comma tuple*
In Python, a tuple is actually created by the comma symbol, not by the
parentheses. Unfortunately, one can actually create a tuple by misplacing a
@@ -845,7 +850,7 @@ Refactoring checker Messages
Emitted when a single "return" or "return None" statement is found at the end
of function or method definition. This statement can safely be removed
because Python will implicitly return None
-:use-implicit-booleaness-not-comparison (C1803): *'%s' can be simplified to '%s' as an empty sequence is falsey*
+:use-implicit-booleaness-not-comparison (C1803): *'%s' can be simplified to '%s' as an empty %s is falsey*
Used when Pylint detects that collection literal comparison is being used to
check for emptiness; Use implicit booleaness instead of a collection classes;
empty collections are considered as false
@@ -927,6 +932,12 @@ Stdlib checker Messages
:invalid-envvar-value (E1507): *%s does not support %s type argument*
Env manipulation functions support only string type arguments. See
https://docs.python.org/3/library/os.html#os.getenv.
+:singledispatch-method (E1519): *singledispatch decorator should not be used with methods, use singledispatchmethod instead.*
+ singledispatch should decorate functions and not class/instance methods. Use
+ singledispatchmethod for those cases.
+:singledispatchmethod-function (E1520): *singledispatchmethod decorator should not be used with functions, use singledispatch instead.*
+ singledispatchmethod should decorate class/instance methods and not
+ functions. Use singledispatch for those cases.
:bad-open-mode (W1501): *"%s" is not a valid mode for open.*
Python supports: r, w, a[, x] modes with b, +, and U (only with r) options.
See https://docs.python.org/3/library/functions.html#open
@@ -979,8 +990,8 @@ Stdlib checker Messages
https://docs.python.org/3/library/subprocess.html#subprocess.run
:bad-thread-instantiation (W1506): *threading.Thread needs the target function*
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.
+ the target function being passed as a kwarg or as a second argument. By
+ default, the first parameter is the group param, not the target param.
String checker
@@ -1144,6 +1155,9 @@ Typecheck checker Messages
:invalid-slice-index (E1127): *Slice index is not an int, None, or instance with __index__*
Used when a slice index is not an integer, None, or an object with an
__index__ method.
+:invalid-slice-step (E1144): *Slice step cannot be 0*
+ Used when a slice step is 0 and the object doesn't implement a custom
+ __getitem__ method.
:too-many-function-args (E1121): *Too many positional arguments for %s call*
Used when a function call passes too many positional arguments.
:unexpected-keyword-arg (E1123): *Unexpected keyword argument %r in %s call*
@@ -1295,7 +1309,9 @@ Variables checker Messages
variable is not defined in the module scope.
:self-cls-assignment (W0642): *Invalid assignment to %s in method*
Invalid assignment to self or cls in instance or class method respectively.
-:unbalanced-tuple-unpacking (W0632): *Possible unbalanced tuple unpacking with sequence%s: left side has %d label(s), right side has %d value(s)*
+:unbalanced-dict-unpacking (W0644): *Possible unbalanced dict unpacking with %s: left side has %d label%s, right side has %d value%s*
+ Used when there is an unbalanced dict unpacking in assignment or for loop
+:unbalanced-tuple-unpacking (W0632): *Possible unbalanced tuple unpacking with sequence %s: left side has %d label%s, right side has %d value%s*
Used when there is an unbalanced tuple unpacking in assignment
:possibly-unused-variable (W0641): *Possibly unused variable %r*
Used when a variable is defined but might not be used. The possibility comes
diff --git a/doc/user_guide/configuration/all-options.rst b/doc/user_guide/configuration/all-options.rst
index 41b58efba..c9e651565 100644
--- a/doc/user_guide/configuration/all-options.rst
+++ b/doc/user_guide/configuration/all-options.rst
@@ -99,7 +99,7 @@ Standard Checkers
--ignore-paths
""""""""""""""
-*Add files or directories matching the regular expressions patterns to the ignore-list. The regex matches against paths and can be in Posix or Windows format. Because '\' represents the directory delimiter on Windows systems, it can't be used as an escape character.*
+*Add files or directories matching the regular expressions patterns to the ignore-list. The regex matches against paths and can be in Posix or Windows format. Because '\\' represents the directory delimiter on Windows systems, it can't be used as an escape character.*
**Default:** ``[]``
@@ -217,9 +217,9 @@ Standard Checkers
confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"]
- # disable =
+ disable = ["consider-using-augmented-assign"]
- # enable =
+ enable = []
evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))"
@@ -620,7 +620,7 @@ Standard Checkers
"""""""""""""""""""""""""""""""""""""""
*List of valid names for the first argument in a metaclass class method.*
-**Default:** ``('cls',)``
+**Default:** ``('mcs',)``
@@ -642,7 +642,7 @@ Standard Checkers
valid-classmethod-first-arg = ["cls"]
- valid-metaclass-classmethod-first-arg = ["cls"]
+ valid-metaclass-classmethod-first-arg = ["mcs"]
@@ -798,7 +798,7 @@ Standard Checkers
""""""""""""""""""""""""
*Exceptions that will emit a warning when caught.*
-**Default:** ``('BaseException', 'Exception')``
+**Default:** ``('builtins.BaseException', 'builtins.Exception')``
@@ -812,7 +812,7 @@ Standard Checkers
.. code-block:: toml
[tool.pylint.exceptions]
- overgeneral-exceptions = ["BaseException", "Exception"]
+ overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
@@ -1673,6 +1673,68 @@ Extensions
</details>
+.. _dunder-options:
+
+``Dunder`` **Checker**
+----------------------
+--good-dunder-names
+"""""""""""""""""""
+*Good dunder names which should always be accepted.*
+
+**Default:** ``[]``
+
+
+
+.. raw:: html
+
+ <details>
+ <summary><a>Example configuration section</a></summary>
+
+**Note:** Only ``tool.pylint`` is required, the section title is not. These are the default values.
+
+.. code-block:: toml
+
+ [tool.pylint.dunder]
+ good-dunder-names = []
+
+
+
+.. raw:: html
+
+ </details>
+
+
+.. _magic-value-options:
+
+``Magic-value`` **Checker**
+---------------------------
+--valid-magic-values
+""""""""""""""""""""
+* List of valid magic values that `magic-value-compare` will not detect.*
+
+**Default:** ``(0, -1, 1, '', '__main__')``
+
+
+
+.. raw:: html
+
+ <details>
+ <summary><a>Example configuration section</a></summary>
+
+**Note:** Only ``tool.pylint`` is required, the section title is not. These are the default values.
+
+.. code-block:: toml
+
+ [tool.pylint.magic-value]
+ valid-magic-values = [0, -1, 1, "", "__main__"]
+
+
+
+.. raw:: html
+
+ </details>
+
+
.. _parameter_documentation-options:
``Parameter_documentation`` **Checker**
diff --git a/doc/user_guide/configuration/index.rst b/doc/user_guide/configuration/index.rst
index d039b4445..ffe8c51a3 100644
--- a/doc/user_guide/configuration/index.rst
+++ b/doc/user_guide/configuration/index.rst
@@ -9,7 +9,7 @@ various projects and a lot of checks to activate if they suit your style.
You can generate a sample configuration file with ``--generate-toml-config``
or ``--generate-rcfile``. Every option present on the command line before this
-will be included in the file
+will be included in the file.
For example::
@@ -18,6 +18,13 @@ For example::
In practice, it is often better to create a minimal configuration file which only contains
configuration overrides. For all other options, Pylint will use its default values.
+.. note::
+
+ The internals that create the configuration files fall back to the default values if
+ no other value was given. This means that some values depend on the interpreter that
+ was used to generate the file. Most notably ``py-version`` which defaults to the
+ current interpreter.
+
.. toctree::
:maxdepth: 2
:titlesonly:
diff --git a/doc/user_guide/installation/ide_integration/flymake-emacs.rst b/doc/user_guide/installation/ide_integration/flymake-emacs.rst
index 44b9b3262..79310ff59 100644
--- a/doc/user_guide/installation/ide_integration/flymake-emacs.rst
+++ b/doc/user_guide/installation/ide_integration/flymake-emacs.rst
@@ -4,12 +4,10 @@ Using Pylint through Flymake in Emacs
=====================================
.. warning::
- If you're reading this doc and are actually using flymake please
- open a support question at https://github.com/PyCQA/pylint/issues/new/choose
- and tell us, we don't have any maintainers for emacs and are thinking about
- dropping the support.
-
-.. TODO 3.0, do we still need to support flymake ?
+ The Emacs package now has its own repository and is looking for a maintainer.
+ If you're reading this doc and are interested in maintaining this package or
+ are actually using flymake please open an issue at
+ https://github.com/emacsorphanage/pylint/issues/new/choose
To enable Flymake for Python, insert the following into your .emacs:
diff --git a/doc/user_guide/installation/ide_integration/index.rst b/doc/user_guide/installation/ide_integration/index.rst
index c1bf5eb4d..c359c8ee1 100644
--- a/doc/user_guide/installation/ide_integration/index.rst
+++ b/doc/user_guide/installation/ide_integration/index.rst
@@ -18,6 +18,7 @@ Below you can find tutorials for some of the most common ones.
- PyDev_
- pyscripter_ in the `Tool -> Tools` menu.
- Spyder_ in the `View -> Panes -> Static code analysis`
+- `Sublime Text`_
- :ref:`TextMate <pylint_in_textmate>`
- Vim_
- `Visual Studio Code`_ in the `Preferences -> Settings` menu
@@ -36,6 +37,7 @@ Below you can find tutorials for some of the most common ones.
.. _pydev: https://www.pydev.org/manual_adv_pylint.html
.. _pyscripter: https://github.com/pyscripter/pyscripter
.. _spyder: https://docs.spyder-ide.org/current/panes/pylint.html
+.. _Sublime Text: https://packagecontrol.io/packages/SublimeLinter-pylint
.. _Vim: https://www.vim.org/scripts/script.php?script_id=891
.. _Visual Studio: https://docs.microsoft.com/visualstudio/python/code-pylint
.. _Visual Studio Code: https://code.visualstudio.com/docs/python/linting#_pylint
diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst
index 558b3f1bf..d7c058823 100644
--- a/doc/user_guide/messages/messages_overview.rst
+++ b/doc/user_guide/messages/messages_overview.rst
@@ -102,6 +102,7 @@ All messages in the error category:
error/invalid-repr-returned
error/invalid-sequence-index
error/invalid-slice-index
+ error/invalid-slice-step
error/invalid-slots
error/invalid-slots-object
error/invalid-star-assignment-target
@@ -145,6 +146,8 @@ All messages in the error category:
error/return-arg-in-generator
error/return-in-init
error/return-outside-function
+ error/singledispatch-method
+ error/singledispatchmethod-function
error/star-needs-assignment-target
error/syntax-error
error/too-few-format-args
@@ -205,6 +208,7 @@ All messages in the warning category:
warning/assert-on-tuple
warning/attribute-defined-outside-init
warning/bad-builtin
+ warning/bad-dunder-name
warning/bad-format-string
warning/bad-format-string-key
warning/bad-indentation
@@ -214,7 +218,8 @@ All messages in the warning category:
warning/bare-except
warning/binary-op-exception
warning/boolean-datetime
- warning/broad-except
+ warning/broad-exception-caught
+ warning/broad-exception-raised
warning/cell-var-from-loop
warning/comparison-with-callable
warning/confusing-with-statement
@@ -226,6 +231,7 @@ All messages in the warning category:
warning/deprecated-method
warning/deprecated-module
warning/deprecated-typing-alias
+ warning/dict-init-mutate
warning/differing-param-doc
warning/differing-type-doc
warning/duplicate-except
@@ -273,6 +279,7 @@ All messages in the warning category:
warning/missing-yield-type-doc
warning/modified-iterating-list
warning/multiple-constructor-doc
+ warning/named-expr-without-context
warning/nan-comparison
warning/non-ascii-file-name
warning/non-parent-init-called
@@ -307,6 +314,7 @@ All messages in the warning category:
warning/super-without-brackets
warning/too-many-try-statements
warning/try-except-raise
+ warning/unbalanced-dict-unpacking
warning/unbalanced-tuple-unpacking
warning/undefined-loop-variable
warning/unknown-option-value
@@ -341,6 +349,7 @@ All renamed messages in the warning category:
:maxdepth: 1
:titlesonly:
+ warning/broad-except
warning/cache-max-size-none
warning/implicit-str-concat-in-sequence
warning/lru-cache-decorating-method
@@ -460,6 +469,7 @@ All messages in the refactor category:
refactor/consider-swap-variables
refactor/consider-using-alias
refactor/consider-using-assignment-expr
+ refactor/consider-using-augmented-assign
refactor/consider-using-dict-comprehension
refactor/consider-using-from-import
refactor/consider-using-generator
@@ -480,6 +490,7 @@ All messages in the refactor category:
refactor/empty-comment
refactor/inconsistent-return-statements
refactor/literal-comparison
+ refactor/magic-value-comparison
refactor/no-classmethod-decorator
refactor/no-else-break
refactor/no-else-continue
diff --git a/doc/user_guide/usage/output.rst b/doc/user_guide/usage/output.rst
index c98eb8476..50a7fa76e 100644
--- a/doc/user_guide/usage/output.rst
+++ b/doc/user_guide/usage/output.rst
@@ -131,7 +131,7 @@ Following the analysis message, Pylint can display a set of reports,
each one focusing on a particular aspect of the project, such as number
of messages by categories, modules dependencies. These features can
be enabled through the ``--reports=y`` option, or its shorthand
-version ``-rn``.
+version ``-ry``.
For instance, the metrics report displays summaries gathered from the
current run.
diff --git a/doc/whatsnew/2/2.15/index.rst b/doc/whatsnew/2/2.15/index.rst
index 1e404e81e..b1e40d274 100644
--- a/doc/whatsnew/2/2.15/index.rst
+++ b/doc/whatsnew/2/2.15/index.rst
@@ -169,7 +169,7 @@ False Positives Fixed
---------------------
- Fix the message for ``unnecessary-dunder-call`` for ``__aiter__`` and
- ``__aneext__``. Also
+ ``__anext__``. Also
only emit the warning when ``py-version`` >= 3.10.
Closes #7529 (`#7529 <https://github.com/PyCQA/pylint/issues/7529>`_)
diff --git a/doc/whatsnew/fragments/2374.false_negative b/doc/whatsnew/fragments/2374.false_negative
new file mode 100644
index 000000000..251ffc396
--- /dev/null
+++ b/doc/whatsnew/fragments/2374.false_negative
@@ -0,0 +1,4 @@
+Emit ``used-before-assignment`` when function arguments are redefined inside
+an inner function and accessed there before assignment.
+
+Closes #2374
diff --git a/doc/whatsnew/fragments/2876.new_check b/doc/whatsnew/fragments/2876.new_check
new file mode 100644
index 000000000..a8353a32e
--- /dev/null
+++ b/doc/whatsnew/fragments/2876.new_check
@@ -0,0 +1,4 @@
+Add new extension checker ``dict-init-mutate`` that flags mutating a dictionary immediately
+after the dictionary was created.
+
+Closes #2876
diff --git a/doc/whatsnew/fragments/3038.new_check b/doc/whatsnew/fragments/3038.new_check
new file mode 100644
index 000000000..8e61147fc
--- /dev/null
+++ b/doc/whatsnew/fragments/3038.new_check
@@ -0,0 +1,4 @@
+Added ``bad-dunder-name`` extension check, which flags bad or misspelled dunder methods.
+You can use the ``good-dunder-names`` option to allow specific dunder names.
+
+Closes #3038
diff --git a/doc/whatsnew/fragments/3044.bugfix b/doc/whatsnew/fragments/3044.bugfix
new file mode 100644
index 000000000..9f764ca4b
--- /dev/null
+++ b/doc/whatsnew/fragments/3044.bugfix
@@ -0,0 +1,3 @@
+Fix bug in detecting ``unused-variable`` when iterating on variable.
+
+Closes #3044
diff --git a/doc/whatsnew/fragments/3299.bugfix b/doc/whatsnew/fragments/3299.bugfix
new file mode 100644
index 000000000..dd45d1978
--- /dev/null
+++ b/doc/whatsnew/fragments/3299.bugfix
@@ -0,0 +1,4 @@
+Fix bug in scanning of names inside arguments to `typing.Literal`.
+See https://peps.python.org/pep-0586/#literals-enums-and-forward-references for details.
+
+Refs #3299
diff --git a/doc/whatsnew/fragments/3299.false_positive b/doc/whatsnew/fragments/3299.false_positive
new file mode 100644
index 000000000..b1e61c931
--- /dev/null
+++ b/doc/whatsnew/fragments/3299.false_positive
@@ -0,0 +1,3 @@
+Fix false positive for ``unused-variable`` and ``unused-import`` when a name is only used in a string literal type annotation.
+
+Closes #3299
diff --git a/doc/whatsnew/fragments/3391.extension b/doc/whatsnew/fragments/3391.extension
new file mode 100644
index 000000000..8879610b2
--- /dev/null
+++ b/doc/whatsnew/fragments/3391.extension
@@ -0,0 +1,6 @@
+Added ``consider-using-augmented-assign`` check for ``CodeStyle`` extension
+which flags ``x = x + 1`` to simplify to ``x += 1``.
+This check is disabled by default. To use it, load the code style extension
+with ``load-plugins=pylint.extensions.code_style`` and add ``consider-using-augmented-assign`` in the ``enable`` option.
+
+Closes #3391
diff --git a/doc/whatsnew/fragments/3822.false_positive b/doc/whatsnew/fragments/3822.false_positive
new file mode 100644
index 000000000..00a2143cf
--- /dev/null
+++ b/doc/whatsnew/fragments/3822.false_positive
@@ -0,0 +1,3 @@
+``trailing-whitespaces`` is no longer reported within strings.
+
+Closes #3822
diff --git a/doc/whatsnew/fragments/4150.false_negative b/doc/whatsnew/fragments/4150.false_negative
new file mode 100644
index 000000000..f4b18d814
--- /dev/null
+++ b/doc/whatsnew/fragments/4150.false_negative
@@ -0,0 +1,3 @@
+Fix a false negative for ``unused-import`` when one module used an import in a type annotation that was also used in another module.
+
+Closes #4150
diff --git a/doc/whatsnew/fragments/4354.bugfix b/doc/whatsnew/fragments/4354.bugfix
new file mode 100644
index 000000000..09caf8d13
--- /dev/null
+++ b/doc/whatsnew/fragments/4354.bugfix
@@ -0,0 +1,3 @@
+Fix ignored files being linted when passed on stdin.
+
+Closes #4354
diff --git a/doc/whatsnew/fragments/4562.bugfix b/doc/whatsnew/fragments/4562.bugfix
new file mode 100644
index 000000000..4e153d487
--- /dev/null
+++ b/doc/whatsnew/fragments/4562.bugfix
@@ -0,0 +1,3 @@
+Fix ``no-member`` false negative when augmented assign is done manually, without ``+=``.
+
+Closes #4562
diff --git a/doc/whatsnew/fragments/4655.bugfix b/doc/whatsnew/fragments/4655.bugfix
new file mode 100644
index 000000000..e30433d47
--- /dev/null
+++ b/doc/whatsnew/fragments/4655.bugfix
@@ -0,0 +1,3 @@
+Any assertion on a populated tuple will now receive a ``assert-on-tuple`` warning.
+
+Closes #4655
diff --git a/doc/whatsnew/fragments/4743.bugfix b/doc/whatsnew/fragments/4743.bugfix
new file mode 100644
index 000000000..1f8c30f1a
--- /dev/null
+++ b/doc/whatsnew/fragments/4743.bugfix
@@ -0,0 +1,4 @@
+``missing-return-doc``, ``missing-raises-doc`` and ``missing-yields-doc`` now respect
+the ``no-docstring-rgx`` option.
+
+Closes #4743
diff --git a/doc/whatsnew/fragments/4792.false_negative b/doc/whatsnew/fragments/4792.false_negative
new file mode 100644
index 000000000..6d05ef565
--- /dev/null
+++ b/doc/whatsnew/fragments/4792.false_negative
@@ -0,0 +1,3 @@
+Flag ``superfluous-parens`` if parentheses are used during string concatenation.
+
+Closes #4792
diff --git a/doc/whatsnew/fragments/4809.false_positive b/doc/whatsnew/fragments/4809.false_positive
new file mode 100644
index 000000000..a33be0ea5
--- /dev/null
+++ b/doc/whatsnew/fragments/4809.false_positive
@@ -0,0 +1,3 @@
+Fix false positive for ``global-variable-not-assigned`` when a global variable is re-assigned via an ``ImportFrom`` node.
+
+Closes #4809
diff --git a/doc/whatsnew/fragments/4913.false_negative b/doc/whatsnew/fragments/4913.false_negative
new file mode 100644
index 000000000..bb8686347
--- /dev/null
+++ b/doc/whatsnew/fragments/4913.false_negative
@@ -0,0 +1,3 @@
+Emit ``used-before-assignment`` when relying on names only defined under conditions always testing false.
+
+Closes #4913
diff --git a/doc/whatsnew/fragments/519.false_negative b/doc/whatsnew/fragments/519.false_negative
new file mode 100644
index 000000000..7c8a0f45c
--- /dev/null
+++ b/doc/whatsnew/fragments/519.false_negative
@@ -0,0 +1,3 @@
+Code following a call to ``quit``, ``exit``, ``sys.exit`` or ``os._exit`` will be marked as `unreachable`.
+
+Refs #519
diff --git a/doc/whatsnew/fragments/5478.bugfix b/doc/whatsnew/fragments/5478.bugfix
new file mode 100644
index 000000000..8cadb729c
--- /dev/null
+++ b/doc/whatsnew/fragments/5478.bugfix
@@ -0,0 +1,3 @@
+``consider-iterating-dictionary`` will no longer be raised if bitwise operations are used.
+
+Closes #5478
diff --git a/doc/whatsnew/fragments/5797.new_check b/doc/whatsnew/fragments/5797.new_check
new file mode 100644
index 000000000..f82abe09d
--- /dev/null
+++ b/doc/whatsnew/fragments/5797.new_check
@@ -0,0 +1,4 @@
+Add new check called ``unbalanced-dict-unpacking`` to check for unbalanced dict unpacking
+in assignment and for loops.
+
+Closes #5797
diff --git a/doc/whatsnew/fragments/5886.false_positive b/doc/whatsnew/fragments/5886.false_positive
new file mode 100644
index 000000000..d6ab03940
--- /dev/null
+++ b/doc/whatsnew/fragments/5886.false_positive
@@ -0,0 +1,3 @@
+Fix ``deprecated-method`` false positive when alias for method is similar to name of deprecated method.
+
+Closes #5886
diff --git a/doc/whatsnew/fragments/5920.other b/doc/whatsnew/fragments/5920.other
new file mode 100644
index 000000000..5bd356a9c
--- /dev/null
+++ b/doc/whatsnew/fragments/5920.other
@@ -0,0 +1,3 @@
+Pylint now provides basic support for Python 3.11.
+
+Closes #5920
diff --git a/doc/whatsnew/fragments/6242.bugfix b/doc/whatsnew/fragments/6242.bugfix
new file mode 100644
index 000000000..25d323e7e
--- /dev/null
+++ b/doc/whatsnew/fragments/6242.bugfix
@@ -0,0 +1,4 @@
+Pylint will now filter duplicates given to it before linting. The output should
+be the same whether a file is given/discovered multiple times or not.
+
+Closes #6242, #4053
diff --git a/doc/whatsnew/fragments/6543.feature b/doc/whatsnew/fragments/6543.feature
new file mode 100644
index 000000000..0ebb9b19f
--- /dev/null
+++ b/doc/whatsnew/fragments/6543.feature
@@ -0,0 +1,5 @@
+Update ``pyreverse`` to differentiate between aggregations and compositions.
+``pyreverse`` checks if it's an Instance or a Call of an object via method parameters (via type hints)
+to decide if it's a composition or an aggregation.
+
+Refs #6543
diff --git a/doc/whatsnew/fragments/6592.false_positive b/doc/whatsnew/fragments/6592.false_positive
new file mode 100644
index 000000000..846ddce96
--- /dev/null
+++ b/doc/whatsnew/fragments/6592.false_positive
@@ -0,0 +1,3 @@
+Fix false positive for ``too-many-function-args`` when a function call is assigned to a class attribute inside the class where the function is defined.
+
+Closes #6592
diff --git a/doc/whatsnew/fragments/6639.false_negative b/doc/whatsnew/fragments/6639.false_negative
new file mode 100644
index 000000000..7735c8536
--- /dev/null
+++ b/doc/whatsnew/fragments/6639.false_negative
@@ -0,0 +1,3 @@
+``consider-using-join`` can now be emitted for non-empty string separators.
+
+Closes #6639
diff --git a/doc/whatsnew/fragments/6795.bugfix b/doc/whatsnew/fragments/6795.bugfix
new file mode 100644
index 000000000..20a29da35
--- /dev/null
+++ b/doc/whatsnew/fragments/6795.bugfix
@@ -0,0 +1,3 @@
+Remove ``__index__`` dunder method call from ``unnecessary-dunder-call`` check.
+
+Closes #6795
diff --git a/doc/whatsnew/fragments/6917.new_check b/doc/whatsnew/fragments/6917.new_check
new file mode 100644
index 000000000..d36aa2c59
--- /dev/null
+++ b/doc/whatsnew/fragments/6917.new_check
@@ -0,0 +1,4 @@
+Added ``singledispatch-method`` which informs that ``@singledispatch`` should decorate functions and not class/instance methods.
+Added ``singledispatchmethod-function`` which informs that ``@singledispatchmethod`` should decorate class/instance methods and not functions.
+
+Closes #6917
diff --git a/doc/whatsnew/fragments/7003.bugfix b/doc/whatsnew/fragments/7003.bugfix
new file mode 100644
index 000000000..3e2806f6f
--- /dev/null
+++ b/doc/whatsnew/fragments/7003.bugfix
@@ -0,0 +1,4 @@
+Fixed handling of ``--`` as separator between positional arguments and flags.
+This was not actually fixed in 2.14.5.
+
+Closes #7003, Refs #7096
diff --git a/doc/whatsnew/fragments/7124.other b/doc/whatsnew/fragments/7124.other
new file mode 100644
index 000000000..684cf2dc0
--- /dev/null
+++ b/doc/whatsnew/fragments/7124.other
@@ -0,0 +1,3 @@
+Update message for ``abstract-method`` to include child class name.
+
+Closes #7124
diff --git a/doc/whatsnew/fragments/7169.bugfix b/doc/whatsnew/fragments/7169.bugfix
new file mode 100644
index 000000000..6ddf1a498
--- /dev/null
+++ b/doc/whatsnew/fragments/7169.bugfix
@@ -0,0 +1,3 @@
+Don't crash on ``OSError`` in config file discovery.
+
+Closes #7169
diff --git a/doc/whatsnew/fragments/7208.user_action b/doc/whatsnew/fragments/7208.user_action
new file mode 100644
index 000000000..0492676b3
--- /dev/null
+++ b/doc/whatsnew/fragments/7208.user_action
@@ -0,0 +1,10 @@
+The ``accept-no-raise-doc`` option related to ``missing-raises-doc`` will now
+be correctly taken into account all the time.
+
+Pylint will no longer raise missing-raises-doc (W9006) when no exceptions are
+documented and accept-no-raise-doc is true (issue #7208).
+If you were expecting missing-raises-doc errors to be raised in that case, you
+will now have to add ``accept-no-raise-doc=no`` in your configuration to keep
+the same behavior.
+
+Closes #7208
diff --git a/doc/whatsnew/fragments/7214.bugfix b/doc/whatsnew/fragments/7214.bugfix
new file mode 100644
index 000000000..016d3dc3e
--- /dev/null
+++ b/doc/whatsnew/fragments/7214.bugfix
@@ -0,0 +1,3 @@
+Messages sent to reporter are now copied so a reporter cannot modify the message sent to other reporters.
+
+Closes #7214
diff --git a/doc/whatsnew/fragments/7264.bugfix b/doc/whatsnew/fragments/7264.bugfix
new file mode 100644
index 000000000..dc2aa5d40
--- /dev/null
+++ b/doc/whatsnew/fragments/7264.bugfix
@@ -0,0 +1,8 @@
+Fixed a case where custom plugins specified by command line could silently fail.
+
+Specifically, if a plugin relies on the ``init-hook`` option changing ``sys.path`` before
+it can be imported, this will now emit a ``bad-plugin-value`` message. Before this
+change, it would silently fail to register the plugin for use, but would load
+any configuration, which could have unintended effects.
+
+Fixes part of #7264.
diff --git a/doc/whatsnew/fragments/7264.internal b/doc/whatsnew/fragments/7264.internal
new file mode 100644
index 000000000..2bd3337bd
--- /dev/null
+++ b/doc/whatsnew/fragments/7264.internal
@@ -0,0 +1,9 @@
+Add and fix regression tests for plugin loading.
+
+This shores up the tests that cover the loading of custom plugins as affected
+by any changes made to the ``sys.path`` during execution of an ``init-hook``.
+Given the existing contract of allowing plugins to be loaded by fiddling with
+the path in this way, this is now the last bit of work needed to close Github
+issue #7264.
+
+Closes #7264
diff --git a/doc/whatsnew/fragments/7281.new_check b/doc/whatsnew/fragments/7281.new_check
new file mode 100644
index 000000000..46c2f0551
--- /dev/null
+++ b/doc/whatsnew/fragments/7281.new_check
@@ -0,0 +1,4 @@
+Add ``magic-number`` plugin checker for comparison with constants instead of named constants or enums.
+You can use it with ``--load-plugins=pylint.extensions.magic_value``.
+
+Closes #7281
diff --git a/doc/whatsnew/fragments/7290.false_positive b/doc/whatsnew/fragments/7290.false_positive
new file mode 100644
index 000000000..cfb6a13b5
--- /dev/null
+++ b/doc/whatsnew/fragments/7290.false_positive
@@ -0,0 +1,3 @@
+Pylint now understands the ``kw_only`` keyword argument for ``dataclass``.
+
+Closes #7290, closes #6550, closes #5857
diff --git a/doc/whatsnew/fragments/7311.false_positive b/doc/whatsnew/fragments/7311.false_positive
new file mode 100644
index 000000000..84d57502b
--- /dev/null
+++ b/doc/whatsnew/fragments/7311.false_positive
@@ -0,0 +1,4 @@
+Fix false positive for ``undefined-loop-variable`` in ``for-else`` loops that use a function
+having a return type annotation of ``NoReturn`` or ``Never``.
+
+Closes #7311
diff --git a/doc/whatsnew/fragments/7346.other b/doc/whatsnew/fragments/7346.other
new file mode 100644
index 000000000..01d1a26d9
--- /dev/null
+++ b/doc/whatsnew/fragments/7346.other
@@ -0,0 +1,4 @@
+Update Pyreverse's dot and plantuml printers to detect when class methods are abstract and show them with italic font.
+For the dot printer update the label to use html-like syntax.
+
+Closes #7346
diff --git a/doc/whatsnew/fragments/7368.false_positive b/doc/whatsnew/fragments/7368.false_positive
new file mode 100644
index 000000000..4e9551a32
--- /dev/null
+++ b/doc/whatsnew/fragments/7368.false_positive
@@ -0,0 +1,3 @@
+Fix ``used-before-assignment`` for functions/classes defined in type checking guard.
+
+Closes #7368
diff --git a/doc/whatsnew/fragments/7380.bugfix b/doc/whatsnew/fragments/7380.bugfix
new file mode 100644
index 000000000..dc5ea5fa6
--- /dev/null
+++ b/doc/whatsnew/fragments/7380.bugfix
@@ -0,0 +1,3 @@
+Update ``modified_iterating`` checker to fix a crash with ``for`` loops on empty list.
+
+Closes #7380
diff --git a/doc/whatsnew/fragments/7390.bugfix b/doc/whatsnew/fragments/7390.bugfix
new file mode 100644
index 000000000..c1808eec0
--- /dev/null
+++ b/doc/whatsnew/fragments/7390.bugfix
@@ -0,0 +1,3 @@
+Update wording for ``arguments-differ`` and ``arguments-renamed`` to clarify overriding object.
+
+Closes #7390
diff --git a/doc/whatsnew/fragments/7398.other b/doc/whatsnew/fragments/7398.other
new file mode 100644
index 000000000..e83974ccf
--- /dev/null
+++ b/doc/whatsnew/fragments/7398.other
@@ -0,0 +1,4 @@
+The ``docparams`` extension now considers typing in Numpy style docstrings
+as "documentation" for the ``missing-param-doc`` message.
+
+Refs #7398
diff --git a/doc/whatsnew/fragments/7401.bugfix b/doc/whatsnew/fragments/7401.bugfix
new file mode 100644
index 000000000..8b0f0e2a8
--- /dev/null
+++ b/doc/whatsnew/fragments/7401.bugfix
@@ -0,0 +1,3 @@
+``disable-next`` is now correctly scoped to only the succeeding line.
+
+Closes #7401
diff --git a/doc/whatsnew/fragments/7453.bugfix b/doc/whatsnew/fragments/7453.bugfix
new file mode 100644
index 000000000..94b5240dd
--- /dev/null
+++ b/doc/whatsnew/fragments/7453.bugfix
@@ -0,0 +1,3 @@
+Fixed a crash in the ``unhashable-member`` checker when using a ``lambda`` as a dict key.
+
+Closes #7453
diff --git a/doc/whatsnew/fragments/7457.bugfix b/doc/whatsnew/fragments/7457.bugfix
new file mode 100644
index 000000000..31b48d257
--- /dev/null
+++ b/doc/whatsnew/fragments/7457.bugfix
@@ -0,0 +1,3 @@
+Add `mailcap` to deprecated modules list.
+
+Closes #7457
diff --git a/doc/whatsnew/fragments/7461.bugfix b/doc/whatsnew/fragments/7461.bugfix
new file mode 100644
index 000000000..1fb503c9a
--- /dev/null
+++ b/doc/whatsnew/fragments/7461.bugfix
@@ -0,0 +1,3 @@
+Fix a crash in the ``modified-iterating-dict`` checker involving instance attributes.
+
+Closes #7461
diff --git a/doc/whatsnew/fragments/7463.other b/doc/whatsnew/fragments/7463.other
new file mode 100644
index 000000000..5af1ed640
--- /dev/null
+++ b/doc/whatsnew/fragments/7463.other
@@ -0,0 +1,3 @@
+Relevant `DeprecationWarnings` are now raised with `stacklevel=2`, so they have the callsite attached in the message.
+
+Closes #7463
diff --git a/doc/whatsnew/fragments/7467.bugfix b/doc/whatsnew/fragments/7467.bugfix
new file mode 100644
index 000000000..7e76f86a0
--- /dev/null
+++ b/doc/whatsnew/fragments/7467.bugfix
@@ -0,0 +1,3 @@
+``invalid-class-object`` does not crash anymore when ``__class__`` is assigned alongside another variable.
+
+Closes #7467
diff --git a/doc/whatsnew/fragments/7471.bugfix b/doc/whatsnew/fragments/7471.bugfix
new file mode 100644
index 000000000..b1b6f369c
--- /dev/null
+++ b/doc/whatsnew/fragments/7471.bugfix
@@ -0,0 +1,3 @@
+``--help-msg`` now accepts a comma-separated list of message IDs again.
+
+Closes #7471
diff --git a/doc/whatsnew/fragments/7485.other b/doc/whatsnew/fragments/7485.other
new file mode 100644
index 000000000..e2f3d36d3
--- /dev/null
+++ b/doc/whatsnew/fragments/7485.other
@@ -0,0 +1,3 @@
+Add a ``minimal`` option to ``pylint-config`` and its toml generator.
+
+Closes #7485
diff --git a/doc/whatsnew/fragments/7494.new_check b/doc/whatsnew/fragments/7494.new_check
new file mode 100644
index 000000000..fe62e1483
--- /dev/null
+++ b/doc/whatsnew/fragments/7494.new_check
@@ -0,0 +1,4 @@
+Rename ``broad-except`` to ``broad-exception-caught`` and add new checker ``broad-exception-raised``
+which will warn if general exceptions ``BaseException`` or ``Exception`` are raised.
+
+Closes #7494
diff --git a/doc/whatsnew/fragments/7495.bugfix b/doc/whatsnew/fragments/7495.bugfix
new file mode 100644
index 000000000..cec2bcf53
--- /dev/null
+++ b/doc/whatsnew/fragments/7495.bugfix
@@ -0,0 +1,4 @@
+Allow specifying non-builtin exceptions in the ``overgeneral-exception`` option
+using an exception's qualified name.
+
+Closes #7495
diff --git a/doc/whatsnew/fragments/7501.false_positive b/doc/whatsnew/fragments/7501.false_positive
new file mode 100644
index 000000000..0c2d33a07
--- /dev/null
+++ b/doc/whatsnew/fragments/7501.false_positive
@@ -0,0 +1,3 @@
+Fix false positive for ``unhashable-member`` when subclassing ``dict`` and using the subclass as a dictionary key.
+
+Closes #7501
diff --git a/doc/whatsnew/fragments/7507.bugfix b/doc/whatsnew/fragments/7507.bugfix
new file mode 100644
index 000000000..5ce05c658
--- /dev/null
+++ b/doc/whatsnew/fragments/7507.bugfix
@@ -0,0 +1,4 @@
+Report ``no-self-argument`` rather than ``no-method-argument`` for methods
+with variadic arguments.
+
+Closes #7507
diff --git a/doc/whatsnew/fragments/7507.other b/doc/whatsnew/fragments/7507.other
new file mode 100644
index 000000000..3cdca7465
--- /dev/null
+++ b/doc/whatsnew/fragments/7507.other
@@ -0,0 +1,3 @@
+Add method name to the error messages of ``no-method-argument`` and ``no-self-argument``.
+
+Closes #7507
diff --git a/doc/whatsnew/fragments/7522.bugfix b/doc/whatsnew/fragments/7522.bugfix
new file mode 100644
index 000000000..f4fa9da1a
--- /dev/null
+++ b/doc/whatsnew/fragments/7522.bugfix
@@ -0,0 +1,3 @@
+Fixed an issue where ``syntax-error`` couldn't be raised on files with invalid encodings.
+
+Closes #7522
diff --git a/doc/whatsnew/fragments/7524.bugfix b/doc/whatsnew/fragments/7524.bugfix
new file mode 100644
index 000000000..8a9c5fc79
--- /dev/null
+++ b/doc/whatsnew/fragments/7524.bugfix
@@ -0,0 +1,4 @@
+Fix false positive for ``redefined-outer-name`` when aliasing ``typing``
+e.g. as ``t`` and guarding imports under ``t.TYPE_CHECKING``.
+
+Closes #7524
diff --git a/doc/whatsnew/fragments/7528.bugfix b/doc/whatsnew/fragments/7528.bugfix
new file mode 100644
index 000000000..b06bf1570
--- /dev/null
+++ b/doc/whatsnew/fragments/7528.bugfix
@@ -0,0 +1,3 @@
+Fixed a crash of the ``modified_iterating`` checker when iterating on a set defined as a class attribute.
+
+Closes #7528
diff --git a/doc/whatsnew/fragments/7529.false_positive b/doc/whatsnew/fragments/7529.false_positive
new file mode 100644
index 000000000..7775d9086
--- /dev/null
+++ b/doc/whatsnew/fragments/7529.false_positive
@@ -0,0 +1,4 @@
+Fix the message for ``unnecessary-dunder-call`` for ``__aiter__`` and ``__aneext__``. Also
+only emit the warning when ``py-version`` >= 3.10.
+
+Closes #7529
diff --git a/doc/whatsnew/fragments/7544.other b/doc/whatsnew/fragments/7544.other
new file mode 100644
index 000000000..b868f1265
--- /dev/null
+++ b/doc/whatsnew/fragments/7544.other
@@ -0,0 +1,3 @@
+Prevent leaving the pip install cache in the Docker image.
+
+Refs #7544
diff --git a/doc/whatsnew/fragments/7546.new_check b/doc/whatsnew/fragments/7546.new_check
new file mode 100644
index 000000000..5b1f5a327
--- /dev/null
+++ b/doc/whatsnew/fragments/7546.new_check
@@ -0,0 +1,4 @@
+Added ``nested-min-max`` which flags ``min(1, min(2, 3))`` to simplify to
+``min(1, 2, 3)``.
+
+Closes #7546
diff --git a/doc/whatsnew/fragments/7547.false_negative b/doc/whatsnew/fragments/7547.false_negative
new file mode 100644
index 000000000..97abdf100
--- /dev/null
+++ b/doc/whatsnew/fragments/7547.false_negative
@@ -0,0 +1,3 @@
+Fix a false negative for ``unused-import`` when a constant inside ``typing.Annotated`` was treated as a reference to an import.
+
+Closes #7547
diff --git a/doc/whatsnew/fragments/7563.false_positive b/doc/whatsnew/fragments/7563.false_positive
new file mode 100644
index 000000000..1a3252f35
--- /dev/null
+++ b/doc/whatsnew/fragments/7563.false_positive
@@ -0,0 +1,3 @@
+Fix ``used-before-assignment`` false positive when else branch calls ``sys.exit`` or similar terminating functions.
+
+Closes #7563
diff --git a/doc/whatsnew/fragments/7569.bugfix b/doc/whatsnew/fragments/7569.bugfix
new file mode 100644
index 000000000..c6a84fc64
--- /dev/null
+++ b/doc/whatsnew/fragments/7569.bugfix
@@ -0,0 +1,3 @@
+Use ``py-version`` to determine if a message should be emitted for messages defined with ``max-version`` or ``min-version``.
+
+Closes #7569
diff --git a/doc/whatsnew/fragments/7570.bugfix b/doc/whatsnew/fragments/7570.bugfix
new file mode 100644
index 000000000..13cbc2916
--- /dev/null
+++ b/doc/whatsnew/fragments/7570.bugfix
@@ -0,0 +1,4 @@
+Improve ``bad-thread-instantiation`` check to warn if ``target`` is not passed in as a keyword argument
+or as a second argument.
+
+Closes #7570
diff --git a/doc/whatsnew/fragments/7609.false_positive b/doc/whatsnew/fragments/7609.false_positive
new file mode 100644
index 000000000..5c91f396e
--- /dev/null
+++ b/doc/whatsnew/fragments/7609.false_positive
@@ -0,0 +1,4 @@
+Fix a false positive for ``used-before-assignment`` for imports guarded by
+``typing.TYPE_CHECKING`` later used in variable annotations.
+
+Closes #7609
diff --git a/doc/whatsnew/fragments/7610.bugfix b/doc/whatsnew/fragments/7610.bugfix
new file mode 100644
index 000000000..3eb49fcbb
--- /dev/null
+++ b/doc/whatsnew/fragments/7610.bugfix
@@ -0,0 +1,3 @@
+Fixes edge case of custom method named ``next`` raised an astroid error.
+
+Closes #7610
diff --git a/doc/whatsnew/fragments/7626.false_positive b/doc/whatsnew/fragments/7626.false_positive
new file mode 100644
index 000000000..bfa56c107
--- /dev/null
+++ b/doc/whatsnew/fragments/7626.false_positive
@@ -0,0 +1,4 @@
+Fix a false positive for ``simplify-boolean-expression`` when multiple values
+are inferred for a constant.
+
+Closes #7626
diff --git a/doc/whatsnew/fragments/7626.other b/doc/whatsnew/fragments/7626.other
new file mode 100644
index 000000000..8020fc83f
--- /dev/null
+++ b/doc/whatsnew/fragments/7626.other
@@ -0,0 +1,3 @@
+Add a keyword-only ``compare_constants`` argument to ``safe_infer``.
+
+Refs #7626
diff --git a/doc/whatsnew/fragments/7629.other b/doc/whatsnew/fragments/7629.other
new file mode 100644
index 000000000..bac156ee5
--- /dev/null
+++ b/doc/whatsnew/fragments/7629.other
@@ -0,0 +1,4 @@
+Add ``default_enabled`` option to optional message dict. Provides an option to disable a checker message by default.
+To use a disabled message, the user must enable it explicitly by adding the message to the ``enable`` option.
+
+Refs #7629
diff --git a/doc/whatsnew/fragments/7635.bugfix b/doc/whatsnew/fragments/7635.bugfix
new file mode 100644
index 000000000..72085e029
--- /dev/null
+++ b/doc/whatsnew/fragments/7635.bugfix
@@ -0,0 +1,7 @@
+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.
diff --git a/doc/whatsnew/fragments/7655.other b/doc/whatsnew/fragments/7655.other
new file mode 100644
index 000000000..9024fe38e
--- /dev/null
+++ b/doc/whatsnew/fragments/7655.other
@@ -0,0 +1,3 @@
+Sort ``--generated-rcfile`` output.
+
+Refs #7655
diff --git a/doc/whatsnew/fragments/7661.bugfix b/doc/whatsnew/fragments/7661.bugfix
new file mode 100644
index 000000000..2e58c861b
--- /dev/null
+++ b/doc/whatsnew/fragments/7661.bugfix
@@ -0,0 +1,3 @@
+Fix crash that happened when parsing files with unexpected encoding starting with 'utf' like ``utf13``.
+
+Closes #7661
diff --git a/doc/whatsnew/fragments/7682.false_positive b/doc/whatsnew/fragments/7682.false_positive
new file mode 100644
index 000000000..3f94f447f
--- /dev/null
+++ b/doc/whatsnew/fragments/7682.false_positive
@@ -0,0 +1,3 @@
+``unnecessary-list-index-lookup`` will not be wrongly emitted if ``enumerate`` is called with ``start``.
+
+Closes #7682
diff --git a/doc/whatsnew/fragments/7690.new_check b/doc/whatsnew/fragments/7690.new_check
new file mode 100644
index 000000000..2969428ed
--- /dev/null
+++ b/doc/whatsnew/fragments/7690.new_check
@@ -0,0 +1,3 @@
+Extended ``use-dict-literal`` to also warn about call to ``dict()`` when passing keyword arguments.
+
+Closes #7690
diff --git a/doc/whatsnew/fragments/7699.false_negative b/doc/whatsnew/fragments/7699.false_negative
new file mode 100644
index 000000000..128aeff29
--- /dev/null
+++ b/doc/whatsnew/fragments/7699.false_negative
@@ -0,0 +1,3 @@
+``consider-using-any-or-all`` message will now be raised in cases when boolean is initialized, reassigned during loop, and immediately returned.
+
+Closes #7699
diff --git a/doc/whatsnew/fragments/7737.other b/doc/whatsnew/fragments/7737.other
new file mode 100644
index 000000000..7b7988759
--- /dev/null
+++ b/doc/whatsnew/fragments/7737.other
@@ -0,0 +1,5 @@
+epylint is now deprecated and will be removed in pylint 3.0.0. All emacs and flymake related
+files were removed and their support will now happen in an external repository :
+https://github.com/emacsorphanage/pylint.
+
+Closes #7737
diff --git a/doc/whatsnew/fragments/7742.bugfix b/doc/whatsnew/fragments/7742.bugfix
new file mode 100644
index 000000000..7e3c93089
--- /dev/null
+++ b/doc/whatsnew/fragments/7742.bugfix
@@ -0,0 +1,3 @@
+Fix a crash when a child class with an ``__init__`` method inherits from a parent class with an ``__init__`` class attribute.
+
+Closes #7742
diff --git a/doc/whatsnew/fragments/7760.new_check b/doc/whatsnew/fragments/7760.new_check
new file mode 100644
index 000000000..89cdf29f9
--- /dev/null
+++ b/doc/whatsnew/fragments/7760.new_check
@@ -0,0 +1,5 @@
+Add ``named-expr-without-context`` check to emit a warning if a named
+expression is used outside a context like ``if``, ``for``, ``while``, or
+a comprehension.
+
+Refs #7760
diff --git a/doc/whatsnew/fragments/7762.false_negative b/doc/whatsnew/fragments/7762.false_negative
new file mode 100644
index 000000000..6b4083ff6
--- /dev/null
+++ b/doc/whatsnew/fragments/7762.false_negative
@@ -0,0 +1,4 @@
+Extend ``invalid-slice-index`` to emit an warning for invalid slice indices
+used with string and byte sequences, and range objects.
+
+Refs #7762
diff --git a/doc/whatsnew/fragments/7762.new_check b/doc/whatsnew/fragments/7762.new_check
new file mode 100644
index 000000000..4ff67326b
--- /dev/null
+++ b/doc/whatsnew/fragments/7762.new_check
@@ -0,0 +1,4 @@
+Add ``invalid-slice-step`` check to warn about a slice step value of ``0``
+for common builtin sequences.
+
+Refs #7762
diff --git a/doc/whatsnew/fragments/7765.false_positive b/doc/whatsnew/fragments/7765.false_positive
new file mode 100644
index 000000000..de7c44c5a
--- /dev/null
+++ b/doc/whatsnew/fragments/7765.false_positive
@@ -0,0 +1,3 @@
+Don't warn about ``stop-iteration-return`` when using ``next()`` over ``itertools.cycle``.
+
+Closes #7765
diff --git a/doc/whatsnew/fragments/7770.false_negative b/doc/whatsnew/fragments/7770.false_negative
new file mode 100644
index 000000000..d9a165390
--- /dev/null
+++ b/doc/whatsnew/fragments/7770.false_negative
@@ -0,0 +1,3 @@
+Fixes ``unnecessary-list-index-lookup`` false negative when ``enumerate`` is called with ``iterable`` as a kwarg.
+
+Closes #7770
diff --git a/doc/whatsnew/fragments/7782.bugfix b/doc/whatsnew/fragments/7782.bugfix
new file mode 100644
index 000000000..e4c1498c4
--- /dev/null
+++ b/doc/whatsnew/fragments/7782.bugfix
@@ -0,0 +1,4 @@
+Fix ``valid-metaclass-classmethod-first-arg`` default config value from "cls" to "mcs"
+which would cause both a false-positive and false-negative.
+
+Closes #7782
diff --git a/doc/whatsnew/fragments/7818.false_negative b/doc/whatsnew/fragments/7818.false_negative
new file mode 100644
index 000000000..f1fe76ded
--- /dev/null
+++ b/doc/whatsnew/fragments/7818.false_negative
@@ -0,0 +1,3 @@
+Fix ``dangerous-default-value`` false negative when `*` is used.
+
+Closes #7818
diff --git a/doc/whatsnew/fragments/7821.bugfix b/doc/whatsnew/fragments/7821.bugfix
new file mode 100644
index 000000000..af48814db
--- /dev/null
+++ b/doc/whatsnew/fragments/7821.bugfix
@@ -0,0 +1,3 @@
+Fixes a crash in the ``unnecessary_list_index_lookup`` check when using ``enumerate`` with ``start`` and a class attribute.
+
+Closes #7821
diff --git a/doc/whatsnew/fragments/7828.bugfix b/doc/whatsnew/fragments/7828.bugfix
new file mode 100644
index 000000000..f47cc3cc9
--- /dev/null
+++ b/doc/whatsnew/fragments/7828.bugfix
@@ -0,0 +1,3 @@
+Fixes a crash in ``stop-iteration-return`` when the ``next`` builtin is called without arguments.
+
+Closes #7828
diff --git a/doc/whatsnew/fragments/7860.false_positive b/doc/whatsnew/fragments/7860.false_positive
new file mode 100644
index 000000000..c76425c54
--- /dev/null
+++ b/doc/whatsnew/fragments/7860.false_positive
@@ -0,0 +1,3 @@
+``multiple-statements`` no longer triggers for function stubs using inlined ``...``.
+
+Closes #7860
diff --git a/elisp/pylint-flymake.el b/elisp/pylint-flymake.el
deleted file mode 100644
index ed213bf46..000000000
--- a/elisp/pylint-flymake.el
+++ /dev/null
@@ -1,15 +0,0 @@
-;; Configure Flymake for python
-(when (load "flymake" t)
- (defun flymake-pylint-init ()
- (let* ((temp-file (flymake-init-create-temp-buffer-copy
- 'flymake-create-temp-inplace))
- (local-file (file-relative-name
- temp-file
- (file-name-directory buffer-file-name))))
- (list "epylint" (list local-file))))
-
- (add-to-list 'flymake-allowed-file-name-masks
- '("\\.py\\'" flymake-pylint-init)))
-
-;; Set as a minor mode for python
-(add-hook 'python-mode-hook '(lambda () (flymake-mode)))
diff --git a/elisp/pylint.el b/elisp/pylint.el
deleted file mode 100644
index 327da0fcb..000000000
--- a/elisp/pylint.el
+++ /dev/null
@@ -1,255 +0,0 @@
-;;; pylint.el --- minor mode for running `pylint'
-
-;; Copyright (c) 2009, 2010 Ian Eure <ian.eure@gmail.com>
-;; Author: Ian Eure <ian.eure@gmail.com>
-;; Maintainer: Jonathan Kotta <jpkotta@gmail.com>
-
-;; Keywords: languages python
-;; Version: 1.02
-
-;; pylint.el is free software; you can redistribute it and/or modify it
-;; under the terms of the GNU General Public License as published by the Free
-;; Software Foundation; either version 2, or (at your option) any later
-;; version.
-;;
-;; It is distributed in the hope that it will be useful, but WITHOUT ANY
-;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-;; details.
-;;
-;; You should have received a copy of the GNU General Public License along
-;; with your copy of Emacs; see the file COPYING. If not, write to the Free
-;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-;; MA 02110-1301, USA
-
-;;; Commentary:
-;;
-;; Specialized compile mode for pylint. You may want to add the
-;; following to your init.el:
-;;
-;; (autoload 'pylint "pylint")
-;; (add-hook 'python-mode-hook 'pylint-add-menu-items)
-;; (add-hook 'python-mode-hook 'pylint-add-key-bindings)
-;;
-;; There is also a handy command `pylint-insert-ignore-comment' that
-;; makes it easy to insert comments of the form `# pylint:
-;; ignore=msg1,msg2,...'.
-
-;;; Code:
-
-(require 'compile)
-(require 'tramp)
-
-(defgroup pylint nil
- "Minor mode for running the Pylint Python checker"
- :prefix "pylint-"
- :group 'tools
- :group 'languages)
-
-(defvar pylint-last-buffer nil
- "The most recent PYLINT buffer.
-A PYLINT buffer becomes most recent when you select PYLINT mode in it.
-Notice that using \\[next-error] or \\[compile-goto-error] modifies
-`completion-last-buffer' rather than `pylint-last-buffer'.")
-
-(defconst pylint-regexp-alist
- (let ((base "^\\(.*\\):\\([0-9]+\\):\s+\\(\\[%s.*\\)$"))
- (list
- (list (format base "[FE]") 1 2)
- (list (format base "[RWC]") 1 2 nil 1)))
- "Regexp used to match PYLINT hits. See `compilation-error-regexp-alist'.")
-
-(defcustom pylint-options '("--reports=n" "--output-format=parseable")
- "Options to pass to pylint.py"
- :type '(repeat string)
- :group 'pylint)
-
-(defcustom pylint-use-python-indent-offset nil
- "If non-nil, use `python-indent-offset' to set indent-string."
- :type 'boolean
- :group 'pylint)
-
-(defcustom pylint-command "pylint"
- "PYLINT command."
- :type '(file)
- :group 'pylint)
-
-(defcustom pylint-alternate-pylint-command "pylint2"
- "Command for pylint when invoked with C-u."
- :type '(file)
- :group 'pylint)
-
-(defcustom pylint-ask-about-save nil
- "Non-nil means \\[pylint] asks which buffers to save before compiling.
-Otherwise, it saves all modified buffers without asking."
- :type 'boolean
- :group 'pylint)
-
-(defvar pylint--messages-list ()
- "A list of strings of all pylint messages.")
-
-(defvar pylint--messages-list-hist ()
- "Completion history for `pylint--messages-list'.")
-
-(defun pylint--sort-messages (a b)
- "Compare function for sorting `pylint--messages-list'.
-
-Sorts most recently used elements first using `pylint--messages-list-hist'."
- (let ((idx 0)
- (a-idx most-positive-fixnum)
- (b-idx most-positive-fixnum))
- (dolist (e pylint--messages-list-hist)
- (when (string= e a)
- (setq a-idx idx))
- (when (string= e b)
- (setq b-idx idx))
- (setq idx (1+ idx)))
- (< a-idx b-idx)))
-
-(defun pylint--create-messages-list ()
- "Use `pylint-command' to populate `pylint--messages-list'."
- ;; example output:
- ;; |--we want this--|
- ;; v v
- ;; :using-cmp-argument (W1640): *Using the cmp argument for list.sort / sorted*
- ;; Using the cmp argument for list.sort or the sorted builtin should be avoided,
- ;; since it was removed in Python 3. Using either `key` or `functools.cmp_to_key`
- ;; should be preferred. This message can't be emitted when using Python >= 3.0.
- (setq pylint--messages-list
- (split-string
- (with-temp-buffer
- (shell-command (concat pylint-command " --list-msgs") (current-buffer))
- (flush-lines "^[^:]")
- (goto-char (point-min))
- (while (not (eobp))
- (delete-char 1) ;; delete ";"
- (re-search-forward " ")
- (delete-region (point) (line-end-position))
- (forward-line 1))
- (buffer-substring-no-properties (point-min) (point-max))))))
-
-;;;###autoload
-(defun pylint-insert-ignore-comment (&optional arg)
- "Insert a comment like \"# pylint: disable=msg1,msg2,...\".
-
-This command repeatedly uses `completing-read' to match known
-messages, and ultimately inserts a comma-separated list of all
-the selected messages.
-
-With prefix argument, only insert a comma-separated list (for
-appending to an existing list)."
- (interactive "*P")
- (unless pylint--messages-list
- (pylint--create-messages-list))
- (setq pylint--messages-list
- (sort pylint--messages-list #'pylint--sort-messages))
- (let ((msgs ())
- (msg "")
- (prefix (if arg
- ","
- "# pylint: disable="))
- (sentinel "[DONE]"))
- (while (progn
- (setq msg (completing-read
- "Message: "
- pylint--messages-list
- nil t nil 'pylint--messages-list-hist sentinel))
- (unless (string= sentinel msg)
- (add-to-list 'msgs msg 'append))))
- (setq pylint--messages-list-hist
- (delete sentinel pylint--messages-list-hist))
- (insert prefix (mapconcat 'identity msgs ","))))
-
-(define-compilation-mode pylint-mode "PYLINT"
- (setq pylint-last-buffer (current-buffer))
- (set (make-local-variable 'compilation-error-regexp-alist)
- pylint-regexp-alist)
- (set (make-local-variable 'compilation-disable-input) t))
-
-(defvar pylint-mode-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map compilation-minor-mode-map)
- (define-key map " " 'scroll-up)
- (define-key map "\^?" 'scroll-down)
- (define-key map "\C-c\C-f" 'next-error-follow-minor-mode)
-
- (define-key map "\r" 'compile-goto-error) ;; ?
- (define-key map "n" 'next-error-no-select)
- (define-key map "p" 'previous-error-no-select)
- (define-key map "{" 'compilation-previous-file)
- (define-key map "}" 'compilation-next-file)
- (define-key map "\t" 'compilation-next-error)
- (define-key map [backtab] 'compilation-previous-error)
- map)
- "Keymap for PYLINT buffers.
-`compilation-minor-mode-map' is a cdr of this.")
-
-(defun pylint--make-indent-string ()
- "Make a string for the `--indent-string' option."
- (format "--indent-string='%s'"
- (make-string python-indent-offset ?\ )))
-
-;;;###autoload
-(defun pylint (&optional arg)
- "Run PYLINT, and collect output in a buffer, much like `compile'.
-
-While pylint runs asynchronously, you can use \\[next-error] (M-x next-error),
-or \\<pylint-mode-map>\\[compile-goto-error] in the grep \
-output buffer, to go to the lines where pylint found matches.
-
-\\{pylint-mode-map}"
- (interactive "P")
-
- (save-some-buffers (not pylint-ask-about-save) nil)
- (let* ((filename (buffer-file-name))
- (localname-offset (cl-struct-slot-offset 'tramp-file-name 'localname))
- (filename (or (and (tramp-tramp-file-p filename)
- (elt (tramp-dissect-file-name filename) localname-offset))
- filename))
- (filename (shell-quote-argument filename))
- (pylint-command (if arg
- pylint-alternate-pylint-command
- pylint-command))
- (pylint-options (if (not pylint-use-python-indent-offset)
- pylint-options
- (append pylint-options
- (list (pylint--make-indent-string)))))
- (command (mapconcat
- 'identity
- (append `(,pylint-command) pylint-options `(,filename))
- " ")))
-
- (compilation-start command 'pylint-mode)))
-
-;;;###autoload
-(defun pylint-add-key-bindings ()
- (let ((map (cond
- ((boundp 'py-mode-map) py-mode-map)
- ((boundp 'python-mode-map) python-mode-map))))
-
- ;; shortcuts in the tradition of python-mode and ropemacs
- (define-key map (kbd "C-c m l") 'pylint)
- (define-key map (kbd "C-c m p") 'previous-error)
- (define-key map (kbd "C-c m n") 'next-error)
- (define-key map (kbd "C-c m i") 'pylint-insert-ignore-comment)
- nil))
-
-;;;###autoload
-(defun pylint-add-menu-items ()
- (let ((map (cond
- ((boundp 'py-mode-map) py-mode-map)
- ((boundp 'python-mode-map) python-mode-map))))
-
- (define-key map [menu-bar Python pylint-separator]
- '("--" . pylint-separator))
- (define-key map [menu-bar Python next-error]
- '("Next error" . next-error))
- (define-key map [menu-bar Python prev-error]
- '("Previous error" . previous-error))
- (define-key map [menu-bar Python lint]
- '("Pylint" . pylint))
- nil))
-
-(provide 'pylint)
-
-;;; pylint.el ends here
diff --git a/elisp/startup b/elisp/startup
deleted file mode 100644
index 2f8fed1d4..000000000
--- a/elisp/startup
+++ /dev/null
@@ -1,17 +0,0 @@
-;; -*-emacs-lisp-*-
-;;
-;; Emacs startup file for the Debian GNU/Linux %PACKAGE% package
-;;
-;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at>
-;; Modified by Dirk Eddelbuettel <edd@debian.org>
-;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com>
-
-;; The %PACKAGE% package follows the Debian/GNU Linux 'emacsen' policy and
-;; byte-compiles its elisp files for each 'Emacs flavor' (emacs19,
-;; xemacs19, emacs20, xemacs20...). The compiled code is then
-;; installed in a subdirectory of the respective site-lisp directory.
-;; We have to add this to the load-path:
-(setq load-path (cons (concat "/usr/share/"
- (symbol-name debian-emacs-flavor)
- "/site-lisp/%PACKAGE%") load-path))
-(load-library "pylint")
diff --git a/examples/pylintrc b/examples/pylintrc
index a461b24d5..61a9361d6 100644
--- a/examples/pylintrc
+++ b/examples/pylintrc
@@ -260,7 +260,7 @@ exclude-protected=_asdict,
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=cls
+valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
@@ -307,8 +307,8 @@ min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
-overgeneral-exceptions=BaseException,
- Exception
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception
[FORMAT]
diff --git a/examples/pyproject.toml b/examples/pyproject.toml
index b419f83e7..98cb39bb9 100644
--- a/examples/pyproject.toml
+++ b/examples/pyproject.toml
@@ -222,7 +222,7 @@ exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make"]
valid-classmethod-first-arg = ["cls"]
# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg = ["cls"]
+valid-metaclass-classmethod-first-arg = ["mcs"]
[tool.pylint.design]
# List of regular expressions of class ancestor names to ignore when counting
@@ -264,7 +264,7 @@ min-public-methods = 2
[tool.pylint.exceptions]
# Exceptions that will emit a warning when caught.
-overgeneral-exceptions = ["BaseException", "Exception"]
+overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
[tool.pylint.format]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
diff --git a/pylint/__init__.py b/pylint/__init__.py
index 8f1eaebe0..2cc7edadb 100644
--- a/pylint/__init__.py
+++ b/pylint/__init__.py
@@ -16,6 +16,7 @@ __all__ = [
import os
import sys
+import warnings
from collections.abc import Sequence
from typing import NoReturn
@@ -54,10 +55,16 @@ def run_epylint(argv: Sequence[str] | None = None) -> NoReturn:
"""
from pylint.epylint import Run as EpylintRun
+ warnings.warn(
+ "'run_epylint' will be removed in pylint 3.0, use "
+ "https://github.com/emacsorphanage/pylint instead.",
+ DeprecationWarning,
+ stacklevel=1,
+ )
EpylintRun(argv)
-def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn: # type: ignore[misc]
+def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn:
"""Run pyreverse.
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 3b7b44c37..7d767f3a1 100644
--- a/pylint/__pkginfo__.py
+++ b/pylint/__pkginfo__.py
@@ -9,7 +9,7 @@ It's updated via tbump, do not modify.
from __future__ import annotations
-__version__ = "2.15.7"
+__version__ = "2.16.0-dev"
def get_numversion_from_version(v: str) -> tuple[int, int, int]:
diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py
index 7e39c6877..ed641d8e5 100644
--- a/pylint/checkers/__init__.py
+++ b/pylint/checkers/__init__.py
@@ -127,7 +127,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((value[0].replace("_", " "), new_str, old_str, diff_str))
+ lines.extend((value[0].replace("_", " "), new_str, old_str, diff_str)) # type: ignore[arg-type]
return lines
diff --git a/pylint/checkers/base/basic_checker.py b/pylint/checkers/base/basic_checker.py
index 4594bbaa8..a91291c91 100644
--- a/pylint/checkers/base/basic_checker.py
+++ b/pylint/checkers/base/basic_checker.py
@@ -17,7 +17,7 @@ from astroid import nodes
from pylint import utils as lint_utils
from pylint.checkers import BaseChecker, utils
-from pylint.interfaces import HIGH, INFERENCE
+from pylint.interfaces import HIGH, INFERENCE, Confidence
from pylint.reporters.ureports import nodes as reporter_nodes
from pylint.utils import LinterStats
@@ -188,7 +188,7 @@ class BasicChecker(_BasicChecker):
"re-raised.",
),
"W0199": (
- "Assert called on a 2-item-tuple. Did you mean 'assert x,y'?",
+ "Assert called on a populated tuple. Did you mean 'assert x,y'?",
"assert-on-tuple",
"A call of assert on a tuple will always evaluate to true if "
"the tuple is not empty, and will always evaluate to false if "
@@ -252,6 +252,12 @@ class BasicChecker(_BasicChecker):
"duplicate-value",
"This message is emitted when a set contains the same value two or more times.",
),
+ "W0131": (
+ "Named expression used without context",
+ "named-expr-without-context",
+ "Emitted if named expression is used to do a regular assignment "
+ "outside a context like if, for, while, or a comprehension.",
+ ),
}
reports = (("RP0101", "Statistics by type", report_by_type_stats),)
@@ -410,7 +416,10 @@ class BasicChecker(_BasicChecker):
self.linter.stats.node_count["klass"] += 1
@utils.only_required_for_messages(
- "pointless-statement", "pointless-string-statement", "expression-not-assigned"
+ "pointless-statement",
+ "pointless-string-statement",
+ "expression-not-assigned",
+ "named-expr-without-context",
)
def visit_expr(self, node: nodes.Expr) -> None:
"""Check for various kind of statements without effect."""
@@ -448,7 +457,9 @@ class BasicChecker(_BasicChecker):
or (isinstance(expr, nodes.Const) and expr.value is Ellipsis)
):
return
- if any(expr.nodes_of_class(nodes.Call)):
+ if isinstance(expr, nodes.NamedExpr):
+ self.add_message("named-expr-without-context", node=node, confidence=HIGH)
+ elif any(expr.nodes_of_class(nodes.Call)):
self.add_message(
"expression-not-assigned", node=node, args=expr.as_string()
)
@@ -561,7 +572,7 @@ class BasicChecker(_BasicChecker):
def is_iterable(internal_node: nodes.NodeNG) -> bool:
return isinstance(internal_node, (nodes.List, nodes.Set, nodes.Dict))
- defaults = node.args.defaults or [] + node.args.kw_defaults or []
+ defaults = (node.args.defaults or []) + (node.args.kw_defaults or [])
for default in defaults:
if not default:
continue
@@ -658,12 +669,16 @@ class BasicChecker(_BasicChecker):
self.add_message("misplaced-format-function", node=call_node)
@utils.only_required_for_messages(
- "eval-used", "exec-used", "bad-reversed-sequence", "misplaced-format-function"
+ "eval-used",
+ "exec-used",
+ "bad-reversed-sequence",
+ "misplaced-format-function",
+ "unreachable",
)
def visit_call(self, node: nodes.Call) -> None:
- """Visit a Call node -> check if this is not a disallowed builtin
- call and check for * or ** use.
- """
+ """Visit a Call node."""
+ if utils.is_terminating_func(node):
+ self._check_unreachable(node, confidence=INFERENCE)
self._check_misplaced_format_function(node)
if isinstance(node.func, nodes.Name):
name = node.func.name
@@ -680,12 +695,8 @@ class BasicChecker(_BasicChecker):
@utils.only_required_for_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."""
- if (
- node.fail is None
- and isinstance(node.test, nodes.Tuple)
- and len(node.test.elts) == 2
- ):
- self.add_message("assert-on-tuple", node=node)
+ if isinstance(node.test, nodes.Tuple) and len(node.test.elts) > 0:
+ self.add_message("assert-on-tuple", node=node, confidence=HIGH)
if isinstance(node.test, nodes.Const) and isinstance(node.test.value, str):
if node.test.value:
@@ -735,7 +746,9 @@ class BasicChecker(_BasicChecker):
self._tryfinallys.pop()
def _check_unreachable(
- self, node: nodes.Return | nodes.Continue | nodes.Break | nodes.Raise
+ self,
+ node: nodes.Return | nodes.Continue | nodes.Break | nodes.Raise | nodes.Call,
+ confidence: Confidence = HIGH,
) -> None:
"""Check unreachable code."""
unreachable_statement = node.next_sibling()
@@ -750,7 +763,9 @@ class BasicChecker(_BasicChecker):
unreachable_statement = unreachable_statement.next_sibling()
if unreachable_statement is None:
return
- self.add_message("unreachable", node=unreachable_statement, confidence=HIGH)
+ self.add_message(
+ "unreachable", node=unreachable_statement, confidence=confidence
+ )
def _check_not_in_finally(
self,
diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py
index 3bd5e2ca6..f76c5aa8b 100644
--- a/pylint/checkers/base/basic_error_checker.py
+++ b/pylint/checkers/base/basic_error_checker.py
@@ -12,6 +12,7 @@ from typing import Any
import astroid
from astroid import nodes
+from astroid.typing import InferenceResult
from pylint.checkers import utils
from pylint.checkers.base.basic_checker import _BasicChecker
@@ -432,7 +433,9 @@ class BasicErrorChecker(_BasicChecker):
for inferred in infer_all(node.func):
self._check_inferred_class_is_abstract(inferred, node)
- def _check_inferred_class_is_abstract(self, inferred, node: nodes.Call):
+ def _check_inferred_class_is_abstract(
+ self, inferred: InferenceResult, node: nodes.Call
+ ) -> None:
if not isinstance(inferred, nodes.ClassDef):
return
diff --git a/pylint/checkers/base/comparison_checker.py b/pylint/checkers/base/comparison_checker.py
index 98b0ec258..ffbd27374 100644
--- a/pylint/checkers/base/comparison_checker.py
+++ b/pylint/checkers/base/comparison_checker.py
@@ -89,16 +89,10 @@ class ComparisonChecker(_BasicChecker):
checking_for_absence: bool = False,
) -> None:
"""Check if == or != is being used to compare a singleton value."""
- singleton_values = (True, False, None)
- def _is_singleton_const(node: nodes.NodeNG) -> bool:
- return isinstance(node, nodes.Const) and any(
- node.value is value for value in singleton_values
- )
-
- if _is_singleton_const(left_value):
+ if utils.is_singleton_const(left_value):
singleton, other_value = left_value.value, right_value
- elif _is_singleton_const(right_value):
+ elif utils.is_singleton_const(right_value):
singleton, other_value = right_value.value, left_value
else:
return
@@ -201,17 +195,24 @@ class ComparisonChecker(_BasicChecker):
is_const = isinstance(literal.value, (bytes, str, int, float))
if is_const or is_other_literal:
- bad = node.as_string()
- if "is not" in bad:
+ incorrect_node_str = node.as_string()
+ if "is not" in incorrect_node_str:
equal_or_not_equal = "!="
is_or_is_not = "is not"
else:
equal_or_not_equal = "=="
is_or_is_not = "is"
- good = bad.replace(is_or_is_not, equal_or_not_equal)
+ fixed_node_str = incorrect_node_str.replace(
+ is_or_is_not, equal_or_not_equal
+ )
self.add_message(
"literal-comparison",
- args=(bad, equal_or_not_equal, is_or_is_not, good),
+ args=(
+ incorrect_node_str,
+ equal_or_not_equal,
+ is_or_is_not,
+ fixed_node_str,
+ ),
node=node,
confidence=HIGH,
)
@@ -243,8 +244,8 @@ class ComparisonChecker(_BasicChecker):
suggestion = f"{left_operand} {operator} {right_operand}"
self.add_message("comparison-with-itself", node=node, args=(suggestion,))
- def _check_two_literals_being_compared(self, node: nodes.Compare) -> None:
- """Check if two literals are being compared; this is always a logical tautology."""
+ def _check_constants_comparison(self, node: nodes.Compare) -> None:
+ """When two constants are being compared it is always a logical tautology."""
left_operand = node.left
if not isinstance(left_operand, nodes.Const):
return
@@ -297,7 +298,7 @@ class ComparisonChecker(_BasicChecker):
self._check_callable_comparison(node)
self._check_logical_tautology(node)
self._check_unidiomatic_typecheck(node)
- self._check_two_literals_being_compared(node)
+ self._check_constants_comparison(node)
# NOTE: this checker only works with binary comparisons like 'x == 42'
# but not 'x == y == 42'
if len(node.ops) != 1:
diff --git a/pylint/checkers/base/docstring_checker.py b/pylint/checkers/base/docstring_checker.py
index d40f892de..791b085b5 100644
--- a/pylint/checkers/base/docstring_checker.py
+++ b/pylint/checkers/base/docstring_checker.py
@@ -108,16 +108,16 @@ class DocStringChecker(_BasicChecker):
def open(self) -> None:
self.linter.stats.reset_undocumented()
- @utils.only_required_for_messages("missing-docstring", "empty-docstring")
+ @utils.only_required_for_messages("missing-module-docstring", "empty-docstring")
def visit_module(self, node: nodes.Module) -> None:
self._check_docstring("module", node)
- @utils.only_required_for_messages("missing-docstring", "empty-docstring")
+ @utils.only_required_for_messages("missing-class-docstring", "empty-docstring")
def visit_classdef(self, node: nodes.ClassDef) -> None:
if self.linter.config.no_docstring_rgx.match(node.name) is None:
self._check_docstring("class", node)
- @utils.only_required_for_messages("missing-docstring", "empty-docstring")
+ @utils.only_required_for_messages("missing-function-docstring", "empty-docstring")
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
if self.linter.config.no_docstring_rgx.match(node.name) is None:
ftype = "method" if node.is_method() else "function"
diff --git a/pylint/checkers/base/name_checker/checker.py b/pylint/checkers/base/name_checker/checker.py
index e4b061a17..0eded5f9b 100644
--- a/pylint/checkers/base/name_checker/checker.py
+++ b/pylint/checkers/base/name_checker/checker.py
@@ -337,7 +337,7 @@ class NameChecker(_BasicChecker):
if len(groups[min_warnings]) > 1:
by_line = sorted(
groups[min_warnings],
- key=lambda group: min(
+ key=lambda group: min( # type: ignore[no-any-return]
warning[0].lineno
for warning in group
if warning[0].lineno is not None
diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py
index 778345de8..0debfd3a2 100644
--- a/pylint/checkers/base_checker.py
+++ b/pylint/checkers/base_checker.py
@@ -7,7 +7,7 @@ from __future__ import annotations
import abc
import functools
import warnings
-from collections.abc import Iterator
+from collections.abc import Iterable, Sequence
from inspect import cleandoc
from tokenize import TokenInfo
from typing import TYPE_CHECKING, Any
@@ -54,6 +54,7 @@ class BaseChecker(_ArgumentsProvider):
"longer supported. Child classes should only inherit BaseChecker or any "
"of the other checker types from pylint.checkers.",
DeprecationWarning,
+ stacklevel=2,
)
if self.name is not None:
self.name = self.name.lower()
@@ -105,8 +106,8 @@ class BaseChecker(_ArgumentsProvider):
def get_full_documentation(
self,
msgs: dict[str, MessageDefinitionTuple],
- options: Iterator[tuple[str, OptionDict, Any]],
- reports: tuple[tuple[str, str, ReportsCallable], ...],
+ options: Iterable[tuple[str, OptionDict, Any]],
+ reports: Sequence[tuple[str, str, ReportsCallable]],
doc: str | None = None,
module: str | None = None,
show_options: bool = True,
diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py
index eb157187e..e2806ef43 100644
--- a/pylint/checkers/classes/class_checker.py
+++ b/pylint/checkers/classes/class_checker.py
@@ -9,10 +9,10 @@ from __future__ import annotations
import collections
import sys
from collections import defaultdict
-from collections.abc import Sequence
+from collections.abc import Callable, Sequence
from itertools import chain, zip_longest
from re import Pattern
-from typing import TYPE_CHECKING, Union
+from typing import TYPE_CHECKING, Any, Union
import astroid
from astroid import bases, nodes
@@ -200,7 +200,7 @@ def _positional_parameters(method: nodes.FunctionDef) -> list[nodes.AssignName]:
positional = method.args.args
if method.is_bound() and method.type in {"classmethod", "method"}:
positional = positional[1:]
- return positional
+ return positional # type: ignore[no-any-return]
class _DefaultMissing:
@@ -244,7 +244,9 @@ def _has_different_parameters_default_value(
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)
+ is_same_fn: Callable[[Any, Any], bool] | None = 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
@@ -257,7 +259,7 @@ def _has_different_parameters_default_value(
def _has_different_parameters(
original: list[nodes.AssignName],
overridden: list[nodes.AssignName],
- dummy_parameter_regex: Pattern,
+ dummy_parameter_regex: Pattern[str],
) -> list[str]:
result: list[str] = []
zipped = zip_longest(original, overridden)
@@ -311,7 +313,7 @@ def _has_different_keyword_only_parameters(
def _different_parameters(
original: nodes.FunctionDef,
overridden: nodes.FunctionDef,
- dummy_parameter_regex: Pattern,
+ dummy_parameter_regex: Pattern[str],
) -> list[str]:
"""Determine if the two methods have different parameters.
@@ -581,7 +583,7 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"implemented interface or in an overridden method.",
),
"W0223": (
- "Method %r is abstract in class %r but is not overridden",
+ "Method %r is abstract in class %r but is not overridden in child class %r",
"abstract-method",
"Used when an abstract method (i.e. raise NotImplementedError) is "
"not overridden in concrete class.",
@@ -682,7 +684,7 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"Used when a value in __slots__ conflicts with a class variable, property or method.",
),
"E0243": (
- "Invalid __class__ object",
+ "Invalid assignment to '__class__'. Should be a class definition but got a '%s'",
"invalid-class-object",
"Used when an invalid object is assigned to a __class__ property. "
"Only a class is permitted.",
@@ -790,7 +792,7 @@ a class method.",
(
"valid-metaclass-classmethod-first-arg",
{
- "default": ("cls",),
+ "default": ("mcs",),
"type": "csv",
"metavar": "<argument names>",
"help": "List of valid names for the first argument in \
@@ -839,7 +841,7 @@ a metaclass class method.",
@cached_property
def _dummy_rgx(self) -> Pattern[str]:
- return self.linter.config.dummy_variables_rgx
+ return self.linter.config.dummy_variables_rgx # type: ignore[no-any-return]
@only_required_for_messages(
"abstract-method",
@@ -1180,7 +1182,7 @@ a metaclass class method.",
continue
if not isinstance(parent_function, nodes.FunctionDef):
continue
- self._check_signature(node, parent_function, "overridden", klass)
+ self._check_signature(node, parent_function, klass)
self._check_invalid_overridden_method(node, parent_function)
break
@@ -1577,7 +1579,12 @@ a metaclass class method.",
):
# If is uninferable, we allow it to prevent false positives
return
- self.add_message("invalid-class-object", node=node)
+ self.add_message(
+ "invalid-class-object",
+ node=node,
+ args=inferred.__class__.__name__,
+ confidence=INFERENCE,
+ )
def _check_in_slots(self, node: nodes.AssignAttr) -> None:
"""Check that the given AssignAttr node
@@ -1814,7 +1821,7 @@ a metaclass class method.",
)
@staticmethod
- def _is_inferred_instance(expr, klass: nodes.ClassDef) -> bool:
+ def _is_inferred_instance(expr: nodes.NodeNG, klass: nodes.ClassDef) -> bool:
"""Check if the inferred value of the given *expr* is an instance of
*klass*.
"""
@@ -2005,7 +2012,7 @@ a metaclass class method.",
"""
def is_abstract(method: nodes.FunctionDef) -> bool:
- return method.is_abstract(pass_is_abstract=False)
+ return method.is_abstract(pass_is_abstract=False) # type: ignore[no-any-return]
# check if this class abstract
if class_is_abstract(node):
@@ -2024,7 +2031,13 @@ a metaclass class method.",
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))
+
+ self.add_message(
+ "abstract-method",
+ node=node,
+ args=(name, owner.name, node.name),
+ confidence=INFERENCE,
+ )
def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> None:
"""Check that the __init__ method call super or ancestors'__init__
@@ -2111,7 +2124,6 @@ a metaclass class method.",
self,
method1: nodes.FunctionDef,
refmethod: nodes.FunctionDef,
- class_type: str,
cls: nodes.ClassDef,
) -> None:
"""Check that the signature of the two given methods match."""
@@ -2143,6 +2155,9 @@ a metaclass class method.",
arg_differ_output = _different_parameters(
refmethod, method1, dummy_parameter_regex=self._dummy_rgx
)
+
+ class_type = "overriding"
+
if len(arg_differ_output) > 0:
for msg in arg_differ_output:
if "Number" in msg:
@@ -2187,6 +2202,7 @@ a metaclass class method.",
len(method1.args.defaults) < len(refmethod.args.defaults)
and not method1.args.vararg
):
+ class_type = "overridden"
self.add_message(
"signature-differs", args=(class_type, method1.name), node=method1
)
diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py
index c97393bcc..a8e7fb1da 100644
--- a/pylint/checkers/design_analysis.py
+++ b/pylint/checkers/design_analysis.py
@@ -9,7 +9,7 @@ from __future__ import annotations
import re
from collections import defaultdict
from collections.abc import Iterator
-from typing import TYPE_CHECKING, List, cast
+from typing import TYPE_CHECKING
import astroid
from astroid import nodes
@@ -245,7 +245,7 @@ def _get_parents_iter(
``{A, B, C, D}`` -- both ``E`` and its ancestors are excluded.
"""
parents: set[nodes.ClassDef] = set()
- to_explore = cast(List[nodes.ClassDef], list(node.ancestors(recurs=False)))
+ to_explore = list(node.ancestors(recurs=False))
while to_explore:
parent = to_explore.pop()
if parent.qname() in ignored_parents:
diff --git a/pylint/checkers/dunder_methods.py b/pylint/checkers/dunder_methods.py
index 2e5e54a57..987e539aa 100644
--- a/pylint/checkers/dunder_methods.py
+++ b/pylint/checkers/dunder_methods.py
@@ -10,122 +10,19 @@ from astroid import Instance, Uninferable, nodes
from pylint.checkers import BaseChecker
from pylint.checkers.utils import safe_infer
+from pylint.constants import DUNDER_METHODS
from pylint.interfaces import HIGH
if TYPE_CHECKING:
from pylint.lint import PyLinter
-DUNDER_METHODS: dict[tuple[int, int], dict[str, str]] = {
- (0, 0): {
- "__init__": "Instantiate class directly",
- "__del__": "Use del keyword",
- "__repr__": "Use repr built-in function",
- "__str__": "Use str built-in function",
- "__bytes__": "Use bytes built-in function",
- "__format__": "Use format built-in function, format string method, or f-string",
- "__lt__": "Use < operator",
- "__le__": "Use <= operator",
- "__eq__": "Use == operator",
- "__ne__": "Use != operator",
- "__gt__": "Use > operator",
- "__ge__": "Use >= operator",
- "__hash__": "Use hash built-in function",
- "__bool__": "Use bool built-in function",
- "__getattr__": "Access attribute directly or use getattr built-in function",
- "__getattribute__": "Access attribute directly or use getattr built-in function",
- "__setattr__": "Set attribute directly or use setattr built-in function",
- "__delattr__": "Use del keyword",
- "__dir__": "Use dir built-in function",
- "__get__": "Use get method",
- "__set__": "Use set method",
- "__delete__": "Use del keyword",
- "__instancecheck__": "Use isinstance built-in function",
- "__subclasscheck__": "Use issubclass built-in function",
- "__call__": "Invoke instance directly",
- "__len__": "Use len built-in function",
- "__length_hint__": "Use length_hint method",
- "__getitem__": "Access item via subscript",
- "__setitem__": "Set item via subscript",
- "__delitem__": "Use del keyword",
- "__iter__": "Use iter built-in function",
- "__next__": "Use next built-in function",
- "__reversed__": "Use reversed built-in function",
- "__contains__": "Use in keyword",
- "__add__": "Use + operator",
- "__sub__": "Use - operator",
- "__mul__": "Use * operator",
- "__matmul__": "Use @ operator",
- "__truediv__": "Use / operator",
- "__floordiv__": "Use // operator",
- "__mod__": "Use % operator",
- "__divmod__": "Use divmod built-in function",
- "__pow__": "Use ** operator or pow built-in function",
- "__lshift__": "Use << operator",
- "__rshift__": "Use >> operator",
- "__and__": "Use & operator",
- "__xor__": "Use ^ operator",
- "__or__": "Use | operator",
- "__radd__": "Use + operator",
- "__rsub__": "Use - operator",
- "__rmul__": "Use * operator",
- "__rmatmul__": "Use @ operator",
- "__rtruediv__": "Use / operator",
- "__rfloordiv__": "Use // operator",
- "__rmod__": "Use % operator",
- "__rdivmod__": "Use divmod built-in function",
- "__rpow__": "Use ** operator or pow built-in function",
- "__rlshift__": "Use << operator",
- "__rrshift__": "Use >> operator",
- "__rand__": "Use & operator",
- "__rxor__": "Use ^ operator",
- "__ror__": "Use | operator",
- "__iadd__": "Use += operator",
- "__isub__": "Use -= operator",
- "__imul__": "Use *= operator",
- "__imatmul__": "Use @= operator",
- "__itruediv__": "Use /= operator",
- "__ifloordiv__": "Use //= operator",
- "__imod__": "Use %= operator",
- "__ipow__": "Use **= operator",
- "__ilshift__": "Use <<= operator",
- "__irshift__": "Use >>= operator",
- "__iand__": "Use &= operator",
- "__ixor__": "Use ^= operator",
- "__ior__": "Use |= operator",
- "__neg__": "Multiply by -1 instead",
- "__pos__": "Multiply by +1 instead",
- "__abs__": "Use abs built-in function",
- "__invert__": "Use ~ operator",
- "__complex__": "Use complex built-in function",
- "__int__": "Use int built-in function",
- "__float__": "Use float built-in function",
- "__round__": "Use round built-in function",
- "__trunc__": "Use math.trunc function",
- "__floor__": "Use math.floor function",
- "__ceil__": "Use math.ceil function",
- "__enter__": "Invoke context manager directly",
- "__aenter__": "Invoke context manager directly",
- "__copy__": "Use copy.copy function",
- "__deepcopy__": "Use copy.deepcopy function",
- "__fspath__": "Use os.fspath function instead",
- },
- (3, 10): {
- "__aiter__": "Use aiter built-in function",
- "__anext__": "Use anext built-in function",
- },
-}
-
-
class DunderCallChecker(BaseChecker):
"""Check for unnecessary dunder method calls.
Docs: https://docs.python.org/3/reference/datamodel.html#basic-customization
- We exclude __new__, __subclasses__, __init_subclass__, __set_name__,
- __class_getitem__, __missing__, __exit__, __await__,
- __aexit__, __getnewargs_ex__, __getnewargs__, __getstate__,
- __setstate__, __reduce__, __reduce_ex__,
- and __index__ (see https://github.com/PyCQA/pylint/issues/6795)
+ We exclude names in list pylint.constants.EXTRA_DUNDER_METHODS such as
+ __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/exceptions.py b/pylint/checkers/exceptions.py
index 0d3f0706f..a563c6eb9 100644
--- a/pylint/checkers/exceptions.py
+++ b/pylint/checkers/exceptions.py
@@ -8,6 +8,7 @@ from __future__ import annotations
import builtins
import inspect
+import warnings
from collections.abc import Generator
from typing import TYPE_CHECKING, Any
@@ -59,8 +60,6 @@ def _is_raising(body: list[nodes.NodeNG]) -> bool:
return any(isinstance(node, nodes.Raise) for node in body)
-OVERGENERAL_EXCEPTIONS = ("BaseException", "Exception")
-
MSGS: dict[str, MessageDefinitionTuple] = {
"E0701": (
"Bad except clauses order (%s)",
@@ -115,11 +114,12 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"bare-except",
"Used when an except clause doesn't specify exceptions type to catch.",
),
- "W0703": (
+ "W0718": (
"Catching too general exception %s",
- "broad-except",
+ "broad-exception-caught",
"Used when an except catches a too general exception, "
"possibly burying unrelated errors.",
+ {"old_names": [("W0703", "broad-except")]},
),
"W0705": (
"Catching previously caught exception type %s",
@@ -165,6 +165,11 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"is not valid for the exception in question. Usually emitted when having "
"binary operations between exceptions in except handlers.",
),
+ "W0719": (
+ "Raising too general exception: %s",
+ "broad-exception-raised",
+ "Used when an except raises a too general exception.",
+ ),
}
@@ -192,7 +197,26 @@ class ExceptionRaiseRefVisitor(BaseVisitor):
def visit_name(self, node: nodes.Name) -> None:
if node.name == "NotImplemented":
- self._checker.add_message("notimplemented-raised", node=self._node)
+ self._checker.add_message(
+ "notimplemented-raised", node=self._node, confidence=HIGH
+ )
+ return
+
+ try:
+ exceptions = list(_annotated_unpack_infer(node))
+ except astroid.InferenceError:
+ return
+
+ for _, exception in exceptions:
+ if isinstance(
+ exception, nodes.ClassDef
+ ) and self._checker._is_overgeneral_exception(exception):
+ self._checker.add_message(
+ "broad-exception-raised",
+ args=exception.name,
+ node=self._node,
+ confidence=INFERENCE,
+ )
def visit_call(self, node: nodes.Call) -> None:
if isinstance(node.func, nodes.Name):
@@ -204,7 +228,9 @@ class ExceptionRaiseRefVisitor(BaseVisitor):
):
msg = node.args[0].value
if "%" in msg or ("{" in msg and "}" in msg):
- self._checker.add_message("raising-format-tuple", node=self._node)
+ self._checker.add_message(
+ "raising-format-tuple", node=self._node, confidence=HIGH
+ )
class ExceptionRaiseLeafVisitor(BaseVisitor):
@@ -212,7 +238,10 @@ class ExceptionRaiseLeafVisitor(BaseVisitor):
def visit_const(self, node: nodes.Const) -> None:
self._checker.add_message(
- "raising-bad-type", node=self._node, args=node.value.__class__.__name__
+ "raising-bad-type",
+ node=self._node,
+ args=node.value.__class__.__name__,
+ confidence=INFERENCE,
)
def visit_instance(self, instance: objects.ExceptionInstance) -> None:
@@ -225,14 +254,28 @@ class ExceptionRaiseLeafVisitor(BaseVisitor):
def visit_classdef(self, node: nodes.ClassDef) -> None:
if not utils.inherit_from_std_ex(node) and utils.has_known_bases(node):
if node.newstyle:
- self._checker.add_message("raising-non-exception", node=self._node)
+ self._checker.add_message(
+ "raising-non-exception",
+ node=self._node,
+ confidence=INFERENCE,
+ )
def visit_tuple(self, _: nodes.Tuple) -> None:
- self._checker.add_message("raising-bad-type", node=self._node, args="tuple")
+ self._checker.add_message(
+ "raising-bad-type",
+ node=self._node,
+ args="tuple",
+ confidence=INFERENCE,
+ )
def visit_default(self, node: nodes.NodeNG) -> None:
name = getattr(node, "name", node.__class__.__name__)
- self._checker.add_message("raising-bad-type", node=self._node, args=name)
+ self._checker.add_message(
+ "raising-bad-type",
+ node=self._node,
+ args=name,
+ confidence=INFERENCE,
+ )
class ExceptionsChecker(checkers.BaseChecker):
@@ -244,7 +287,7 @@ class ExceptionsChecker(checkers.BaseChecker):
(
"overgeneral-exceptions",
{
- "default": OVERGENERAL_EXCEPTIONS,
+ "default": ("builtins.BaseException", "builtins.Exception"),
"type": "csv",
"metavar": "<comma-separated class names>",
"help": "Exceptions that will emit a warning when caught.",
@@ -254,6 +297,18 @@ class ExceptionsChecker(checkers.BaseChecker):
def open(self) -> None:
self._builtin_exceptions = _builtin_exceptions()
+ for exc_name in self.linter.config.overgeneral_exceptions:
+ if "." not in exc_name:
+ warnings.warn_explicit(
+ "Specifying exception names in the overgeneral-exceptions option"
+ " without module name is deprecated and support for it"
+ " will be removed in pylint 3.0."
+ f" Use fully qualified name (maybe 'builtins.{exc_name}' ?) instead.",
+ category=UserWarning,
+ filename="pylint: Command line or configuration file",
+ lineno=1,
+ module="pylint",
+ )
super().open()
@utils.only_required_for_messages(
@@ -264,6 +319,7 @@ class ExceptionsChecker(checkers.BaseChecker):
"bad-exception-cause",
"raising-format-tuple",
"raise-missing-from",
+ "broad-exception-raised",
)
def visit_raise(self, node: nodes.Raise) -> None:
if node.exc is None:
@@ -302,7 +358,7 @@ class ExceptionsChecker(checkers.BaseChecker):
expected = (nodes.ExceptHandler,)
if not current or not isinstance(current.parent, expected):
- self.add_message("misplaced-bare-raise", node=node)
+ self.add_message("misplaced-bare-raise", node=node, confidence=HIGH)
def _check_bad_exception_cause(self, node: nodes.Raise) -> None:
"""Verify that the exception cause is properly set.
@@ -493,7 +549,7 @@ class ExceptionsChecker(checkers.BaseChecker):
@utils.only_required_for_messages(
"bare-except",
- "broad-except",
+ "broad-exception-caught",
"try-except-raise",
"binary-op-exception",
"bad-except-order",
@@ -508,17 +564,22 @@ class ExceptionsChecker(checkers.BaseChecker):
for index, handler in enumerate(node.handlers):
if handler.type is None:
if not _is_raising(handler.body):
- self.add_message("bare-except", node=handler)
+ self.add_message("bare-except", node=handler, confidence=HIGH)
# check if an "except:" is followed by some other
# except
if index < (nb_handlers - 1):
msg = "empty except clause should always appear last"
- self.add_message("bad-except-order", node=node, args=msg)
+ self.add_message(
+ "bad-except-order", node=node, args=msg, confidence=HIGH
+ )
elif isinstance(handler.type, nodes.BoolOp):
self.add_message(
- "binary-op-exception", node=handler, args=handler.type.op
+ "binary-op-exception",
+ node=handler,
+ args=handler.type.op,
+ confidence=HIGH,
)
else:
try:
@@ -547,24 +608,40 @@ class ExceptionsChecker(checkers.BaseChecker):
if previous_exc in exc_ancestors:
msg = f"{previous_exc.name} is an ancestor class of {exception.name}"
self.add_message(
- "bad-except-order", node=handler.type, args=msg
+ "bad-except-order",
+ node=handler.type,
+ args=msg,
+ confidence=INFERENCE,
)
- if (
- exception.name in self.linter.config.overgeneral_exceptions
- and exception.root().name == utils.EXCEPTIONS_MODULE
- and not _is_raising(handler.body)
+ if self._is_overgeneral_exception(exception) and not _is_raising(
+ handler.body
):
self.add_message(
- "broad-except", args=exception.name, node=handler.type
+ "broad-exception-caught",
+ args=exception.name,
+ node=handler.type,
+ confidence=INFERENCE,
)
if exception in exceptions_classes:
self.add_message(
- "duplicate-except", args=exception.name, node=handler.type
+ "duplicate-except",
+ args=exception.name,
+ node=handler.type,
+ confidence=INFERENCE,
)
exceptions_classes += [exc for _, exc in exceptions]
+ def _is_overgeneral_exception(self, exception: nodes.ClassDef) -> bool:
+ return (
+ exception.qname() in self.linter.config.overgeneral_exceptions
+ # TODO: 3.0: not a qualified name, deprecated
+ or "." not in exception.name
+ and exception.name in self.linter.config.overgeneral_exceptions
+ and exception.root().name == utils.EXCEPTIONS_MODULE
+ )
+
def register(linter: PyLinter) -> None:
linter.register_checker(ExceptionsChecker(linter))
diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py
index 5d9a854b9..001330b2b 100644
--- a/pylint/checkers/format.py
+++ b/pylint/checkers/format.py
@@ -22,12 +22,7 @@ from typing import TYPE_CHECKING
from astroid import nodes
from pylint.checkers import BaseRawFileChecker, BaseTokenChecker
-from pylint.checkers.utils import (
- is_overload_stub,
- is_protocol_class,
- node_frame_class,
- only_required_for_messages,
-)
+from pylint.checkers.utils import only_required_for_messages
from pylint.constants import WarningScope
from pylint.interfaces import HIGH
from pylint.typing import MessageDefinitionTuple
@@ -55,6 +50,8 @@ _KEYWORD_TOKENS = {
"while",
"yield",
"with",
+ "=",
+ ":=",
}
_JUNK_TOKENS = {tokenize.COMMENT, tokenize.NL}
@@ -273,7 +270,7 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker):
line = tokens.line(line_start)
if tokens.type(line_start) not in _JUNK_TOKENS:
self._lines[line_num] = line.split("\n")[0]
- self.check_lines(line, line_num)
+ self.check_lines(tokens, line_start, line, line_num)
def process_module(self, node: nodes.Module) -> None:
pass
@@ -404,12 +401,6 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker):
# the full line; therefore we check the next token on the line.
if tok_type == tokenize.INDENT:
self.new_line(TokenWrapper(tokens), idx - 1, idx + 1)
- # A tokenizer oddity: if a line contains a multi-line string,
- # the NEWLINE also gets its own token which we already checked in
- # the multi-line docstring case.
- # See https://github.com/PyCQA/pylint/issues/6936
- elif tok_type == tokenize.NEWLINE:
- pass
else:
self.new_line(TokenWrapper(tokens), idx - 1, idx)
@@ -535,7 +526,7 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker):
tolineno = node.tolineno
assert tolineno, node
lines: list[str] = []
- for line in range(line, tolineno + 1):
+ for line in range(line, tolineno + 1): # noqa: B020
self._visited_lines[line] = 1
try:
lines.append(self._lines[line].rstrip())
@@ -567,26 +558,20 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker):
):
return
- # Function overloads that use ``Ellipsis`` are exempted.
+ # Functions stubs with ``Ellipsis`` as body are exempted.
if (
- isinstance(node, nodes.Expr)
+ isinstance(node.parent, nodes.FunctionDef)
+ and isinstance(node, nodes.Expr)
and isinstance(node.value, nodes.Const)
and node.value.value is Ellipsis
):
- frame = node.frame(future=True)
- if is_overload_stub(frame) or is_protocol_class(node_frame_class(frame)):
- return
+ return
self.add_message("multiple-statements", node=node)
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
- white-space.
- """
- if not line.endswith("\n"):
- self.add_message("missing-final-newline", line=i)
- return
+ def check_trailing_whitespace_ending(self, line: str, i: int) -> None:
+ """Check that there is no trailing white-space."""
# exclude \f (formfeed) from the rstrip
stripped_line = line.rstrip("\t\n\r\v ")
if line[len(stripped_line) :] not in ("\n", "\r\n"):
@@ -655,7 +640,9 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker):
buffer += atomic_line
return res
- def check_lines(self, lines: str, lineno: int) -> None:
+ def check_lines(
+ self, tokens: TokenWrapper, line_start: int, lines: str, lineno: int
+ ) -> None:
"""Check given lines for potential messages.
Check if lines have:
@@ -676,17 +663,20 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker):
split_lines = self.specific_splitlines(lines)
for offset, line in enumerate(split_lines):
- self.check_line_ending(line, lineno + offset)
-
- # hold onto the initial lineno for later
- potential_line_length_warning = False
- for offset, line in enumerate(split_lines):
- # this check is purposefully simple and doesn't rstrip
- # since this is running on every line you're checking it's
- # advantageous to avoid doing a lot of work
- if len(line) > max_chars:
- potential_line_length_warning = True
- break
+ if not line.endswith("\n"):
+ self.add_message("missing-final-newline", line=lineno + offset)
+ continue
+ # We don't test for trailing whitespaces in strings
+ # See https://github.com/PyCQA/pylint/issues/6936
+ # and https://github.com/PyCQA/pylint/issues/3822
+ if tokens.type(line_start) != tokenize.STRING:
+ self.check_trailing_whitespace_ending(line, lineno + offset)
+
+ # This check is purposefully simple and doesn't rstrip since this is running
+ # on every line you're checking it's advantageous to avoid doing a lot of work
+ potential_line_length_warning = any(
+ len(line) > max_chars for line in split_lines
+ )
# if there were no lines passing the max_chars config, we don't bother
# running the full line check (as we've met an even more strict condition)
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
index 7cab78586..61c18649b 100644
--- a/pylint/checkers/imports.py
+++ b/pylint/checkers/imports.py
@@ -11,8 +11,8 @@ import copy
import os
import sys
from collections import defaultdict
-from collections.abc import Sequence
-from typing import TYPE_CHECKING, Any
+from collections.abc import ItemsView, Sequence
+from typing import TYPE_CHECKING, Any, Dict, List, Union
import astroid
from astroid import nodes
@@ -37,6 +37,9 @@ from pylint.utils.linterstats import LinterStats
if TYPE_CHECKING:
from pylint.lint import PyLinter
+# The dictionary with Any should actually be a _ImportTree again
+# but mypy doesn't support recursive types yet
+_ImportTree = Dict[str, Union[List[Dict[str, Any]], List[str]]]
DEPRECATED_MODULES = {
(0, 0, 0): {"tkinter.tix", "fpectl"},
@@ -47,7 +50,7 @@ DEPRECATED_MODULES = {
(3, 6, 0): {"asynchat", "asyncore", "smtpd"},
(3, 7, 0): {"macpath"},
(3, 9, 0): {"lib2to3", "parser", "symbol", "binhex"},
- (3, 10, 0): {"distutils"},
+ (3, 10, 0): {"distutils", "typing.io", "typing.re"},
(3, 11, 0): {
"aifc",
"audioop",
@@ -57,6 +60,7 @@ DEPRECATED_MODULES = {
"crypt",
"imghdr",
"msilib",
+ "mailcap",
"nis",
"nntplib",
"ossaudiodev",
@@ -148,35 +152,37 @@ def _ignore_import_failure(
# utilities to represents import dependencies as tree and dot graph ###########
-def _make_tree_defs(mod_files_list):
+def _make_tree_defs(mod_files_list: ItemsView[str, set[str]]) -> _ImportTree:
"""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 = {}
+ tree_defs: _ImportTree = {}
for mod, files in mod_files_list:
- node = (tree_defs, ())
+ node: list[_ImportTree | list[str]] = [tree_defs, []]
for prefix in mod.split("."):
- node = node[0].setdefault(prefix, [{}, []])
+ assert isinstance(node[0], dict)
+ node = node[0].setdefault(prefix, ({}, [])) # type: ignore[arg-type,assignment]
+ assert isinstance(node[1], list)
node[1] += files
return tree_defs
-def _repr_tree_defs(data, indent_str=None):
+def _repr_tree_defs(data: _ImportTree, indent_str: str | None = None) -> str:
"""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])):
- files = "" if not files else f"({','.join(sorted(files))})"
+ files_list = "" if not files else f"({','.join(sorted(files))})"
if indent_str is None:
- lines.append(f"{mod} {files}")
+ lines.append(f"{mod} {files_list}")
sub_indent_str = " "
else:
- lines.append(rf"{indent_str}\-{mod} {files}")
+ lines.append(rf"{indent_str}\-{mod} {files_list}")
if i == len(nodes_items) - 1:
sub_indent_str = f"{indent_str} "
else:
sub_indent_str = f"{indent_str}| "
- if sub:
+ if sub and isinstance(sub, dict):
lines.append(_repr_tree_defs(sub, sub_indent_str))
return "\n".join(lines)
@@ -420,7 +426,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
def __init__(self, linter: PyLinter) -> None:
BaseChecker.__init__(self, linter)
self.import_graph: defaultdict[str, set[str]] = defaultdict(set)
- self._imports_stack: list[tuple[Any, Any]] = []
+ self._imports_stack: list[tuple[ImportNode, str]] = []
self._first_non_import_node = None
self._module_pkg: dict[
Any, Any
@@ -692,24 +698,32 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
self._imports_stack.append((node, importedname))
@staticmethod
- def _is_fallback_import(node, imports):
+ def _is_fallback_import(
+ node: ImportNode, imports: list[tuple[ImportNode, str]]
+ ) -> bool:
imports = [import_node for (import_node, _) in imports]
return any(astroid.are_exclusive(import_node, node) for import_node in imports)
- def _check_imports_order(self, _module_node):
+ def _check_imports_order(
+ self, _module_node: nodes.Module
+ ) -> tuple[
+ list[tuple[ImportNode, str]],
+ list[tuple[ImportNode, str]],
+ list[tuple[ImportNode, str]],
+ ]:
"""Checks imports of module `node` are grouped by category.
Imports must follow this order: standard, 3rd party, local
"""
- std_imports = []
- third_party_imports = []
- first_party_imports = []
+ std_imports: list[tuple[ImportNode, str]] = []
+ third_party_imports: list[tuple[ImportNode, str]] = []
+ first_party_imports: list[tuple[ImportNode, str]] = []
# need of a list that holds third or first party ordered import
- external_imports = []
- local_imports = []
- third_party_not_ignored = []
- first_party_not_ignored = []
- local_not_ignored = []
+ external_imports: list[tuple[ImportNode, str]] = []
+ local_imports: list[tuple[ImportNode, str]] = []
+ third_party_not_ignored: list[tuple[ImportNode, str]] = []
+ first_party_not_ignored: list[tuple[ImportNode, str]] = []
+ local_not_ignored: list[tuple[ImportNode, str]] = []
isort_driver = IsortDriver(self.linter.config)
for node, modname in self._imports_stack:
if modname.startswith("."):
@@ -864,7 +878,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
):
self._excluded_edges[context_name].add(importedmodname)
- def _check_preferred_module(self, node, mod_path):
+ def _check_preferred_module(self, node: ImportNode, mod_path: str) -> None:
"""Check if the module has a preferred replacement."""
if mod_path in self.preferred_modules:
self.add_message(
diff --git a/pylint/checkers/mapreduce_checker.py b/pylint/checkers/mapreduce_checker.py
index 9d721aa49..96e86d7c0 100644
--- a/pylint/checkers/mapreduce_checker.py
+++ b/pylint/checkers/mapreduce_checker.py
@@ -20,6 +20,7 @@ class MapReduceMixin(metaclass=abc.ABCMeta):
"MapReduceMixin has been deprecated and will be removed in pylint 3.0. "
"To make a checker reduce map data simply implement get_map_data and reduce_map_data.",
DeprecationWarning,
+ stacklevel=2,
)
@abc.abstractmethod
diff --git a/pylint/checkers/modified_iterating_checker.py b/pylint/checkers/modified_iterating_checker.py
index c98e7ebc5..bdc8fff7f 100644
--- a/pylint/checkers/modified_iterating_checker.py
+++ b/pylint/checkers/modified_iterating_checker.py
@@ -121,7 +121,7 @@ class ModifiedIterationChecker(checkers.BaseChecker):
if isinstance(iter_obj, nodes.Attribute)
else iter_obj.name
)
- return (infer_val == utils.safe_infer(iter_obj)) and (
+ return (infer_val == utils.safe_infer(iter_obj)) and ( # type: ignore[no-any-return]
node.value.func.expr.name == iter_obj_name
)
@@ -168,7 +168,7 @@ class ModifiedIterationChecker(checkers.BaseChecker):
iter_obj_name = iter_obj.attrname
else:
iter_obj_name = iter_obj.name
- return node.targets[0].value.name == iter_obj_name
+ return node.targets[0].value.name == iter_obj_name # type: ignore[no-any-return]
def _modified_iterating_set_cond(
self, node: nodes.NodeNG, iter_obj: nodes.Name | nodes.Attribute
diff --git a/pylint/checkers/nested_min_max.py b/pylint/checkers/nested_min_max.py
new file mode 100644
index 000000000..39feb5f42
--- /dev/null
+++ b/pylint/checkers/nested_min_max.py
@@ -0,0 +1,95 @@
+# Licensed under the GPL: https://www.gnu.org/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 nested min/max functions."""
+
+from __future__ import annotations
+
+import copy
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import only_required_for_messages, safe_infer
+from pylint.interfaces import INFERENCE
+
+if TYPE_CHECKING:
+ from pylint.lint import PyLinter
+
+
+class NestedMinMaxChecker(BaseChecker):
+ """Multiple nested min/max calls on the same line will raise multiple messages.
+
+ This behaviour is intended as it would slow down the checker to check
+ for nested call with minimal benefits.
+ """
+
+ FUNC_NAMES = ("builtins.min", "builtins.max")
+
+ name = "nested_min_max"
+ msgs = {
+ "W3301": (
+ "Do not use nested call of '%s'; it's possible to do '%s' instead",
+ "nested-min-max",
+ "Nested calls ``min(1, min(2, 3))`` can be rewritten as ``min(1, 2, 3)``.",
+ )
+ }
+
+ @classmethod
+ def is_min_max_call(cls, node: nodes.NodeNG) -> bool:
+ if not isinstance(node, nodes.Call):
+ return False
+
+ inferred = safe_infer(node.func)
+ return (
+ isinstance(inferred, nodes.FunctionDef)
+ and inferred.qname() in cls.FUNC_NAMES
+ )
+
+ @classmethod
+ def get_redundant_calls(cls, node: nodes.Call) -> list[nodes.Call]:
+ return [
+ arg
+ for arg in node.args
+ if cls.is_min_max_call(arg) and arg.func.name == node.func.name
+ ]
+
+ @only_required_for_messages("nested-min-max")
+ def visit_call(self, node: nodes.Call) -> None:
+ if not self.is_min_max_call(node):
+ return
+
+ redundant_calls = self.get_redundant_calls(node)
+ if not redundant_calls:
+ return
+
+ fixed_node = copy.copy(node)
+ while len(redundant_calls) > 0:
+ for i, arg in enumerate(fixed_node.args):
+ # Exclude any calls with generator expressions as there is no
+ # clear better suggestion for them.
+ if isinstance(arg, nodes.Call) and any(
+ isinstance(a, nodes.GeneratorExp) for a in arg.args
+ ):
+ return
+
+ if arg in redundant_calls:
+ fixed_node.args = (
+ fixed_node.args[:i] + arg.args + fixed_node.args[i + 1 :]
+ )
+ break
+
+ redundant_calls = self.get_redundant_calls(fixed_node)
+
+ self.add_message(
+ "nested-min-max",
+ node=node,
+ args=(node.func.name, fixed_node.as_string()),
+ confidence=INFERENCE,
+ )
+
+
+def register(linter: PyLinter) -> None:
+ linter.register_checker(NestedMinMaxChecker(linter))
diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py
index 13172a97e..2ad038619 100644
--- a/pylint/checkers/refactoring/implicit_booleaness_checker.py
+++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py
@@ -9,6 +9,7 @@ from astroid import bases, nodes
from pylint import checkers
from pylint.checkers import utils
+from pylint.interfaces import HIGH, INFERENCE
class ImplicitBooleanessChecker(checkers.BaseChecker):
@@ -50,7 +51,6 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
* comparison such as variable != empty_literal:
"""
- # configuration section name
name = "refactoring"
msgs = {
"C1802": (
@@ -64,7 +64,7 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
{"old_names": [("C1801", "len-as-condition")]},
),
"C1803": (
- "'%s' can be simplified to '%s' as an empty sequence is falsey",
+ "'%s' can be simplified to '%s' as an empty %s is falsey",
"use-implicit-booleaness-not-comparison",
"Used when Pylint detects that collection literal comparison is being "
"used to check for emptiness; Use implicit booleaness instead "
@@ -99,7 +99,11 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
)
if isinstance(len_arg, generator_or_comprehension):
# The node is a generator or comprehension as in len([x for x in ...])
- self.add_message("use-implicit-booleaness-not-len", node=node)
+ self.add_message(
+ "use-implicit-booleaness-not-len",
+ node=node,
+ confidence=HIGH,
+ )
return
try:
instance = next(len_arg.infer())
@@ -113,7 +117,11 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
if "range" in mother_classes or (
affected_by_pep8 and not self.instance_has_bool(instance)
):
- self.add_message("use-implicit-booleaness-not-len", node=node)
+ self.add_message(
+ "use-implicit-booleaness-not-len",
+ node=node,
+ confidence=INFERENCE,
+ )
@staticmethod
def instance_has_bool(class_def: nodes.ClassDef) -> bool:
@@ -134,7 +142,9 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
and node.op == "not"
and utils.is_call_of_name(node.operand, "len")
):
- self.add_message("use-implicit-booleaness-not-len", node=node)
+ self.add_message(
+ "use-implicit-booleaness-not-len", node=node, confidence=HIGH
+ )
@utils.only_required_for_messages("use-implicit-booleaness-not-comparison")
def visit_compare(self, node: nodes.Compare) -> None:
@@ -177,35 +187,42 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
# No need to check for operator when visiting compare node
if operator in {"==", "!=", ">=", ">", "<=", "<"}:
- collection_literal = "{}"
- if isinstance(literal_node, nodes.List):
- collection_literal = "[]"
- if isinstance(literal_node, nodes.Tuple):
- collection_literal = "()"
-
- instance_name = "x"
- if isinstance(target_node, nodes.Call) and target_node.func:
- instance_name = f"{target_node.func.as_string()}(...)"
- elif isinstance(target_node, (nodes.Attribute, nodes.Name)):
- instance_name = target_node.as_string()
-
- original_comparison = (
- f"{instance_name} {operator} {collection_literal}"
- )
- suggestion = (
- f"{instance_name}"
- if operator == "!="
- else f"not {instance_name}"
- )
self.add_message(
"use-implicit-booleaness-not-comparison",
- args=(
- original_comparison,
- suggestion,
+ args=self._implicit_booleaness_message_args(
+ literal_node, operator, target_node
),
node=node,
+ confidence=HIGH,
)
+ def _get_node_description(self, node: nodes.NodeNG) -> str:
+ return {
+ nodes.List: "list",
+ nodes.Tuple: "tuple",
+ nodes.Dict: "dict",
+ nodes.Const: "str",
+ }.get(type(node), "iterable")
+
+ def _implicit_booleaness_message_args(
+ self, literal_node: nodes.NodeNG, operator: str, target_node: nodes.NodeNG
+ ) -> tuple[str, str, str]:
+ """Helper to get the right message for "use-implicit-booleaness-not-comparison"."""
+ description = self._get_node_description(literal_node)
+ collection_literal = {
+ "list": "[]",
+ "tuple": "()",
+ "dict": "{}",
+ }.get(description, "iterable")
+ instance_name = "x"
+ if isinstance(target_node, nodes.Call) and target_node.func:
+ instance_name = f"{target_node.func.as_string()}(...)"
+ elif isinstance(target_node, (nodes.Attribute, nodes.Name)):
+ instance_name = target_node.as_string()
+ original_comparison = f"{instance_name} {operator} {collection_literal}"
+ suggestion = f"{instance_name}" if operator == "!=" else f"not {instance_name}"
+ return original_comparison, suggestion, description
+
@staticmethod
def base_names_of_instance(node: bases.Uninferable | bases.Instance) -> list[str]:
"""Return all names inherited by a class instance or those returned by a
diff --git a/pylint/checkers/refactoring/recommendation_checker.py b/pylint/checkers/refactoring/recommendation_checker.py
index 7873dc25e..cda26e064 100644
--- a/pylint/checkers/refactoring/recommendation_checker.py
+++ b/pylint/checkers/refactoring/recommendation_checker.py
@@ -9,6 +9,7 @@ from astroid import nodes
from pylint import checkers
from pylint.checkers import utils
+from pylint.interfaces import INFERENCE
class RecommendationChecker(checkers.BaseChecker):
@@ -67,7 +68,7 @@ class RecommendationChecker(checkers.BaseChecker):
self._py36_plus = py_version >= (3, 6)
@staticmethod
- def _is_builtin(node, function):
+ def _is_builtin(node: nodes.NodeNG, function: str) -> bool:
inferred = utils.safe_infer(node)
if not inferred:
return False
@@ -85,6 +86,10 @@ class RecommendationChecker(checkers.BaseChecker):
return
if node.func.attrname != "keys":
return
+
+ if isinstance(node.parent, nodes.BinOp) and node.parent.op in {"&", "|", "^"}:
+ return
+
comp_ancestor = utils.get_node_first_ancestor_of_type(node, nodes.Compare)
if (
isinstance(node.parent, (nodes.For, nodes.Comprehension))
@@ -101,7 +106,9 @@ class RecommendationChecker(checkers.BaseChecker):
inferred.bound, nodes.Dict
):
return
- self.add_message("consider-iterating-dictionary", node=node)
+ self.add_message(
+ "consider-iterating-dictionary", node=node, confidence=INFERENCE
+ )
def _check_use_maxsplit_arg(self, node: nodes.Call) -> None:
"""Add message when accessing first or last elements of a str.split() or
diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py
index 87b831609..6a79ce55a 100644
--- a/pylint/checkers/refactoring/refactoring_checker.py
+++ b/pylint/checkers/refactoring/refactoring_checker.py
@@ -12,7 +12,7 @@ import tokenize
from collections.abc import Iterator
from functools import reduce
from re import Pattern
-from typing import TYPE_CHECKING, Any, NamedTuple, Union
+from typing import TYPE_CHECKING, Any, NamedTuple, Union, cast
import astroid
from astroid import bases, nodes
@@ -147,7 +147,7 @@ def _is_part_of_with_items(node: nodes.Call) -> bool:
if isinstance(current, nodes.With):
items_start = current.items[0][0].lineno
items_end = current.items[-1][0].tolineno
- return items_start <= node.lineno <= items_end
+ return items_start <= node.lineno <= items_end # type: ignore[no-any-return]
current = current.parent
return False
@@ -181,7 +181,7 @@ def _is_part_of_assignment_target(node: nodes.NodeNG) -> bool:
return node in node.parent.targets
if isinstance(node.parent, nodes.AugAssign):
- return node == node.parent.target
+ return node == node.parent.target # type: ignore[no-any-return]
if isinstance(node.parent, (nodes.Tuple, nodes.List)):
return _is_part_of_assignment_target(node.parent)
@@ -464,9 +464,9 @@ class RefactoringChecker(checkers.BaseTokenChecker):
"The literal is faster as it avoids an additional function call.",
),
"R1735": (
- "Consider using {} instead of dict()",
+ "Consider using '%s' instead of a call to 'dict'.",
"use-dict-literal",
- "Emitted when using dict() to create an empty dictionary instead of the literal {}. "
+ "Emitted when using dict() to create a dictionary instead of a literal '{ ... }'. "
"The literal is faster as it avoids an additional function call.",
),
"R1736": (
@@ -523,7 +523,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
@cached_property
def _dummy_rgx(self) -> Pattern[str]:
- return self.linter.config.dummy_variables_rgx
+ return self.linter.config.dummy_variables_rgx # type: ignore[no-any-return]
@staticmethod
def _is_bool_const(node: nodes.Return | nodes.Assign) -> bool:
@@ -748,13 +748,13 @@ class RefactoringChecker(checkers.BaseTokenChecker):
@staticmethod
def _type_and_name_are_equal(node_a: Any, node_b: Any) -> bool:
if isinstance(node_a, nodes.Name) and isinstance(node_b, nodes.Name):
- return node_a.name == node_b.name
+ return node_a.name == node_b.name # type: ignore[no-any-return]
if isinstance(node_a, nodes.AssignName) and isinstance(
node_b, nodes.AssignName
):
- return node_a.name == node_b.name
+ return node_a.name == node_b.name # type: ignore[no-any-return]
if isinstance(node_a, nodes.Const) and isinstance(node_b, nodes.Const):
- return node_a.value == node_b.value
+ return node_a.value == node_b.value # type: ignore[no-any-return]
return False
def _is_dict_get_block(self, node: nodes.If) -> bool:
@@ -1074,7 +1074,8 @@ class RefactoringChecker(checkers.BaseTokenChecker):
self._check_super_with_arguments(node)
self._check_consider_using_generator(node)
self._check_consider_using_with(node)
- self._check_use_list_or_dict_literal(node)
+ self._check_use_list_literal(node)
+ self._check_use_dict_literal(node)
@staticmethod
def _has_exit_in_scope(scope: nodes.LocalsDictNodeNG) -> bool:
@@ -1364,7 +1365,9 @@ class RefactoringChecker(checkers.BaseTokenChecker):
break
@staticmethod
- def _apply_boolean_simplification_rules(operator: str, values):
+ def _apply_boolean_simplification_rules(
+ operator: str, values: list[nodes.NodeNG]
+ ) -> list[nodes.NodeNG]:
"""Removes irrelevant values or returns short-circuiting values.
This function applies the following two rules:
@@ -1374,7 +1377,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
2) False values in OR expressions are only relevant if all values are
false, and the reverse for AND
"""
- simplified_values = []
+ simplified_values: list[nodes.NodeNG] = []
for subnode in values:
inferred_bool = None
@@ -1390,7 +1393,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
return simplified_values or [nodes.Const(operator == "and")]
- def _simplify_boolean_operation(self, bool_op: nodes.BoolOp):
+ def _simplify_boolean_operation(self, bool_op: nodes.BoolOp) -> nodes.BoolOp:
"""Attempts to simplify a boolean operation.
Recursively applies simplification on the operator terms,
@@ -1593,15 +1596,61 @@ class RefactoringChecker(checkers.BaseTokenChecker):
if could_be_used_in_with and not _will_be_released_automatically(node):
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 {}."""
- if node.as_string() in {"list()", "dict()"}:
+ def _check_use_list_literal(self, node: nodes.Call) -> None:
+ """Check if empty list is created by using the literal []."""
+ if node.as_string() == "list()":
inferred = utils.safe_infer(node.func)
if isinstance(inferred, nodes.ClassDef) and not node.args:
if inferred.qname() == "builtins.list":
self.add_message("use-list-literal", node=node)
- elif inferred.qname() == "builtins.dict" and not node.keywords:
- self.add_message("use-dict-literal", node=node)
+
+ def _check_use_dict_literal(self, node: nodes.Call) -> None:
+ """Check if dict is created by using the literal {}."""
+ if not isinstance(node.func, astroid.Name) or node.func.name != "dict":
+ return
+ inferred = utils.safe_infer(node.func)
+ if (
+ isinstance(inferred, nodes.ClassDef)
+ and inferred.qname() == "builtins.dict"
+ and not node.args
+ ):
+ self.add_message(
+ "use-dict-literal",
+ args=(self._dict_literal_suggestion(node),),
+ node=node,
+ confidence=INFERENCE,
+ )
+
+ @staticmethod
+ def _dict_literal_suggestion(node: nodes.Call) -> str:
+ """Return a suggestion of reasonable length."""
+ elements: list[str] = []
+ for keyword in node.keywords:
+ if len(", ".join(elements)) >= 64:
+ break
+ if keyword not in node.kwargs:
+ elements.append(f'"{keyword.arg}": {keyword.value.as_string()}')
+ for keyword in node.kwargs:
+ if len(", ".join(elements)) >= 64:
+ break
+ elements.append(f"**{keyword.value.as_string()}")
+ suggestion = ", ".join(elements)
+ return f"{{{suggestion}{', ... ' if len(suggestion) > 64 else ''}}}"
+
+ @staticmethod
+ def _name_to_concatenate(node: nodes.NodeNG) -> str | None:
+ """Try to extract the name used in a concatenation loop."""
+ if isinstance(node, nodes.Name):
+ return cast("str | None", node.name)
+ if not isinstance(node, nodes.JoinedStr):
+ return None
+
+ values = [
+ value for value in node.values if isinstance(value, nodes.FormattedValue)
+ ]
+ if len(values) != 1 or not isinstance(values[0].value, nodes.Name):
+ return None
+ return cast("str | None", values[0].value.name)
def _check_consider_using_join(self, aug_assign: nodes.AugAssign) -> None:
"""We start with the augmented assignment and work our way upwards.
@@ -1630,8 +1679,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
and aug_assign.target.name in result_assign_names
and isinstance(assign.value, nodes.Const)
and isinstance(assign.value.value, str)
- and isinstance(aug_assign.value, nodes.Name)
- and aug_assign.value.name == for_loop.target.name
+ and self._name_to_concatenate(aug_assign.value) == for_loop.target.name
)
if is_concat_loop:
self.add_message("consider-using-join", node=aug_assign)
@@ -1749,7 +1797,9 @@ class RefactoringChecker(checkers.BaseTokenChecker):
)
@staticmethod
- def _and_or_ternary_arguments(node: nodes.BoolOp):
+ def _and_or_ternary_arguments(
+ node: nodes.BoolOp,
+ ) -> tuple[nodes.NodeNG, nodes.NodeNG, nodes.NodeNG]:
false_value = node.values[1]
condition, true_value = node.values[0].values
return condition, true_value, false_value
@@ -2101,11 +2151,19 @@ class RefactoringChecker(checkers.BaseTokenChecker):
not isinstance(node.iter, nodes.Call)
or not isinstance(node.iter.func, nodes.Name)
or not node.iter.func.name == "enumerate"
- or not node.iter.args
- or not isinstance(node.iter.args[0], nodes.Name)
):
return
+ try:
+ iterable_arg = utils.get_argument_from_call(
+ node.iter, position=0, keyword="iterable"
+ )
+ except utils.NoSuchArgumentError:
+ return
+
+ if not isinstance(iterable_arg, nodes.Name):
+ return
+
if not isinstance(node.target, nodes.Tuple) or len(node.target.elts) < 2:
# enumerate() result is being assigned without destructuring
return
@@ -2121,7 +2179,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
# is not redundant, hence we should not report an error.
return
- iterating_object_name = node.iter.args[0].name
+ iterating_object_name = iterable_arg.name
value_variable = node.target.elts[1]
# Store potential violations. These will only be reported if we don't
@@ -2248,15 +2306,13 @@ class RefactoringChecker(checkers.BaseTokenChecker):
return False, confidence
def _get_start_value(self, node: nodes.NodeNG) -> tuple[int | None, Confidence]:
- confidence = HIGH
-
- if isinstance(node, (nodes.Name, nodes.Call)):
+ if isinstance(node, (nodes.Name, nodes.Call, nodes.Attribute)):
inferred = utils.safe_infer(node)
start_val = inferred.value if inferred else None
- confidence = INFERENCE
- elif isinstance(node, nodes.UnaryOp):
- start_val = node.operand.value
- else:
- start_val = node.value
+ return start_val, INFERENCE
+ if isinstance(node, nodes.UnaryOp):
+ return node.operand.value, HIGH
+ if isinstance(node, nodes.Const):
+ return node.value, HIGH
- return start_val, confidence
+ return None, HIGH
diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py
index 7938c27e8..3b18ddbfd 100644
--- a/pylint/checkers/similar.py
+++ b/pylint/checkers/similar.py
@@ -28,7 +28,7 @@ import re
import sys
import warnings
from collections import defaultdict
-from collections.abc import Callable, Generator, Iterable
+from collections.abc import Callable, Generator, Iterable, Sequence
from getopt import getopt
from io import BufferedIOBase, BufferedReader, BytesIO
from itertools import chain, groupby
@@ -49,7 +49,7 @@ import astroid
from astroid import nodes
from pylint.checkers import BaseChecker, BaseRawFileChecker, table_lines_from_stats
-from pylint.reporters.ureports.nodes import Table
+from pylint.reporters.ureports.nodes import Section, Table
from pylint.typing import MessageDefinitionTuple, Options
from pylint.utils import LinterStats, decoding_stream
@@ -185,7 +185,7 @@ class LineSetStartCouple(NamedTuple):
f"<LineSetStartCouple <{self.fst_lineset_index};{self.snd_lineset_index}>>"
)
- def __eq__(self, other) -> bool:
+ def __eq__(self, other: Any) -> bool:
if not isinstance(other, LineSetStartCouple):
return NotImplemented
return (
@@ -382,7 +382,7 @@ class Similar:
self.namespace.ignore_docstrings,
self.namespace.ignore_imports,
self.namespace.ignore_signatures,
- line_enabled_callback=self.linter._is_one_message_enabled # type: ignore[attr-defined]
+ line_enabled_callback=self.linter._is_one_message_enabled
if hasattr(self, "linter")
else None,
)
@@ -585,7 +585,7 @@ def stripped_lines(
line_begins_import = {
lineno: all(is_import for _, is_import in node_is_import_group)
for lineno, node_is_import_group in groupby(
- node_is_import_by_lineno, key=lambda x: x[0]
+ node_is_import_by_lineno, key=lambda x: x[0] # type: ignore[no-any-return]
)
}
current_line_is_import = False
@@ -689,7 +689,7 @@ class LineSet:
line_enabled_callback=line_enabled_callback,
)
- def __str__(self):
+ def __str__(self) -> str:
return f"<Lineset for {self.name}>"
def __len__(self) -> int:
@@ -730,7 +730,7 @@ MSGS: dict[str, MessageDefinitionTuple] = {
def report_similarities(
- sect,
+ sect: Section,
stats: LinterStats,
old_stats: LinterStats | None,
) -> None:
@@ -886,7 +886,7 @@ def usage(status: int = 0) -> NoReturn:
sys.exit(status)
-def Run(argv=None) -> NoReturn:
+def Run(argv: Sequence[str] | None = None) -> NoReturn:
"""Standalone command line access point."""
if argv is None:
argv = sys.argv[1:]
@@ -905,7 +905,7 @@ def Run(argv=None) -> NoReturn:
ignore_docstrings = False
ignore_imports = False
ignore_signatures = False
- opts, args = getopt(argv, s_opts, l_opts)
+ opts, args = getopt(list(argv), s_opts, l_opts)
for opt, val in opts:
if opt in {"-d", "--duplicates"}:
min_lines = int(val)
diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py
index e16377ab7..23209a2bc 100644
--- a/pylint/checkers/spelling.py
+++ b/pylint/checkers/spelling.py
@@ -55,10 +55,10 @@ except ImportError:
pass
def get_tokenizer(
- tag: str | None = None,
- chunkers: list[Chunker] | None = None,
- filters: list[Filter] | None = None,
- ): # pylint: disable=unused-argument
+ tag: str | None = None, # pylint: disable=unused-argument
+ chunkers: list[Chunker] | None = None, # pylint: disable=unused-argument
+ filters: list[Filter] | None = None, # pylint: disable=unused-argument
+ ) -> Filter:
return Filter()
@@ -75,14 +75,14 @@ else:
instr = " To make it work, install the 'python-enchant' package."
-class WordsWithDigitsFilter(Filter):
+class WordsWithDigitsFilter(Filter): # type: ignore[misc]
"""Skips words with digits."""
def _skip(self, word: str) -> bool:
return any(char.isdigit() for char in word)
-class WordsWithUnderscores(Filter):
+class WordsWithUnderscores(Filter): # type: ignore[misc]
"""Skips words with underscores.
They are probably function parameter names.
@@ -92,7 +92,7 @@ class WordsWithUnderscores(Filter):
return "_" in word
-class RegExFilter(Filter):
+class RegExFilter(Filter): # type: ignore[misc]
"""Parent class for filters using regular expressions.
This filter skips any words the match the expression
@@ -128,12 +128,14 @@ class SphinxDirectives(RegExFilter):
_pattern = re.compile(r"^(:([a-z]+)){1,2}:`([^`]+)(`)?")
-class ForwardSlashChunker(Chunker):
+class ForwardSlashChunker(Chunker): # type: ignore[misc]
"""This chunker allows splitting words like 'before/after' into 'before' and
'after'.
"""
- def next(self):
+ _text: str
+
+ def next(self) -> tuple[str, int]:
while True:
if not self._text:
raise StopIteration()
diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py
index 456815cff..6f40116f7 100644
--- a/pylint/checkers/stdlib.py
+++ b/pylint/checkers/stdlib.py
@@ -243,7 +243,12 @@ DEPRECATED_METHODS: dict[int, DeprecationDict] = {
},
(3, 11, 0): {
"locale.getdefaultlocale",
- "unittest.TestLoader.findTestCases",
+ "locale.resetlocale",
+ "re.template",
+ "unittest.findTestCases",
+ "unittest.makeSuite",
+ "unittest.getTestCaseNames",
+ "unittest.TestLoader.loadTestsFromModule",
"unittest.TestLoader.loadTestsFromTestCase",
"unittest.TestLoader.getTestCaseNames",
},
@@ -300,6 +305,9 @@ DEPRECATED_CLASSES: dict[tuple[int, int, int], dict[str, set[str]]] = {
}
},
(3, 11, 0): {
+ "typing": {
+ "Text",
+ },
"webbrowser": {
"MacOSX",
},
@@ -373,7 +381,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
"threading.Thread needs the target function",
"bad-thread-instantiation",
"The warning is emitted when a threading.Thread class "
- "is instantiated without the target function being passed. "
+ "is instantiated without the target function being passed as a kwarg or as a second argument. "
"By default, the first parameter is the group param, not the target param.",
),
"W1507": (
@@ -389,6 +397,20 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
"Env manipulation functions support only string type arguments. "
"See https://docs.python.org/3/library/os.html#os.getenv.",
),
+ "E1519": (
+ "singledispatch decorator should not be used with methods, "
+ "use singledispatchmethod instead.",
+ "singledispatch-method",
+ "singledispatch should decorate functions and not class/instance methods. "
+ "Use singledispatchmethod for those cases.",
+ ),
+ "E1520": (
+ "singledispatchmethod decorator should not be used with functions, "
+ "use singledispatch instead.",
+ "singledispatchmethod-function",
+ "singledispatchmethod should decorate class/instance methods and not functions. "
+ "Use singledispatch for those cases.",
+ ),
"W1508": (
"%s default type is %s. Expected str or None.",
"invalid-envvar-default",
@@ -466,8 +488,14 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
# synced with the config argument deprecated-modules
def _check_bad_thread_instantiation(self, node: nodes.Call) -> None:
- if not node.kwargs and not node.keywords and len(node.args) <= 1:
- self.add_message("bad-thread-instantiation", node=node)
+ func_kwargs = {key.arg for key in node.keywords}
+ if "target" in func_kwargs:
+ return
+
+ if len(node.args) < 2 and (not node.kwargs or "target" not in func_kwargs):
+ self.add_message(
+ "bad-thread-instantiation", node=node, confidence=interfaces.HIGH
+ )
def _check_for_preexec_fn_in_popen(self, node: nodes.Call) -> None:
if node.keywords:
@@ -557,10 +585,15 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
for value in node.values:
self._check_datetime(value)
- @utils.only_required_for_messages("method-cache-max-size-none")
+ @utils.only_required_for_messages(
+ "method-cache-max-size-none",
+ "singledispatch-method",
+ "singledispatchmethod-function",
+ )
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
if node.decorators and isinstance(node.parent, nodes.ClassDef):
self._check_lru_cache_decorators(node.decorators)
+ self._check_dispatch_decorators(node)
def _check_lru_cache_decorators(self, decorators: nodes.Decorators) -> None:
"""Check if instance methods are decorated with functools.lru_cache."""
@@ -599,6 +632,36 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
confidence=interfaces.INFERENCE,
)
+ def _check_dispatch_decorators(self, node: nodes.FunctionDef) -> None:
+ decorators_map: dict[str, tuple[nodes.NodeNG, interfaces.Confidence]] = {}
+
+ for decorator in node.decorators.nodes:
+ if isinstance(decorator, nodes.Name) and decorator.name:
+ decorators_map[decorator.name] = (decorator, interfaces.HIGH)
+ elif utils.is_registered_in_singledispatch_function(node):
+ decorators_map["singledispatch"] = (decorator, interfaces.INFERENCE)
+ elif utils.is_registered_in_singledispatchmethod_function(node):
+ decorators_map["singledispatchmethod"] = (
+ decorator,
+ interfaces.INFERENCE,
+ )
+
+ if "singledispatch" in decorators_map and "classmethod" in decorators_map:
+ self.add_message(
+ "singledispatch-method",
+ node=decorators_map["singledispatch"][0],
+ confidence=decorators_map["singledispatch"][1],
+ )
+ elif (
+ "singledispatchmethod" in decorators_map
+ and "staticmethod" in decorators_map
+ ):
+ self.add_message(
+ "singledispatchmethod-function",
+ node=decorators_map["singledispatchmethod"][0],
+ confidence=decorators_map["singledispatchmethod"][1],
+ )
+
def _check_redundant_assert(self, node: nodes.Call, infer: InferenceResult) -> None:
if (
isinstance(infer, astroid.BoundMethod)
diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py
index e5aa70b56..4afe32a16 100644
--- a/pylint/checkers/strings.py
+++ b/pylint/checkers/strings.py
@@ -437,7 +437,7 @@ class StringFormatChecker(BaseChecker):
self._check_new_format(node, func)
def _detect_vacuous_formatting(
- self, node: nodes.Call, positional_arguments
+ self, node: nodes.Call, positional_arguments: list[SuccessfulInferenceResult]
) -> None:
counter = collections.Counter(
arg.name for arg in positional_arguments if isinstance(arg, nodes.Name)
@@ -494,7 +494,7 @@ class StringFormatChecker(BaseChecker):
check_args = False
# Consider "{[0]} {[1]}" as num_args.
- num_args += sum(1 for field in named_fields if field == "")
+ num_args += sum(1 for field in named_fields if not field)
if named_fields:
for field in named_fields:
if field and field not in named_arguments:
@@ -509,7 +509,7 @@ class StringFormatChecker(BaseChecker):
# num_args can be 0 if manual_pos is not.
num_args = num_args or manual_pos
if positional_arguments or num_args:
- empty = any(field == "" for field in named_fields)
+ empty = not all(field for field in named_fields)
if named_arguments or empty:
# Verify the required number of positional arguments
# only if the .format got at least one keyword argument.
@@ -534,7 +534,10 @@ class StringFormatChecker(BaseChecker):
self._check_new_format_specifiers(node, fields, named_arguments)
def _check_new_format_specifiers(
- self, node: nodes.Call, fields: list[tuple[str, list[tuple[bool, str]]]], named
+ self,
+ node: nodes.Call,
+ fields: list[tuple[str, list[tuple[bool, str]]]],
+ named: dict[str, SuccessfulInferenceResult],
) -> None:
"""Check attribute and index access in the format
string ("{0.a}" and "{0[a]}").
@@ -543,7 +546,7 @@ class StringFormatChecker(BaseChecker):
for key, specifiers in fields:
# Obtain the argument. If it can't be obtained
# or inferred, skip this check.
- if key == "":
+ if not key:
# {[0]} will have an unnamed argument, defaulting
# to 0. It will not be present in `named`, so use the value
# 0 for it.
@@ -831,16 +834,16 @@ class StringConstantChecker(BaseTokenChecker, BaseRawFileChecker):
def process_string_token(self, token: str, start_row: int, start_col: int) -> None:
quote_char = None
- index = None
- for index, char in enumerate(token):
+ for _index, char in enumerate(token):
if char in "'\"":
quote_char = char
break
if quote_char is None:
return
-
- prefix = token[:index].lower() # markers like u, b, r.
- after_prefix = token[index:]
+ # pylint: disable=undefined-loop-variable
+ prefix = token[:_index].lower() # markers like u, b, r.
+ after_prefix = token[_index:]
+ # pylint: enable=undefined-loop-variable
# Chop off quotes
quote_length = (
3 if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char else 1
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index d97f352f6..192bccbbb 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -13,15 +13,16 @@ import re
import shlex
import sys
import types
-from collections.abc import Callable, Iterator, Sequence
+from collections.abc import Callable, Iterable, Iterator, Sequence
from functools import singledispatch
from re import Pattern
-from typing import TYPE_CHECKING, Any, Union
+from typing import TYPE_CHECKING, Any, TypeVar, Union
import astroid
import astroid.exceptions
import astroid.helpers
-from astroid import bases, nodes
+from astroid import arguments, bases, nodes
+from astroid.typing import InferenceResult, SuccessfulInferenceResult
from pylint.checkers import BaseChecker, utils
from pylint.checkers.utils import (
@@ -47,7 +48,7 @@ from pylint.checkers.utils import (
supports_membership_test,
supports_setitem,
)
-from pylint.interfaces import INFERENCE
+from pylint.interfaces import HIGH, INFERENCE
from pylint.typing import MessageDefinitionTuple
if sys.version_info >= (3, 8):
@@ -68,6 +69,8 @@ CallableObjects = Union[
nodes.ClassDef,
]
+_T = TypeVar("_T")
+
STR_FORMAT = {"builtins.str.format"}
ASYNCIO_COROUTINE = "asyncio.coroutines.coroutine"
BUILTIN_TUPLE = "builtins.tuple"
@@ -85,16 +88,16 @@ class VERSION_COMPATIBLE_OVERLOAD:
VERSION_COMPATIBLE_OVERLOAD_SENTINEL = VERSION_COMPATIBLE_OVERLOAD()
-def _unflatten(iterable):
+def _unflatten(iterable: Iterable[_T]) -> Iterator[_T]:
for index, elem in enumerate(iterable):
if isinstance(elem, Sequence) and not isinstance(elem, str):
yield from _unflatten(elem)
elif elem and not index:
# We're interested only in the first element.
- yield elem
+ yield elem # type: ignore[misc]
-def _flatten_container(iterable):
+def _flatten_container(iterable: Iterable[_T]) -> Iterator[_T]:
# Flatten nested containers into a single iterable
for item in iterable:
if isinstance(item, (list, tuple, types.GeneratorType)):
@@ -103,7 +106,12 @@ def _flatten_container(iterable):
yield item
-def _is_owner_ignored(owner, attrname, ignored_classes, ignored_modules):
+def _is_owner_ignored(
+ owner: SuccessfulInferenceResult,
+ attrname: str | None,
+ ignored_classes: Iterable[str],
+ ignored_modules: Iterable[str],
+) -> bool:
"""Check if the given owner should be ignored.
This will verify if the owner's module is in *ignored_modules*
@@ -125,15 +133,15 @@ def _is_owner_ignored(owner, attrname, ignored_classes, ignored_modules):
@singledispatch
-def _node_names(node):
+def _node_names(node: SuccessfulInferenceResult) -> Iterable[str]:
if not hasattr(node, "locals"):
return []
- return node.locals.keys()
+ return node.locals.keys() # type: ignore[no-any-return]
@_node_names.register(nodes.ClassDef)
@_node_names.register(astroid.Instance)
-def _(node):
+def _(node: nodes.ClassDef | bases.Instance) -> Iterable[str]:
values = itertools.chain(node.instance_attrs.keys(), node.locals.keys())
try:
@@ -145,7 +153,7 @@ def _(node):
return itertools.chain(values, other_values)
-def _string_distance(seq1, seq2):
+def _string_distance(seq1: str, seq2: str) -> int:
seq2_length = len(seq2)
row = list(range(1, seq2_length + 1)) + [0]
@@ -163,20 +171,25 @@ def _string_distance(seq1, seq2):
return row[seq2_length - 1]
-def _similar_names(owner, attrname, distance_threshold, max_choices):
+def _similar_names(
+ owner: SuccessfulInferenceResult,
+ attrname: str | None,
+ distance_threshold: int,
+ max_choices: int,
+) -> list[str]:
"""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.
"""
- possible_names = []
+ possible_names: list[tuple[str, int]] = []
names = _node_names(owner)
for name in names:
if name == attrname:
continue
- distance = _string_distance(attrname, name)
+ distance = _string_distance(attrname or "", name)
if distance <= distance_threshold:
possible_names.append((name, distance))
@@ -191,7 +204,12 @@ def _similar_names(owner, attrname, distance_threshold, max_choices):
return sorted(picked)
-def _missing_member_hint(owner, attrname, distance_threshold, max_choices):
+def _missing_member_hint(
+ owner: SuccessfulInferenceResult,
+ attrname: str | None,
+ distance_threshold: int,
+ max_choices: int,
+) -> str:
names = _similar_names(owner, attrname, distance_threshold, max_choices)
if not names:
# No similar name.
@@ -356,6 +374,12 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"(i.e. doesn't define __hash__ method).",
{"old_names": [("E1140", "unhashable-dict-key")]},
),
+ "E1144": (
+ "Slice step cannot be 0",
+ "invalid-slice-step",
+ "Used when a slice step is 0 and the object doesn't implement "
+ "a custom __getitem__ method.",
+ ),
"W1113": (
"Keyword argument before variable positional arguments list "
"in the definition of %s function",
@@ -397,13 +421,13 @@ SEQUENCE_TYPES = {
def _emit_no_member(
- node,
- owner,
- owner_name,
+ node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr,
+ owner: InferenceResult,
+ owner_name: str | None,
mixin_class_rgx: Pattern[str],
- ignored_mixins=True,
- ignored_none=True,
-):
+ ignored_mixins: bool = True,
+ ignored_none: bool = True,
+) -> bool:
"""Try to see if no-member should be emitted for the given owner.
The following cases are ignored:
@@ -640,7 +664,11 @@ def _determine_callable(
raise ValueError
-def _has_parent_of_type(node, node_type, statement):
+def _has_parent_of_type(
+ node: nodes.Call,
+ node_type: nodes.Keyword | nodes.Starred,
+ statement: nodes.Statement,
+) -> bool:
"""Check if the given node has a parent of the given type."""
parent = node.parent
while not isinstance(parent, node_type) and statement.parent_of(parent):
@@ -648,9 +676,9 @@ def _has_parent_of_type(node, node_type, statement):
return isinstance(parent, node_type)
-def _no_context_variadic_keywords(node, scope):
+def _no_context_variadic_keywords(node: nodes.Call, scope: nodes.Lambda) -> bool:
statement = node.statement(future=True)
- variadics = ()
+ variadics = []
if isinstance(scope, nodes.Lambda) and not isinstance(scope, nodes.FunctionDef):
variadics = list(node.keywords or []) + node.kwargs
@@ -663,12 +691,17 @@ def _no_context_variadic_keywords(node, scope):
return _no_context_variadic(node, scope.args.kwarg, nodes.Keyword, variadics)
-def _no_context_variadic_positional(node, scope):
+def _no_context_variadic_positional(node: nodes.Call, scope: nodes.Lambda) -> bool:
variadics = node.starargs + node.kwargs
return _no_context_variadic(node, scope.args.vararg, nodes.Starred, variadics)
-def _no_context_variadic(node, variadic_name, variadic_type, variadics):
+def _no_context_variadic(
+ node: nodes.Call,
+ variadic_name: str | None,
+ variadic_type: nodes.Keyword | nodes.Starred,
+ variadics: list[nodes.Keyword | nodes.Starred],
+) -> bool:
"""Verify if the given call node has variadic nodes without context.
This is a workaround for handling cases of nested call functions
@@ -714,7 +747,7 @@ def _no_context_variadic(node, variadic_name, variadic_type, variadics):
return False
-def _is_invalid_metaclass(metaclass):
+def _is_invalid_metaclass(metaclass: nodes.ClassDef) -> bool:
try:
mro = metaclass.mro()
except NotImplementedError:
@@ -726,7 +759,9 @@ def _is_invalid_metaclass(metaclass):
return False
-def _infer_from_metaclass_constructor(cls, func: nodes.FunctionDef):
+def _infer_from_metaclass_constructor(
+ cls: nodes.ClassDef, func: nodes.FunctionDef
+) -> InferenceResult | None:
"""Try to infer what the given *func* constructor is building.
:param astroid.FunctionDef func:
@@ -763,14 +798,15 @@ def _infer_from_metaclass_constructor(cls, func: nodes.FunctionDef):
return inferred or None
-def _is_c_extension(module_node):
+def _is_c_extension(module_node: InferenceResult) -> bool:
return (
- not astroid.modutils.is_standard_module(module_node.name)
+ isinstance(module_node, nodes.Module)
+ and not astroid.modutils.is_standard_module(module_node.name)
and not module_node.fully_defined()
)
-def _is_invalid_isinstance_type(arg):
+def _is_invalid_isinstance_type(arg: nodes.NodeNG) -> bool:
# Return True if we are sure that arg is not a type
inferred = utils.safe_infer(arg)
if not inferred:
@@ -945,11 +981,11 @@ accessed. Python regular expressions are accepted.",
self._mixin_class_rgx = self.linter.config.mixin_class_rgx
@cached_property
- def _suggestion_mode(self):
- return self.linter.config.suggestion_mode
+ def _suggestion_mode(self) -> bool:
+ return self.linter.config.suggestion_mode # type: ignore[no-any-return]
@cached_property
- def _compiled_generated_members(self) -> tuple[Pattern, ...]:
+ def _compiled_generated_members(self) -> tuple[Pattern[str], ...]:
# do this lazily since config not fully initialized in __init__
# generated_members may contain regular expressions
# (surrounded by quote `"` and followed by a comma `,`)
@@ -973,15 +1009,15 @@ accessed. Python regular expressions are accepted.",
@only_required_for_messages("invalid-metaclass")
def visit_classdef(self, node: nodes.ClassDef) -> None:
- def _metaclass_name(metaclass):
+ def _metaclass_name(metaclass: InferenceResult) -> str | None:
# pylint: disable=unidiomatic-typecheck
if isinstance(metaclass, (nodes.ClassDef, nodes.FunctionDef)):
- return metaclass.name
+ return metaclass.name # type: ignore[no-any-return]
if type(metaclass) is bases.Instance:
# Really do mean type, not isinstance, since subclasses of bases.Instance
# like Const or Dict should use metaclass.as_string below.
return str(metaclass)
- return metaclass.as_string()
+ return metaclass.as_string() # type: ignore[no-any-return]
metaclass = node.declared_metaclass()
if not metaclass:
@@ -1040,9 +1076,9 @@ accessed. Python regular expressions are accepted.",
return
# list of (node, nodename) which are missing the attribute
- missingattr = set()
+ missingattr: set[tuple[SuccessfulInferenceResult, str | None]] = set()
- non_opaque_inference_results = [
+ non_opaque_inference_results: list[SuccessfulInferenceResult] = [
owner
for owner in inferred
if owner is not astroid.Uninferable and not isinstance(owner, nodes.Unknown)
@@ -1105,6 +1141,9 @@ accessed. Python regular expressions are accepted.",
try:
if isinstance(
attr_node.statement(future=True), nodes.AugAssign
+ ) or (
+ isinstance(attr_parent, nodes.Assign)
+ and utils.is_augmented_assign(attr_parent)[0]
):
continue
except astroid.exceptions.StatementMissing:
@@ -1139,7 +1178,11 @@ accessed. Python regular expressions are accepted.",
confidence=INFERENCE,
)
- def _get_nomember_msgid_hint(self, node, owner):
+ def _get_nomember_msgid_hint(
+ self,
+ node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr,
+ owner: SuccessfulInferenceResult,
+ ) -> tuple[Literal["c-extension-no-member", "no-member"], str]:
suggestions_are_possible = self._suggestion_mode and isinstance(
owner, nodes.Module
)
@@ -1157,7 +1200,7 @@ accessed. Python regular expressions are accepted.",
)
else:
hint = ""
- return msg, hint
+ return msg, hint # type: ignore[return-value]
@only_required_for_messages(
"assignment-from-no-return",
@@ -1240,7 +1283,7 @@ accessed. Python regular expressions are accepted.",
and isinstance(utils.safe_infer(node.func.expr), nodes.List)
)
- def _check_dundername_is_string(self, node) -> None:
+ def _check_dundername_is_string(self, node: nodes.Assign) -> None:
"""Check a string is assigned to self.__name__."""
# Check the left-hand side of the assignment is <something>.__name__
@@ -1261,7 +1304,7 @@ accessed. Python regular expressions are accepted.",
# Add the message
self.add_message("non-str-assignment-to-dunder-name", node=node)
- def _check_uninferable_call(self, node):
+ def _check_uninferable_call(self, node: nodes.Call) -> None:
"""Check that the given uninferable Call node does not
call an actual function.
"""
@@ -1313,7 +1356,13 @@ accessed. Python regular expressions are accepted.",
self.add_message("not-callable", node=node, args=node.func.as_string())
- def _check_argument_order(self, node, call_site, called, called_param_names):
+ def _check_argument_order(
+ self,
+ node: nodes.Call,
+ call_site: arguments.CallSite,
+ called: CallableObjects,
+ called_param_names: list[str | None],
+ ) -> None:
"""Match the supplied argument names against the function parameters.
Warn if some argument names are not in the same order as they are in
@@ -1353,7 +1402,7 @@ accessed. Python regular expressions are accepted.",
if calling_parg_names != called_param_names[: len(calling_parg_names)]:
self.add_message("arguments-out-of-order", node=node, args=())
- def _check_isinstance_args(self, node):
+ def _check_isinstance_args(self, node: nodes.Call) -> None:
if len(node.args) != 2:
# isinstance called with wrong number of args
return
@@ -1449,7 +1498,7 @@ accessed. Python regular expressions are accepted.",
# Analyze the list of formal parameters.
args = list(itertools.chain(called.args.posonlyargs or (), called.args.args))
num_mandatory_parameters = len(args) - len(called.args.defaults)
- parameters: list[list[Any]] = []
+ parameters: list[tuple[tuple[str | None, nodes.NodeNG | None], bool]] = []
parameter_name_to_index = {}
for i, arg in enumerate(args):
if isinstance(arg, nodes.Tuple):
@@ -1466,7 +1515,7 @@ accessed. Python regular expressions are accepted.",
defval = called.args.defaults[i - num_mandatory_parameters]
else:
defval = None
- parameters.append([(name, defval), False])
+ parameters.append(((name, defval), False))
kwparams = {}
for i, arg in enumerate(called.args.kwonlyargs):
@@ -1484,7 +1533,7 @@ accessed. Python regular expressions are accepted.",
# 1. Match the positional arguments.
for i in range(num_positional_args):
if i < len(parameters):
- parameters[i][1] = True
+ parameters[i] = (parameters[i][0], True)
elif called.args.vararg is not None:
# The remaining positional arguments get assigned to the *args
# parameter.
@@ -1515,7 +1564,7 @@ accessed. Python regular expressions are accepted.",
args=(keyword, callable_name),
)
else:
- parameters[i][1] = True
+ parameters[i] = (parameters[i][0], True)
elif keyword in kwparams:
if kwparams[keyword][1]:
# Duplicate definition of function parameter.
@@ -1541,11 +1590,14 @@ accessed. Python regular expressions are accepted.",
# 3. Match the **kwargs, if any.
if node.kwargs:
- for i, [(name, defval), assigned] in enumerate(parameters):
+ # TODO: It's possible to remove this disable by using dummy-variables-rgx
+ # see https://github.com/PyCQA/pylint/pull/7697#discussion_r1010832518
+ # pylint: disable-next=unused-variable
+ for i, [(name, _defval), _assigned] in enumerate(parameters):
# Assume that *kwargs provides values for all remaining
# unassigned named parameters.
if name is not None:
- parameters[i][1] = True
+ parameters[i] = (parameters[i][0], True)
else:
# **kwargs can't assign to tuples.
pass
@@ -1612,7 +1664,7 @@ accessed. Python regular expressions are accepted.",
return True
- def _check_invalid_sequence_index(self, subscript: nodes.Subscript):
+ def _check_invalid_sequence_index(self, subscript: nodes.Subscript) -> None:
# Look for index operations where the parent is a sequence type.
# If the types can be determined, only allow indices to be int,
# slice or instances with __index__.
@@ -1654,13 +1706,7 @@ accessed. Python regular expressions are accepted.",
):
return None
- # For ExtSlice objects coming from visit_extslice, no further
- # inference is necessary, since if we got this far the ExtSlice
- # is an error.
- if isinstance(subscript.value, nodes.ExtSlice):
- index_type = subscript.value
- else:
- index_type = safe_infer(subscript.slice)
+ index_type = safe_infer(subscript.slice)
if index_type is None or index_type is astroid.Uninferable:
return None
# Constants must be of type int
@@ -1717,14 +1763,6 @@ accessed. Python regular expressions are accepted.",
self.add_message("not-callable", node=node, args=node.func.as_string())
- @only_required_for_messages("invalid-sequence-index")
- def visit_extslice(self, node: nodes.ExtSlice) -> None:
- if not node.parent or not hasattr(node.parent, "value"):
- return None
- # Check extended slice objects as if they were used as a sequence
- # index to check if the object being sliced can support them
- return self._check_invalid_sequence_index(node.parent)
-
def _check_invalid_slice_index(self, node: nodes.Slice) -> None:
# Check the type of each part of the slice
invalid_slices_nodes: list[nodes.NodeNG] = []
@@ -1753,14 +1791,16 @@ accessed. Python regular expressions are accepted.",
pass
invalid_slices_nodes.append(index)
- if not invalid_slices_nodes:
+ invalid_slice_step = (
+ node.step and isinstance(node.step, nodes.Const) and node.step.value == 0
+ )
+
+ if not (invalid_slices_nodes or invalid_slice_step):
return
# Anything else is an error, unless the object that is indexed
# is a custom object, which knows how to handle this kind of slices
parent = node.parent
- if isinstance(parent, nodes.ExtSlice):
- parent = parent.parent
if isinstance(parent, nodes.Subscript):
inferred = safe_infer(parent.value)
if inferred is None or inferred is astroid.Uninferable:
@@ -1773,11 +1813,19 @@ accessed. Python regular expressions are accepted.",
astroid.objects.FrozenSet,
nodes.Set,
)
- if not isinstance(inferred, known_objects):
+ if not (
+ isinstance(inferred, known_objects)
+ or isinstance(inferred, nodes.Const)
+ and inferred.pytype() in {"builtins.str", "builtins.bytes"}
+ or isinstance(inferred, astroid.bases.Instance)
+ and inferred.pytype() == "builtins.range"
+ ):
# Might be an instance that knows how to handle this slice object
return
for snode in invalid_slices_nodes:
self.add_message("invalid-slice-index", node=snode)
+ if invalid_slice_step:
+ self.add_message("invalid-slice-step", node=node.step, confidence=HIGH)
@only_required_for_messages("not-context-manager")
def visit_with(self, node: nodes.With) -> None:
@@ -1903,7 +1951,7 @@ accessed. Python regular expressions are accepted.",
if not allowed_nested_syntax:
self._check_unsupported_alternative_union_syntax(node)
- def _includes_version_compatible_overload(self, attrs: list):
+ def _includes_version_compatible_overload(self, attrs: list[nodes.NodeNG]) -> bool:
"""Check if a set of overloads of an operator includes one that
can be relied upon for our configured Python version.
@@ -1973,7 +2021,7 @@ accessed. Python regular expressions are accepted.",
"""Detect TypeErrors for augmented binary arithmetic operands."""
self._check_binop_errors(node)
- def _check_binop_errors(self, node):
+ def _check_binop_errors(self, node: nodes.BinOp | nodes.AugAssign) -> None:
for error in node.type_errors():
# Let the error customize its output.
if any(
@@ -1983,7 +2031,7 @@ accessed. Python regular expressions are accepted.",
continue
self.add_message("unsupported-binary-operation", args=str(error), node=node)
- def _check_membership_test(self, node):
+ def _check_membership_test(self, node: nodes.NodeNG) -> None:
if is_inside_abstract_class(node):
return
if is_comprehension(node):
@@ -2034,6 +2082,7 @@ accessed. Python regular expressions are accepted.",
"unhashable-member",
"invalid-sequence-index",
"invalid-slice-index",
+ "invalid-slice-step",
)
def visit_subscript(self, node: nodes.Subscript) -> None:
self._check_invalid_sequence_index(node)
@@ -2161,7 +2210,7 @@ class IterableChecker(BaseChecker):
}
@staticmethod
- def _is_asyncio_coroutine(node):
+ def _is_asyncio_coroutine(node: nodes.NodeNG) -> bool:
if not isinstance(node, nodes.Call):
return False
@@ -2179,7 +2228,7 @@ class IterableChecker(BaseChecker):
return True
return False
- def _check_iterable(self, node, check_async=False):
+ def _check_iterable(self, node: nodes.NodeNG, check_async: bool = False) -> None:
if is_inside_abstract_class(node):
return
inferred = safe_infer(node)
@@ -2188,7 +2237,7 @@ class IterableChecker(BaseChecker):
if not is_iterable(inferred, check_async=check_async):
self.add_message("not-an-iterable", args=node.as_string(), node=node)
- def _check_mapping(self, node):
+ def _check_mapping(self, node: nodes.NodeNG) -> None:
if is_inside_abstract_class(node):
return
if isinstance(node, nodes.DictComp):
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 0ccf1d883..a2a0c1b37 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -231,6 +231,12 @@ SUBSCRIPTABLE_CLASSES_PEP585 = frozenset(
)
)
+SINGLETON_VALUES = {True, False, None}
+
+TERMINATING_FUNCS_QNAMES = frozenset(
+ {"_sitebuiltins.Quitter", "sys.exit", "posix._exit", "nt._exit"}
+)
+
class NoSuchArgumentError(Exception):
pass
@@ -246,6 +252,7 @@ def is_inside_lambda(node: nodes.NodeNG) -> bool:
"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",
DeprecationWarning,
+ stacklevel=2,
)
return any(isinstance(parent, nodes.Lambda) for parent in node.node_ancestors())
@@ -279,12 +286,12 @@ SPECIAL_BUILTINS = ("__builtins__",) # '__path__', '__file__')
def is_builtin_object(node: nodes.NodeNG) -> bool:
"""Returns True if the given node is an object from the __builtin__ module."""
- return node and node.root().name == "builtins"
+ return node and node.root().name == "builtins" # type: ignore[no-any-return]
def is_builtin(name: str) -> bool:
"""Return true if <name> could be considered as a builtin defined by python."""
- return name in builtins or name in SPECIAL_BUILTINS # type: ignore[attr-defined]
+ return name in builtins or name in SPECIAL_BUILTINS # type: ignore[operator]
def is_defined_in_scope(
@@ -359,6 +366,14 @@ def is_defined_before(var_node: nodes.Name) -> bool:
continue
defnode_scope = defnode.scope()
if isinstance(defnode_scope, COMP_NODE_TYPES + (nodes.Lambda,)):
+ # Avoid the case where var_node_scope is a nested function
+ # FunctionDef is a Lambda until https://github.com/PyCQA/astroid/issues/291
+ if isinstance(defnode_scope, nodes.FunctionDef):
+ var_node_scope = var_node.scope()
+ if var_node_scope is not defnode_scope and isinstance(
+ var_node_scope, nodes.FunctionDef
+ ):
+ return False
return True
if defnode.lineno < var_node.lineno:
return True
@@ -480,7 +495,7 @@ def only_required_for_messages(
def store_messages(
func: AstCallbackMethod[_CheckerT, _NodeT]
) -> AstCallbackMethod[_CheckerT, _NodeT]:
- setattr(func, "checks_msgs", messages)
+ func.checks_msgs = messages # type: ignore[attr-defined]
return func
return store_messages
@@ -499,6 +514,7 @@ def check_messages(
"utils.check_messages will be removed in favour of calling "
"utils.only_required_for_messages in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
return only_required_for_messages(*messages)
@@ -598,7 +614,7 @@ def split_format_field_names(
format_string: str,
) -> tuple[str, Iterable[tuple[bool, str]]]:
try:
- return _string.formatter_field_name_split(format_string)
+ return _string.formatter_field_name_split(format_string) # type: ignore[no-any-return]
except ValueError as e:
raise IncompleteFormatString() from e
@@ -782,7 +798,7 @@ def error_of_type(
expected_errors = {stringify_error(error) for error in error_type}
if not handler.type:
return False
- return handler.catch(expected_errors)
+ return handler.catch(expected_errors) # type: ignore[no-any-return]
def decorated_with_property(node: nodes.FunctionDef) -> bool:
@@ -798,7 +814,7 @@ def decorated_with_property(node: nodes.FunctionDef) -> bool:
return False
-def _is_property_kind(node, *kinds: str) -> bool:
+def _is_property_kind(node: nodes.NodeNG, *kinds: str) -> bool:
if not isinstance(node, (astroid.UnboundMethod, nodes.FunctionDef)):
return False
if node.decorators:
@@ -808,17 +824,17 @@ def _is_property_kind(node, *kinds: str) -> bool:
return False
-def is_property_setter(node) -> bool:
+def is_property_setter(node: nodes.NodeNG) -> bool:
"""Check if the given node is a property setter."""
return _is_property_kind(node, "setter")
-def is_property_deleter(node) -> bool:
+def is_property_deleter(node: nodes.NodeNG) -> bool:
"""Check if the given node is a property deleter."""
return _is_property_kind(node, "deleter")
-def is_property_setter_or_deleter(node) -> bool:
+def is_property_setter_or_deleter(node: nodes.NodeNG) -> bool:
"""Check if the given node is either a property setter or a deleter."""
return _is_property_kind(node, "setter", "deleter")
@@ -1404,7 +1420,7 @@ def has_known_bases(
) -> bool:
"""Return true if all base classes of a class could be inferred."""
try:
- return klass._all_bases_known
+ return klass._all_bases_known # type: ignore[no-any-return]
except AttributeError:
pass
for base in klass.bases:
@@ -1462,11 +1478,15 @@ def is_registered_in_singledispatch_function(node: nodes.FunctionDef) -> bool:
decorators = node.decorators.nodes if node.decorators else []
for decorator in decorators:
- # func.register are function calls
- if not isinstance(decorator, nodes.Call):
+ # func.register are function calls or register attributes
+ # when the function is annotated with types
+ if isinstance(decorator, nodes.Call):
+ func = decorator.func
+ elif isinstance(decorator, nodes.Attribute):
+ func = decorator
+ else:
continue
- func = decorator.func
if not isinstance(func, nodes.Attribute) or func.attrname != "register":
continue
@@ -1481,6 +1501,43 @@ def is_registered_in_singledispatch_function(node: nodes.FunctionDef) -> bool:
return False
+def find_inferred_fn_from_register(node: nodes.NodeNG) -> nodes.FunctionDef | None:
+ # func.register are function calls or register attributes
+ # when the function is annotated with types
+ if isinstance(node, nodes.Call):
+ func = node.func
+ elif isinstance(node, nodes.Attribute):
+ func = node
+ else:
+ return None
+
+ if not isinstance(func, nodes.Attribute) or func.attrname != "register":
+ return None
+
+ func_def = safe_infer(func.expr)
+ if not isinstance(func_def, nodes.FunctionDef):
+ return None
+
+ return func_def
+
+
+def is_registered_in_singledispatchmethod_function(node: nodes.FunctionDef) -> bool:
+ """Check if the given function node is a singledispatchmethod function."""
+
+ singledispatchmethod_qnames = (
+ "functools.singledispatchmethod",
+ "singledispatch.singledispatchmethod",
+ )
+
+ decorators = node.decorators.nodes if node.decorators else []
+ for decorator in decorators:
+ func_def = find_inferred_fn_from_register(decorator)
+ if func_def:
+ return decorated_with(func_def, singledispatchmethod_qnames)
+
+ return False
+
+
def get_node_last_lineno(node: nodes.NodeNG) -> int:
"""Get the last lineno of the given node.
@@ -1502,7 +1559,7 @@ def get_node_last_lineno(node: nodes.NodeNG) -> int:
if getattr(node, "body", False):
return get_node_last_lineno(node.body[-1])
# Not a compound statement
- return node.lineno
+ return node.lineno # type: ignore[no-any-return]
def is_postponed_evaluation_enabled(node: nodes.NodeNG) -> bool:
@@ -1523,6 +1580,7 @@ def is_class_subscriptable_pep585_with_postponed_evaluation_enabled(
"Use 'is_postponed_evaluation_enabled(node) and "
"is_node_in_type_annotation_context(node)' instead.",
DeprecationWarning,
+ stacklevel=2,
)
return (
is_postponed_evaluation_enabled(node)
@@ -1695,14 +1753,14 @@ def get_iterating_dictionary_name(node: nodes.For | nodes.Comprehension) -> str
inferred = safe_infer(node.iter.func)
if not isinstance(inferred, astroid.BoundMethod):
return None
- return node.iter.as_string().rpartition(".keys")[0]
+ return node.iter.as_string().rpartition(".keys")[0] # type: ignore[no-any-return]
# Is it a dictionary?
if isinstance(node.iter, (nodes.Name, nodes.Attribute)):
inferred = safe_infer(node.iter)
if not isinstance(inferred, nodes.Dict):
return None
- return node.iter.as_string()
+ return node.iter.as_string() # type: ignore[no-any-return]
return None
@@ -1738,7 +1796,7 @@ def get_import_name(importnode: ImportNode, modname: str | None) -> str | None:
root = importnode.root()
if isinstance(root, nodes.Module):
try:
- return root.relative_to_absolute_name(modname, level=importnode.level)
+ return root.relative_to_absolute_name(modname, level=importnode.level) # type: ignore[no-any-return]
except TooManyLevelsError:
return modname
return modname
@@ -1841,11 +1899,20 @@ def is_empty_str_literal(node: nodes.NodeNG | None) -> 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 nodes.Return that returns a constant boolean."""
return (
isinstance(node, nodes.Return)
and isinstance(node.value, nodes.Const)
- and node.value.value in {True, False}
+ and isinstance(node.value.value, bool)
+ )
+
+
+def assigned_bool(node: nodes.NodeNG) -> bool:
+ """Returns true if a node is a nodes.Assign that returns a constant boolean."""
+ return (
+ isinstance(node, nodes.Assign)
+ and isinstance(node.value, nodes.Const)
+ and isinstance(node.value.value, bool)
)
@@ -1855,7 +1922,7 @@ def get_node_first_ancestor_of_type(
"""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
+ return ancestor # type: ignore[no-any-return]
return None
@@ -1906,24 +1973,27 @@ def in_type_checking_block(node: nodes.NodeNG) -> bool:
return False
-def is_typing_literal(node: nodes.NodeNG) -> bool:
- """Check if a node refers to typing.Literal."""
+def is_typing_member(node: nodes.NodeNG, names_to_check: tuple[str, ...]) -> bool:
+ """Check if `node` is a member of the `typing` module and has one of the names from
+ `names_to_check`.
+ """
if isinstance(node, nodes.Name):
try:
import_from = node.lookup(node.name)[1][0]
except IndexError:
return False
+
if isinstance(import_from, nodes.ImportFrom):
return (
import_from.modname == "typing"
- and import_from.real_name(node.name) == "Literal"
+ and import_from.real_name(node.name) in names_to_check
)
elif isinstance(node, nodes.Attribute):
inferred_module = safe_infer(node.expr)
return (
isinstance(inferred_module, nodes.Module)
and inferred_module.name == "typing"
- and node.attrname == "Literal"
+ and node.attrname in names_to_check
)
return False
@@ -1969,6 +2039,71 @@ def is_hashable(node: nodes.NodeNG) -> bool:
return True
+def get_full_name_of_attribute(node: nodes.Attribute | nodes.AssignAttr) -> str:
+ """Return the full name of an attribute and the classes it belongs to.
+
+ For example: "Class1.Class2.attr"
+ """
+ parent = node.parent
+ ret = node.attrname or ""
+ while isinstance(parent, (nodes.Attribute, nodes.Name)):
+ if isinstance(parent, nodes.Attribute):
+ ret = f"{parent.attrname}.{ret}"
+ else:
+ ret = f"{parent.name}.{ret}"
+ parent = parent.parent
+ return ret
+
+
+def _is_target_name_in_binop_side(
+ target: nodes.AssignName | nodes.AssignAttr, side: nodes.NodeNG | None
+) -> bool:
+ """Determine whether the target name-like node is referenced in the side node."""
+ if isinstance(side, nodes.Name):
+ if isinstance(target, nodes.AssignName):
+ return target.name == side.name # type: ignore[no-any-return]
+ return False
+ if isinstance(side, nodes.Attribute) and isinstance(target, nodes.AssignAttr):
+ return get_full_name_of_attribute(target) == get_full_name_of_attribute(side)
+ return False
+
+
+def is_augmented_assign(node: nodes.Assign) -> tuple[bool, str]:
+ """Determine if the node is assigning itself (with modifications) to itself.
+
+ For example: x = 1 + x
+ """
+ if not isinstance(node.value, nodes.BinOp):
+ return False, ""
+
+ binop = node.value
+ target = node.targets[0]
+
+ if not isinstance(target, (nodes.AssignName, nodes.AssignAttr)):
+ return False, ""
+
+ # We don't want to catch x = "1" + x or x = "%s" % x
+ if isinstance(binop.left, nodes.Const) and isinstance(
+ binop.left.value, (str, bytes)
+ ):
+ return False, ""
+
+ # This could probably be improved but for now we disregard all assignments from calls
+ if isinstance(binop.left, nodes.Call) or isinstance(binop.right, nodes.Call):
+ return False, ""
+
+ if _is_target_name_in_binop_side(target, binop.left):
+ return True, binop.op
+ if _is_target_name_in_binop_side(target, binop.right):
+ inferred_left = safe_infer(binop.left)
+ if isinstance(inferred_left, nodes.Const) and isinstance(
+ inferred_left.value, int
+ ):
+ return True, binop.op
+ return False, ""
+ return False, ""
+
+
def is_module_ignored(
module: nodes.Module,
ignored_modules: Iterable[str],
@@ -2001,3 +2136,31 @@ def is_module_ignored(
return True
return False
+
+
+def is_singleton_const(node: nodes.NodeNG) -> bool:
+ return isinstance(node, nodes.Const) and any(
+ node.value is value for value in SINGLETON_VALUES
+ )
+
+
+def is_terminating_func(node: nodes.Call) -> bool:
+ """Detect call to exit(), quit(), os._exit(), or sys.exit()."""
+ if (
+ not isinstance(node.func, nodes.Attribute)
+ and not (isinstance(node.func, nodes.Name))
+ or isinstance(node.parent, nodes.Lambda)
+ ):
+ return False
+
+ try:
+ for inferred in node.func.infer():
+ if (
+ hasattr(inferred, "qname")
+ and inferred.qname() in TERMINATING_FUNCS_QNAMES
+ ):
+ return True
+ except (StopIteration, astroid.InferenceError):
+ pass
+
+ return False
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index f1c81fc33..818d625d1 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple
import astroid
from astroid import bases, extract_node, nodes
+from astroid.nodes import _base_nodes
from astroid.typing import InferenceResult
from pylint.checkers import BaseChecker, utils
@@ -112,6 +113,13 @@ TYPING_NAMES = frozenset(
}
)
+DICT_TYPES = (
+ astroid.objects.DictValues,
+ astroid.objects.DictKeys,
+ astroid.objects.DictItems,
+ astroid.nodes.node_classes.Dict,
+)
+
class VariableVisitConsumerAction(Enum):
"""Reported by _check_consumer() and its sub-methods to determine the
@@ -125,7 +133,7 @@ class VariableVisitConsumerAction(Enum):
RETURN = 1
-def _is_from_future_import(stmt, name):
+def _is_from_future_import(stmt: nodes.ImportFrom, name: str) -> bool | None:
"""Check if the name is a future import from another module."""
try:
module = stmt.do_import_module(stmt.modname)
@@ -139,7 +147,9 @@ def _is_from_future_import(stmt, name):
@lru_cache(maxsize=1000)
-def overridden_method(klass, name):
+def overridden_method(
+ klass: nodes.LocalsDictNodeNG, name: str | None
+) -> nodes.FunctionDef | None:
"""Get overridden method if any."""
try:
parent = next(klass.local_attr_ancestors(name))
@@ -156,23 +166,32 @@ def overridden_method(klass, name):
return None
-def _get_unpacking_extra_info(node, inferred):
+def _get_unpacking_extra_info(node: nodes.Assign, inferred: InferenceResult) -> str:
"""Return extra information to add to the message for unpacking-non-sequence
- and unbalanced-tuple-unpacking errors.
+ and unbalanced-tuple/dict-unpacking errors.
"""
more = ""
+ if isinstance(inferred, DICT_TYPES):
+ if isinstance(node, nodes.Assign):
+ more = node.value.as_string()
+ elif isinstance(node, nodes.For):
+ more = node.iter.as_string()
+ return more
+
inferred_module = inferred.root().name
if node.root().name == inferred_module:
if node.lineno == inferred.lineno:
- more = f" {inferred.as_string()}"
+ more = f"'{inferred.as_string()}'"
elif inferred.lineno:
- more = f" defined at line {inferred.lineno}"
+ more = f"defined at line {inferred.lineno}"
elif inferred.lineno:
- more = f" defined at line {inferred.lineno} of {inferred_module}"
+ more = f"defined at line {inferred.lineno} of {inferred_module}"
return more
-def _detect_global_scope(node, frame, defframe):
+def _detect_global_scope(
+ node: nodes.Name, frame: nodes.LocalsDictNodeNG, defframe: nodes.LocalsDictNodeNG
+) -> bool:
"""Detect that the given frames share a global scope.
Two frames share a global scope when neither
@@ -215,7 +234,7 @@ def _detect_global_scope(node, frame, defframe):
# for annotations of function arguments, they'll have
# their parent the Arguments node.
if frame.parent_of(defframe):
- return node.lineno < defframe.lineno
+ return node.lineno < defframe.lineno # type: ignore[no-any-return]
if not isinstance(node.parent, (nodes.FunctionDef, nodes.Arguments)):
return False
elif any(
@@ -249,7 +268,7 @@ def _detect_global_scope(node, frame, defframe):
return False
# 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
+ return frame.lineno < defframe.lineno # type: ignore[no-any-return]
def _infer_name_module(
@@ -257,10 +276,12 @@ def _infer_name_module(
) -> Generator[InferenceResult, None, None]:
context = astroid.context.InferenceContext()
context.lookupname = name
- return node.infer(context, asname=False)
+ return node.infer(context, asname=False) # type: ignore[no-any-return]
-def _fix_dot_imports(not_consumed):
+def _fix_dot_imports(
+ not_consumed: dict[str, list[nodes.NodeNG]]
+) -> list[tuple[str, _base_nodes.ImportNode]]:
"""Try to fix imports with multiple dots, by returning a dictionary
with the import names expanded.
@@ -268,7 +289,7 @@ def _fix_dot_imports(not_consumed):
like 'xml' (when we have both 'xml.etree' and 'xml.sax'), to 'xml.etree'
and 'xml.sax' respectively.
"""
- names = {}
+ names: dict[str, _base_nodes.ImportNode] = {}
for name, stmts in not_consumed.items():
if any(
isinstance(stmt, nodes.AssignName)
@@ -301,7 +322,7 @@ def _fix_dot_imports(not_consumed):
second_name = import_module_name
if second_name and second_name not in names:
names[second_name] = stmt
- return sorted(names.items(), key=lambda a: a[1].fromlineno)
+ return sorted(names.items(), key=lambda a: a[1].fromlineno) # type: ignore[no-any-return]
def _find_frame_imports(name: str, frame: nodes.LocalsDictNodeNG) -> bool:
@@ -326,7 +347,9 @@ def _find_frame_imports(name: str, frame: nodes.LocalsDictNodeNG) -> bool:
return False
-def _import_name_is_global(stmt, global_names) -> bool:
+def _import_name_is_global(
+ stmt: nodes.Global | _base_nodes.ImportNode, global_names: set[str]
+) -> bool:
for import_name, import_alias in stmt.names:
# If the import uses an alias, check only that.
# Otherwise, check only the import name.
@@ -345,7 +368,7 @@ def _flattened_scope_names(
return set(itertools.chain.from_iterable(values))
-def _assigned_locally(name_node: nodes.Name):
+def _assigned_locally(name_node: nodes.Name) -> bool:
"""Checks if name_node has corresponding assign statement in same scope."""
name_node_scope = name_node.scope()
assign_stmts = name_node_scope.nodes_of_class(nodes.AssignName)
@@ -354,7 +377,7 @@ def _assigned_locally(name_node: nodes.Name):
)
-def _has_locals_call_after_node(stmt, scope):
+def _has_locals_call_after_node(stmt: nodes.NodeNG, scope: nodes.FunctionDef) -> bool:
skip_nodes = (
nodes.FunctionDef,
nodes.ClassDef,
@@ -471,9 +494,8 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"the loop.",
),
"W0632": (
- "Possible unbalanced tuple unpacking with "
- "sequence%s: "
- "left side has %d label(s), right side has %d value(s)",
+ "Possible unbalanced tuple unpacking with sequence %s: left side has %d "
+ "label%s, right side has %d value%s",
"unbalanced-tuple-unpacking",
"Used when there is an unbalanced tuple unpacking in assignment",
{"old_names": [("E0632", "old-unbalanced-tuple-unpacking")]},
@@ -481,8 +503,7 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"E0633": (
"Attempting to unpack a non-sequence%s",
"unpacking-non-sequence",
- "Used when something which is not "
- "a sequence is used in an unpack assignment",
+ "Used when something which is not a sequence is used in an unpack assignment",
{"old_names": [("W0633", "old-unpacking-non-sequence")]},
),
"W0640": (
@@ -511,6 +532,12 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"Emitted when an index used on an iterable goes beyond the length of that "
"iterable.",
),
+ "W0644": (
+ "Possible unbalanced dict unpacking with %s: "
+ "left side has %d label%s, right side has %d value%s",
+ "unbalanced-dict-unpacking",
+ "Used when there is an unbalanced dict unpacking in assignment or for loop",
+ ),
}
@@ -526,21 +553,22 @@ class ScopeConsumer(NamedTuple):
class NamesConsumer:
"""A simple class to handle consumed, to consume and scope type info of node locals."""
- def __init__(self, node, scope_type):
+ def __init__(self, node: nodes.NodeNG, scope_type: str) -> None:
self._atomic = ScopeConsumer(
copy.copy(node.locals), {}, collections.defaultdict(list), scope_type
)
self.node = node
+ self._if_nodes_deemed_uncertain: set[nodes.If] = set()
- 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 = [
+ def __repr__(self) -> str:
+ _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)
+ to_consumes = ", ".join(_to_consumes)
+ consumed = ", ".join(_consumed)
+ consumed_uncertain = ", ".join(_consumed_uncertain)
return f"""
to_consume : {to_consumes}
consumed : {consumed}
@@ -548,15 +576,15 @@ consumed_uncertain: {consumed_uncertain}
scope_type : {self._atomic.scope_type}
"""
- def __iter__(self):
+ def __iter__(self) -> Iterator[Any]:
return iter(self._atomic)
@property
- def to_consume(self):
+ def to_consume(self) -> dict[str, list[nodes.NodeNG]]:
return self._atomic.to_consume
@property
- def consumed(self):
+ def consumed(self) -> dict[str, list[nodes.NodeNG]]:
return self._atomic.consumed
@property
@@ -572,10 +600,10 @@ scope_type : {self._atomic.scope_type}
return self._atomic.consumed_uncertain
@property
- def scope_type(self):
+ def scope_type(self) -> str:
return self._atomic.scope_type
- def mark_as_consumed(self, name, consumed_nodes):
+ def mark_as_consumed(self, name: str, consumed_nodes: list[nodes.NodeNG]) -> None:
"""Mark the given nodes as consumed for the name.
If all of the nodes for the name were consumed, delete the name from
@@ -628,6 +656,13 @@ scope_type : {self._atomic.scope_type}
if VariablesChecker._comprehension_between_frame_and_node(node):
return found_nodes
+ # Filter out assignments guarded by always false conditions
+ if found_nodes:
+ uncertain_nodes = self._uncertain_nodes_in_false_tests(found_nodes, node)
+ 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]
+
# Filter out assignments in ExceptHandlers that node is not contained in
if found_nodes:
found_nodes = [
@@ -674,6 +709,118 @@ scope_type : {self._atomic.scope_type}
return found_nodes
@staticmethod
+ def _exhaustively_define_name_raise_or_return(
+ name: str, node: nodes.NodeNG
+ ) -> bool:
+ """Return True if there is a collectively exhaustive set of paths under
+ this `if_node` that define `name`, raise, or return.
+ """
+ # Handle try and with
+ if isinstance(node, (nodes.TryExcept, nodes.TryFinally)):
+ # Allow either a path through try/else/finally OR a path through ALL except handlers
+ return (
+ NamesConsumer._defines_name_raises_or_returns_recursive(name, node)
+ or isinstance(node, nodes.TryExcept)
+ and all(
+ NamesConsumer._defines_name_raises_or_returns_recursive(
+ name, handler
+ )
+ for handler in node.handlers
+ )
+ )
+ if isinstance(node, nodes.With):
+ return NamesConsumer._defines_name_raises_or_returns_recursive(name, node)
+
+ if not isinstance(node, nodes.If):
+ return False
+
+ # Be permissive if there is a break
+ if any(node.nodes_of_class(nodes.Break)):
+ return True
+
+ # Is there an assignment in this node itself, e.g. in named expression?
+ if NamesConsumer._defines_name_raises_or_returns(name, node):
+ return True
+
+ # If there is no else, then there is no collectively exhaustive set of paths
+ if not node.orelse:
+ return False
+
+ return NamesConsumer._branch_handles_name(
+ name, node.body
+ ) and NamesConsumer._branch_handles_name(name, node.orelse)
+
+ @staticmethod
+ def _branch_handles_name(name: str, body: Iterable[nodes.NodeNG]) -> bool:
+ return any(
+ NamesConsumer._defines_name_raises_or_returns(name, if_body_stmt)
+ or isinstance(
+ if_body_stmt,
+ (nodes.If, nodes.TryExcept, nodes.TryFinally, nodes.With),
+ )
+ and NamesConsumer._exhaustively_define_name_raise_or_return(
+ name, if_body_stmt
+ )
+ for if_body_stmt in body
+ )
+
+ def _uncertain_nodes_in_false_tests(
+ self, found_nodes: list[nodes.NodeNG], node: nodes.NodeNG
+ ) -> list[nodes.NodeNG]:
+ """Identify nodes of uncertain execution because they are defined under
+ tests that evaluate false.
+
+ Don't identify a node if there is a collectively exhaustive set of paths
+ that define the name, raise, or return (e.g. every if/else branch).
+ """
+ uncertain_nodes = []
+ for other_node in found_nodes:
+ if in_type_checking_block(other_node):
+ continue
+
+ if not isinstance(other_node, nodes.AssignName):
+ continue
+
+ closest_if = utils.get_node_first_ancestor_of_type(other_node, nodes.If)
+ if closest_if is None:
+ continue
+ if node.frame() is not closest_if.frame():
+ continue
+ if closest_if is not None and closest_if.parent_of(node):
+ continue
+
+ # Name defined in every if/else branch
+ if NamesConsumer._exhaustively_define_name_raise_or_return(
+ other_node.name, closest_if
+ ):
+ continue
+
+ # Higher-level if already determined to be always false
+ if any(
+ if_node.parent_of(closest_if)
+ for if_node in self._if_nodes_deemed_uncertain
+ ):
+ uncertain_nodes.append(other_node)
+ continue
+
+ # All inferred values must test false
+ if isinstance(closest_if.test, nodes.NamedExpr):
+ test = closest_if.test.value
+ else:
+ test = closest_if.test
+ all_inferred = utils.infer_all(test)
+ if not all_inferred or not all(
+ isinstance(inferred, nodes.Const) and not inferred.value
+ for inferred in all_inferred
+ ):
+ continue
+
+ uncertain_nodes.append(other_node)
+ self._if_nodes_deemed_uncertain.add(closest_if)
+
+ return uncertain_nodes
+
+ @staticmethod
def _uncertain_nodes_in_except_blocks(
found_nodes: list[nodes.NodeNG],
node: nodes.NodeNG,
@@ -704,7 +851,14 @@ scope_type : {self._atomic.scope_type}
isinstance(else_statement, nodes.Return)
for else_statement in closest_try_except.orelse
)
- if try_block_returns or else_block_returns:
+ else_block_exits = any(
+ isinstance(else_statement, nodes.Expr)
+ and isinstance(else_statement.value, nodes.Call)
+ and utils.is_terminating_func(else_statement.value)
+ for else_statement in closest_try_except.orelse
+ )
+
+ if try_block_returns or else_block_returns or else_block_exits:
# 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 (
@@ -739,7 +893,7 @@ scope_type : {self._atomic.scope_type}
@staticmethod
def _defines_name_raises_or_returns(name: str, node: nodes.NodeNG) -> bool:
- if isinstance(node, (nodes.Raise, nodes.Return)):
+ if isinstance(node, (nodes.Raise, nodes.Assert, nodes.Return)):
return True
if (
isinstance(node, nodes.AnnAssign)
@@ -1084,17 +1238,51 @@ class VariablesChecker(BaseChecker):
),
)
- def __init__(self, linter=None):
+ def __init__(self, linter: PyLinter) -> None:
super().__init__(linter)
self._to_consume: list[NamesConsumer] = []
- self._checking_mod_attr = None
- self._type_annotation_names = []
+ self._type_annotation_names: list[str] = []
self._except_handler_names_queue: list[
tuple[nodes.ExceptHandler, nodes.AssignName]
] = []
"""This is a queue, last in first out."""
self._postponed_evaluation_enabled = False
+ @utils.only_required_for_messages(
+ "unbalanced-dict-unpacking",
+ )
+ def visit_for(self, node: nodes.For) -> None:
+ if not isinstance(node.target, nodes.Tuple):
+ return
+
+ targets = node.target.elts
+
+ inferred = utils.safe_infer(node.iter)
+ if not isinstance(inferred, DICT_TYPES):
+ return
+
+ values = self._nodes_to_unpack(inferred)
+ if not values:
+ # no dict items returned
+ return
+
+ if isinstance(inferred, astroid.objects.DictItems):
+ # dict.items() is a bit special because values will be a tuple
+ # So as long as there are always 2 targets and values each are
+ # a tuple with two items, this will unpack correctly.
+ # Example: `for key, val in {1: 2, 3: 4}.items()`
+ if len(targets) == 2 and all(len(x.elts) == 2 for x in values):
+ return
+
+ # Starred nodes indicate ambiguous unpacking
+ # if `dict.items()` is used so we won't flag them.
+ if any(isinstance(target, nodes.Starred) for target in targets):
+ return
+
+ if len(targets) != len(values):
+ details = _get_unpacking_extra_info(node, inferred)
+ self._report_unbalanced_unpacking(node, inferred, targets, values, details)
+
def leave_for(self, node: nodes.For) -> None:
self._store_type_annotation_names(node)
@@ -1139,6 +1327,7 @@ class VariablesChecker(BaseChecker):
return
self._check_imports(not_consumed)
+ self._type_annotation_names = []
def visit_classdef(self, node: nodes.ClassDef) -> None:
"""Visit class: update consumption analysis variable."""
@@ -1489,7 +1678,7 @@ class VariablesChecker(BaseChecker):
stmt: nodes.NodeNG,
frame: nodes.LocalsDictNodeNG,
current_consumer: NamesConsumer,
- base_scope_type: Any,
+ base_scope_type: str,
) -> tuple[VariableVisitConsumerAction, list[nodes.NodeNG] | None]:
"""Checks a consumer for conditions that should trigger messages."""
# If the name has already been consumed, only check it's not a loop
@@ -1531,7 +1720,7 @@ class VariablesChecker(BaseChecker):
defframe = defstmt.frame(future=True)
# The class reuses itself in the class scope.
- is_recursive_klass = (
+ is_recursive_klass: bool = (
frame is defframe
and defframe.parent_of(node)
and isinstance(defframe, nodes.ClassDef)
@@ -1731,7 +1920,10 @@ class VariablesChecker(BaseChecker):
self._check_module_attrs(node, module, name.split("."))
@utils.only_required_for_messages(
- "unbalanced-tuple-unpacking", "unpacking-non-sequence", "self-cls-assignment"
+ "unbalanced-tuple-unpacking",
+ "unpacking-non-sequence",
+ "self-cls-assignment",
+ "unbalanced_dict_unpacking",
)
def visit_assign(self, node: nodes.Assign) -> None:
"""Check unbalanced tuple unpacking for assignments and unpacking
@@ -1742,6 +1934,11 @@ class VariablesChecker(BaseChecker):
return
targets = node.targets[0].itered()
+
+ # Check if we have starred nodes.
+ if any(isinstance(target, nodes.Starred) for target in targets):
+ return
+
try:
inferred = utils.safe_infer(node.value)
if inferred is not None:
@@ -1771,19 +1968,21 @@ class VariablesChecker(BaseChecker):
# Relying on other checker's options, which might not have been initialized yet.
@cached_property
- def _analyse_fallback_blocks(self):
- return self.linter.config.analyse_fallback_blocks
+ def _analyse_fallback_blocks(self) -> bool:
+ return bool(self.linter.config.analyse_fallback_blocks)
@cached_property
- def _ignored_modules(self):
- return self.linter.config.ignored_modules
+ def _ignored_modules(self) -> Iterable[str]:
+ return self.linter.config.ignored_modules # type: ignore[no-any-return]
@cached_property
- def _allow_global_unused_variables(self):
- return self.linter.config.allow_global_unused_variables
+ def _allow_global_unused_variables(self) -> bool:
+ return bool(self.linter.config.allow_global_unused_variables)
@staticmethod
- def _defined_in_function_definition(node, frame):
+ def _defined_in_function_definition(
+ node: nodes.NodeNG, frame: nodes.NodeNG
+ ) -> bool:
in_annotation_or_default_or_decorator = False
if (
isinstance(frame, nodes.FunctionDef)
@@ -1837,13 +2036,13 @@ class VariablesChecker(BaseChecker):
@staticmethod
def _is_variable_violation(
node: nodes.Name,
- defnode,
+ defnode: nodes.NodeNG,
stmt: nodes.Statement,
defstmt: nodes.Statement,
- frame, # scope of statement of node
- defframe,
- base_scope_type,
- is_recursive_klass,
+ frame: nodes.LocalsDictNodeNG, # scope of statement of node
+ defframe: nodes.LocalsDictNodeNG,
+ base_scope_type: str,
+ is_recursive_klass: bool,
) -> tuple[bool, bool, bool]:
maybe_before_assign = True
annotation_return = False
@@ -2347,10 +2546,10 @@ class VariablesChecker(BaseChecker):
def _check_is_unused(
self,
- name,
- node,
- stmt,
- global_names,
+ name: str,
+ node: nodes.FunctionDef,
+ stmt: nodes.NodeNG,
+ global_names: set[str],
nonlocal_names: Iterable[str],
comprehension_target_names: Iterable[str],
) -> None:
@@ -2438,21 +2637,31 @@ class VariablesChecker(BaseChecker):
self.add_message(message_name, args=name, node=stmt)
- def _is_name_ignored(self, stmt, name):
+ def _is_name_ignored(
+ self, stmt: nodes.NodeNG, name: str
+ ) -> re.Pattern[str] | re.Match[str] | None:
authorized_rgx = self.linter.config.dummy_variables_rgx
if (
isinstance(stmt, nodes.AssignName)
and isinstance(stmt.parent, nodes.Arguments)
or isinstance(stmt, nodes.Arguments)
):
- regex = self.linter.config.ignored_argument_names
+ regex: re.Pattern[str] = self.linter.config.ignored_argument_names
else:
regex = authorized_rgx
+ # See https://stackoverflow.com/a/47007761/2519059 to
+ # understand what this function return. Please do NOT use
+ # this elsewhere, this is confusing for no benefit
return regex and regex.match(name)
def _check_unused_arguments(
- self, name, node, stmt, argnames, nonlocal_names: Iterable[str]
- ):
+ self,
+ name: str,
+ node: nodes.FunctionDef,
+ stmt: nodes.NodeNG,
+ argnames: list[str],
+ nonlocal_names: Iterable[str],
+ ) -> None:
is_method = node.is_method()
klass = node.parent.frame(future=True)
if is_method and isinstance(klass, nodes.ClassDef):
@@ -2548,12 +2757,12 @@ class VariablesChecker(BaseChecker):
):
self.add_message("cell-var-from-loop", node=node, args=node.name)
- def _should_ignore_redefined_builtin(self, stmt):
+ def _should_ignore_redefined_builtin(self, stmt: nodes.NodeNG) -> bool:
if not isinstance(stmt, nodes.ImportFrom):
return False
return stmt.modname in self.linter.config.redefining_builtins_modules
- def _allowed_redefined_builtin(self, name):
+ def _allowed_redefined_builtin(self, name: str) -> bool:
return name in self.linter.config.allowed_redefined_builtins
@staticmethod
@@ -2568,7 +2777,7 @@ class VariablesChecker(BaseChecker):
future=True
).parent_of(closest_comprehension_scope)
- def _store_type_annotation_node(self, type_annotation):
+ def _store_type_annotation_node(self, type_annotation: nodes.NodeNG) -> None:
"""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)
@@ -2593,7 +2802,9 @@ class VariablesChecker(BaseChecker):
annotation.name for annotation in type_annotation.nodes_of_class(nodes.Name)
)
- def _store_type_annotation_names(self, node):
+ def _store_type_annotation_names(
+ self, node: nodes.For | nodes.Assign | nodes.With
+ ) -> None:
type_annotation = node.type_annotation
if not type_annotation:
return
@@ -2629,7 +2840,9 @@ class VariablesChecker(BaseChecker):
if self_cls_name in assign_names:
self.add_message("self-cls-assignment", node=node, args=(self_cls_name,))
- def _check_unpacking(self, inferred, node, targets):
+ def _check_unpacking(
+ self, inferred: InferenceResult, node: nodes.Assign, targets: list[nodes.NodeNG]
+ ) -> None:
"""Check for unbalanced tuple unpacking
and unpacking non sequences.
"""
@@ -2649,40 +2862,62 @@ class VariablesChecker(BaseChecker):
# Attempt to check unpacking is properly balanced
values = self._nodes_to_unpack(inferred)
+ details = _get_unpacking_extra_info(node, 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):
- return
- self.add_message(
- "unbalanced-tuple-unpacking",
- node=node,
- args=(
- _get_unpacking_extra_info(node, inferred),
- len(targets),
- len(values),
- ),
+ self._report_unbalanced_unpacking(
+ node, inferred, targets, values, details
)
# attempt to check unpacking may be possible (i.e. RHS is iterable)
elif not utils.is_iterable(inferred):
- self.add_message(
- "unpacking-non-sequence",
- node=node,
- args=(_get_unpacking_extra_info(node, inferred),),
- )
+ self._report_unpacking_non_sequence(node, details)
@staticmethod
def _nodes_to_unpack(node: nodes.NodeNG) -> list[nodes.NodeNG] | None:
"""Return the list of values of the `Assign` node."""
- if isinstance(node, (nodes.Tuple, nodes.List)):
- return node.itered()
+ if isinstance(node, (nodes.Tuple, nodes.List) + DICT_TYPES):
+ return node.itered() # type: ignore[no-any-return]
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):
+ def _report_unbalanced_unpacking(
+ self,
+ node: nodes.NodeNG,
+ inferred: InferenceResult,
+ targets: list[nodes.NodeNG],
+ values: list[nodes.NodeNG],
+ details: str,
+ ) -> None:
+ args = (
+ details,
+ len(targets),
+ "" if len(targets) == 1 else "s",
+ len(values),
+ "" if len(values) == 1 else "s",
+ )
+
+ symbol = (
+ "unbalanced-dict-unpacking"
+ if isinstance(inferred, DICT_TYPES)
+ else "unbalanced-tuple-unpacking"
+ )
+ self.add_message(symbol, node=node, args=args, confidence=INFERENCE)
+
+ def _report_unpacking_non_sequence(self, node: nodes.NodeNG, details: str) -> None:
+ if details and not details.startswith(" "):
+ details = f" {details}"
+ self.add_message("unpacking-non-sequence", node=node, args=details)
+
+ def _check_module_attrs(
+ self,
+ node: _base_nodes.ImportNode,
+ module: nodes.Module,
+ module_names: list[str],
+ ) -> nodes.Module | None:
"""Check that module_names (list of string) are accessible through the
given module, if the latest access name corresponds to a module, return it.
"""
@@ -2714,7 +2949,9 @@ class VariablesChecker(BaseChecker):
return module
return None
- def _check_all(self, node: nodes.Module, not_consumed):
+ def _check_all(
+ self, node: nodes.Module, not_consumed: dict[str, list[nodes.NodeNG]]
+ ) -> None:
assigned = next(node.igetattr("__all__"))
if assigned is astroid.Uninferable:
return
@@ -2765,14 +3002,14 @@ class VariablesChecker(BaseChecker):
# when the file will be checked
pass
- def _check_globals(self, not_consumed):
+ def _check_globals(self, not_consumed: dict[str, nodes.NodeNG]) -> None:
if self._allow_global_unused_variables:
return
for name, node_lst in not_consumed.items():
for node in node_lst:
self.add_message("unused-variable", args=(name,), node=node)
- def _check_imports(self, not_consumed):
+ def _check_imports(self, not_consumed: dict[str, list[nodes.NodeNG]]) -> None:
local_names = _fix_dot_imports(not_consumed)
checked = set()
unused_wildcard_imports: defaultdict[
@@ -2854,9 +3091,9 @@ class VariablesChecker(BaseChecker):
)
del self._to_consume
- def _check_metaclasses(self, node):
+ def _check_metaclasses(self, node: nodes.Module | nodes.FunctionDef) -> None:
"""Update consumption analysis for metaclasses."""
- consumed = [] # [(scope_locals, consumed_key)]
+ consumed: list[tuple[dict[str, list[nodes.NodeNG]], str]] = []
for child_node in node.get_children():
if isinstance(child_node, nodes.ClassDef):
@@ -2867,14 +3104,16 @@ class VariablesChecker(BaseChecker):
for scope_locals, name in consumed:
scope_locals.pop(name, None)
- def _check_classdef_metaclasses(self, klass, parent_node):
+ def _check_classdef_metaclasses(
+ self, klass: nodes.ClassDef, parent_node: nodes.Module | nodes.FunctionDef
+ ) -> list[tuple[dict[str, list[nodes.NodeNG]], str]]:
if not klass._metaclass:
# Skip if this class doesn't use explicitly a metaclass, but inherits it from ancestors
return []
- consumed = [] # [(scope_locals, consumed_key)]
+ consumed: list[tuple[dict[str, list[nodes.NodeNG]], str]] = []
metaclass = klass.metaclass()
- name = None
+ name = ""
if isinstance(klass._metaclass, nodes.Name):
name = klass._metaclass.name
elif isinstance(klass._metaclass, nodes.Attribute) and klass._metaclass.expr:
@@ -2949,7 +3188,7 @@ class VariablesChecker(BaseChecker):
)
def visit_const(self, node: nodes.Const) -> None:
"""Take note of names that appear inside string literal type annotations
- unless the string is a parameter to typing.Literal.
+ unless the string is a parameter to `typing.Literal` or `typing.Annotation`.
"""
if node.pytype() != "builtins.str":
return
@@ -2962,7 +3201,9 @@ class VariablesChecker(BaseChecker):
parent = parent.parent
if isinstance(parent, nodes.Subscript):
origin = next(parent.get_children(), None)
- if origin is not None and utils.is_typing_literal(origin):
+ if origin is not None and utils.is_typing_member(
+ origin, ("Annotated", "Literal")
+ ):
return
try:
diff --git a/pylint/config/__init__.py b/pylint/config/__init__.py
index 7dc96f0cf..5f90bbae0 100644
--- a/pylint/config/__init__.py
+++ b/pylint/config/__init__.py
@@ -31,8 +31,10 @@ from pylint.config.find_default_config_files import (
)
from pylint.config.option import Option
from pylint.config.option_manager_mixin import OptionsManagerMixIn
-from pylint.config.option_parser import OptionParser
-from pylint.config.options_provider_mixin import OptionsProviderMixIn
+from pylint.config.option_parser import OptionParser # type: ignore[attr-defined]
+from pylint.config.options_provider_mixin import ( # type: ignore[attr-defined]
+ OptionsProviderMixIn,
+)
from pylint.constants import PYLINT_HOME, USER_HOME
from pylint.utils import LinterStats
@@ -46,6 +48,7 @@ def load_results(base: str) -> LinterStats | None:
"'pylint.config.load_results' is deprecated, please use "
"'pylint.lint.load_results' instead. This will be removed in 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
return _real_load_results(base, PYLINT_HOME)
@@ -59,5 +62,6 @@ def save_results(results: LinterStats, base: str) -> None:
"'pylint.config.save_results' is deprecated, please use "
"'pylint.lint.save_results' instead. This will be removed in 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
return _real_save_results(results, base, PYLINT_HOME)
diff --git a/pylint/config/_pylint_config/__init__.py b/pylint/config/_pylint_config/__init__.py
index d62400a0e..622d0dfe3 100644
--- a/pylint/config/_pylint_config/__init__.py
+++ b/pylint/config/_pylint_config/__init__.py
@@ -7,5 +7,7 @@
Everything in this module is private.
"""
-from pylint.config._pylint_config.main import _handle_pylint_config_commands # noqa
-from pylint.config._pylint_config.setup import _register_generate_config_options # noqa
+from pylint.config._pylint_config.main import _handle_pylint_config_commands
+from pylint.config._pylint_config.setup import _register_generate_config_options
+
+__all__ = ("_handle_pylint_config_commands", "_register_generate_config_options")
diff --git a/pylint/config/_pylint_config/generate_command.py b/pylint/config/_pylint_config/generate_command.py
index 325c71333..110069b90 100644
--- a/pylint/config/_pylint_config/generate_command.py
+++ b/pylint/config/_pylint_config/generate_command.py
@@ -22,10 +22,11 @@ def generate_interactive_config(linter: PyLinter) -> None:
print("Starting interactive pylint configuration generation")
format_type = utils.get_and_validate_format()
+ minimal = format_type == "toml" and utils.get_minimal_setting()
to_file, output_file_name = utils.get_and_validate_output_file()
if format_type == "toml":
- config_string = linter._generate_config_file()
+ config_string = linter._generate_config_file(minimal=minimal)
else:
output_stream = StringIO()
with warnings.catch_warnings():
diff --git a/pylint/config/_pylint_config/utils.py b/pylint/config/_pylint_config/utils.py
index 0534340b1..cd5f8affe 100644
--- a/pylint/config/_pylint_config/utils.py
+++ b/pylint/config/_pylint_config/utils.py
@@ -9,6 +9,7 @@ from __future__ import annotations
import sys
from collections.abc import Callable
from pathlib import Path
+from typing import TypeVar
if sys.version_info >= (3, 8):
from typing import Literal
@@ -21,6 +22,7 @@ else:
from typing_extensions import ParamSpec
_P = ParamSpec("_P")
+_ReturnValueT = TypeVar("_ReturnValueT", bool, str)
SUPPORTED_FORMATS = {"t", "toml", "i", "ini"}
YES_NO_ANSWERS = {"y", "yes", "n", "no"}
@@ -36,11 +38,11 @@ class InvalidUserInput(Exception):
def should_retry_after_invalid_input(
- func: Callable[_P, str | bool]
-) -> Callable[_P, str | bool]:
+ func: Callable[_P, _ReturnValueT]
+) -> Callable[_P, _ReturnValueT]:
"""Decorator that handles InvalidUserInput exceptions and retries."""
- def inner_function(*args: _P.args, **kwargs: _P.kwargs) -> str | bool:
+ def inner_function(*args: _P.args, **kwargs: _P.kwargs) -> _ReturnValueT:
called_once = False
while True:
try:
@@ -81,7 +83,7 @@ def validate_yes_no(question: str, default: Literal["yes", "no"] | None) -> bool
# pylint: disable-next=bad-builtin
answer = input(question).lower()
- if answer == "" and default:
+ if not answer and default:
answer = default
if answer not in YES_NO_ANSWERS:
@@ -90,6 +92,13 @@ def validate_yes_no(question: str, default: Literal["yes", "no"] | None) -> bool
return answer.startswith("y")
+def get_minimal_setting() -> bool:
+ """Ask the user if they want to use the minimal setting."""
+ return validate_yes_no(
+ "Do you want a minimal configuration without comments or default values?", "no"
+ )
+
+
def get_and_validate_output_file() -> tuple[bool, Path]:
"""Make sure that the output file is correct."""
to_file = validate_yes_no("Do you want to write the output to a file?", "no")
diff --git a/pylint/config/argument.py b/pylint/config/argument.py
index 375801c55..7a03d82b2 100644
--- a/pylint/config/argument.py
+++ b/pylint/config/argument.py
@@ -272,7 +272,7 @@ class _StoreTrueArgument(_BaseStoreArgument):
https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
"""
- # pylint: disable-next=useless-super-delegation # We narrow down the type of action
+ # pylint: disable-next=useless-parent-delegation # We narrow down the type of action
def __init__(
self,
*,
@@ -365,9 +365,9 @@ class _ExtendArgument(_DeprecationArgument):
) -> None:
# The extend action is included in the stdlib from 3.8+
if PY38_PLUS:
- action_class = argparse._ExtendAction # type: ignore[attr-defined]
+ action_class = argparse._ExtendAction
else:
- action_class = _ExtendAction
+ action_class = _ExtendAction # type: ignore[assignment]
self.dest = dest
"""The destination of the argument."""
diff --git a/pylint/config/arguments_manager.py b/pylint/config/arguments_manager.py
index 40058071c..c771ad355 100644
--- a/pylint/config/arguments_manager.py
+++ b/pylint/config/arguments_manager.py
@@ -38,8 +38,10 @@ from pylint.config.exceptions import (
)
from pylint.config.help_formatter import _HelpFormatter
from pylint.config.option import Option
-from pylint.config.option_parser import OptionParser
-from pylint.config.options_provider_mixin import OptionsProviderMixIn
+from pylint.config.option_parser import OptionParser # type: ignore[attr-defined]
+from pylint.config.options_provider_mixin import ( # type: ignore[attr-defined]
+ OptionsProviderMixIn,
+)
from pylint.config.utils import _convert_option_to_argument, _parse_rich_type_value
from pylint.constants import MAIN_CHECKER_NAME
from pylint.typing import DirectoryNamespaceDict, OptionDict
@@ -123,6 +125,7 @@ class _ArgumentsManager:
warnings.warn(
"options_providers has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
return self._options_providers
@@ -131,6 +134,7 @@ class _ArgumentsManager:
warnings.warn(
"Setting options_providers has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
self._options_providers = value
@@ -280,6 +284,7 @@ class _ArgumentsManager:
"reset_parsers has been deprecated. Parsers should be instantiated "
"once during initialization and do not need to be reset.",
DeprecationWarning,
+ stacklevel=2,
)
# configuration file parser
self.cfgfile_parser = configparser.ConfigParser(
@@ -287,7 +292,7 @@ class _ArgumentsManager:
)
# command line parser
self.cmdline_parser = OptionParser(Option, usage=usage)
- self.cmdline_parser.options_manager = self # type: ignore[attr-defined]
+ self.cmdline_parser.options_manager = self
self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS)
def register_options_provider(
@@ -299,6 +304,7 @@ class _ArgumentsManager:
"arguments providers should be registered by initializing ArgumentsProvider. "
"This automatically registers the provider on the ArgumentsManager.",
DeprecationWarning,
+ stacklevel=2,
)
self.options_providers.append(provider)
non_group_spec_options = [
@@ -343,6 +349,7 @@ class _ArgumentsManager:
"registered by initializing ArgumentsProvider. "
"This automatically registers the group on the ArgumentsManager.",
DeprecationWarning,
+ stacklevel=2,
)
# add option group to the command line parser
if group_name in self._mygroups:
@@ -379,6 +386,7 @@ class _ArgumentsManager:
"add_optik_option has been deprecated. Options should be automatically "
"added by initializing an ArgumentsProvider.",
DeprecationWarning,
+ stacklevel=2,
)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
@@ -397,6 +405,7 @@ class _ArgumentsManager:
"optik_option has been deprecated. Parsing of option dictionaries should be done "
"automatically by initializing an ArgumentsProvider.",
DeprecationWarning,
+ stacklevel=2,
)
optdict = copy.copy(optdict)
if "action" in optdict:
@@ -434,6 +443,7 @@ class _ArgumentsManager:
warnings.warn(
"generate_config has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
options_by_section = {}
sections = []
@@ -496,6 +506,7 @@ class _ArgumentsManager:
"load_provider_defaults has been deprecated. Parsing of option defaults should be done "
"automatically by initializing an ArgumentsProvider.",
DeprecationWarning,
+ stacklevel=2,
)
for provider in self.options_providers:
with warnings.catch_warnings():
@@ -513,6 +524,7 @@ class _ArgumentsManager:
warnings.warn(
"read_config_file has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
if not config_file:
if verbose:
@@ -584,6 +596,7 @@ class _ArgumentsManager:
warnings.warn(
"load_config_file has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
parser = self.cfgfile_parser
for section in parser.sections():
@@ -598,6 +611,7 @@ class _ArgumentsManager:
warnings.warn(
"load_configuration has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
@@ -609,6 +623,7 @@ class _ArgumentsManager:
warnings.warn(
"DEPRECATED: load_configuration_from_config has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
for opt, opt_value in config.items():
opt = opt.replace("_", "-")
@@ -625,6 +640,7 @@ class _ArgumentsManager:
warnings.warn(
"load_command_line_configuration has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
args = sys.argv[1:] if args is None else list(args)
(options, args) = self.cmdline_parser.parse_args(args=args)
@@ -635,7 +651,7 @@ class _ArgumentsManager:
if value is None:
continue
setattr(config, attr, value)
- return args
+ return args # type: ignore[return-value]
def help(self, level: int | None = None) -> str:
"""Return the usage string based on the available options."""
@@ -644,15 +660,19 @@ class _ArgumentsManager:
"Supplying a 'level' argument to help() has been deprecated."
"You can call help() without any arguments.",
DeprecationWarning,
+ stacklevel=2,
)
return self._arg_parser.format_help()
- def cb_set_provider_option(self, option, opt, value, parser): # pragma: no cover
+ def cb_set_provider_option( # pragma: no cover
+ self, option: Any, opt: Any, value: Any, parser: Any
+ ) -> None:
"""DEPRECATED: Optik callback for option setting."""
# TODO: 3.0: Remove deprecated method.
warnings.warn(
"cb_set_provider_option has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
if opt.startswith("--"):
# remove -- on long option
@@ -672,10 +692,11 @@ class _ArgumentsManager:
"global_set_option has been deprecated. You can use _arguments_manager.set_option "
"or linter.set_option to set options on the global configuration object.",
DeprecationWarning,
+ stacklevel=2,
)
self.set_option(opt, value)
- def _generate_config_file(self) -> str:
+ def _generate_config_file(self, *, minimal: bool = False) -> str:
"""Write a configuration file according to the current configuration into
stdout.
"""
@@ -714,19 +735,21 @@ class _ArgumentsManager:
continue
# Add help comment
- help_msg = optdict.get("help", "")
- assert isinstance(help_msg, str)
- help_text = textwrap.wrap(help_msg, width=79)
- for line in help_text:
- group_table.add(tomlkit.comment(line))
+ if not minimal:
+ help_msg = optdict.get("help", "")
+ assert isinstance(help_msg, str)
+ help_text = textwrap.wrap(help_msg, width=79)
+ for line in help_text:
+ group_table.add(tomlkit.comment(line))
# Get current value of option
value = getattr(self.config, optname.replace("-", "_"))
# Create a comment if the option has no value
if not value:
- group_table.add(tomlkit.comment(f"{optname} ="))
- group_table.add(tomlkit.nl())
+ if not minimal:
+ group_table.add(tomlkit.comment(f"{optname} ="))
+ group_table.add(tomlkit.nl())
continue
# Skip deprecated options
@@ -747,19 +770,24 @@ class _ArgumentsManager:
if optdict.get("type") == "py_version":
value = ".".join(str(i) for i in value)
+ # Check if it is default value if we are in minimal mode
+ if minimal and value == optdict.get("default"):
+ continue
+
# Add to table
group_table.add(optname, value)
group_table.add(tomlkit.nl())
assert group.title
- pylint_tool_table.add(group.title.lower(), group_table)
+ if group_table:
+ pylint_tool_table.add(group.title.lower(), group_table)
toml_string = tomlkit.dumps(toml_doc)
# Make sure the string we produce is valid toml and can be parsed
tomllib.loads(toml_string)
- return toml_string
+ return str(toml_string)
def set_option(
self,
@@ -775,12 +803,14 @@ class _ArgumentsManager:
"The 'action' argument has been deprecated. You can use set_option "
"without the 'action' or 'optdict' arguments.",
DeprecationWarning,
+ stacklevel=2,
)
if optdict != "default_value":
warnings.warn(
"The 'optdict' argument has been deprecated. You can use set_option "
"without the 'action' or 'optdict' arguments.",
DeprecationWarning,
+ stacklevel=2,
)
self.config = self._arg_parser.parse_known_args(
diff --git a/pylint/config/arguments_provider.py b/pylint/config/arguments_provider.py
index 2ab44b161..ea229e1c3 100644
--- a/pylint/config/arguments_provider.py
+++ b/pylint/config/arguments_provider.py
@@ -24,6 +24,7 @@ class UnsupportedAction(Exception):
warnings.warn(
"UnsupportedAction has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
super().__init__(*args)
@@ -55,6 +56,7 @@ class _ArgumentsProvider:
"The level attribute has been deprecated. It was used to display the checker in the help or not,"
" and everything is displayed in the help now. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
return self._level
@@ -65,6 +67,7 @@ class _ArgumentsProvider:
"Setting the level attribute has been deprecated. It was used to display the checker in the help or not,"
" and everything is displayed in the help now. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
self._level = value
@@ -75,6 +78,7 @@ class _ArgumentsProvider:
"The checker-specific config attribute has been deprecated. Please use "
"'linter.config' to access the global configuration object.",
DeprecationWarning,
+ stacklevel=2,
)
return self._arguments_manager.config
@@ -85,6 +89,7 @@ class _ArgumentsProvider:
"registered by initializing an ArgumentsProvider. "
"This automatically registers the group on the ArgumentsManager.",
DeprecationWarning,
+ stacklevel=2,
)
for opt, optdict in self.options:
action = optdict.get("action")
@@ -105,6 +110,7 @@ class _ArgumentsProvider:
"option_attrname has been deprecated. It will be removed "
"in a future release.",
DeprecationWarning,
+ stacklevel=2,
)
if optdict is None:
with warnings.catch_warnings():
@@ -118,11 +124,17 @@ class _ArgumentsProvider:
"option_value has been deprecated. It will be removed "
"in a future release.",
DeprecationWarning,
+ stacklevel=2,
)
return getattr(self._arguments_manager.config, opt.replace("-", "_"), None)
- # pylint: disable-next=unused-argument
- def set_option(self, optname, value, action=None, optdict=None): # pragma: no cover
+ def set_option( # pragma: no cover
+ self,
+ optname: Any,
+ value: Any,
+ action: Any = None, # pylint: disable=unused-argument
+ optdict: Any = None, # pylint: disable=unused-argument
+ ) -> None:
"""DEPRECATED: Method called to set an option (registered in the options
list).
"""
@@ -131,6 +143,7 @@ class _ArgumentsProvider:
"set_option has been deprecated. You can use _arguments_manager.set_option "
"or linter.set_option to set options on the global configuration object.",
DeprecationWarning,
+ stacklevel=2,
)
self._arguments_manager.set_option(optname, value)
@@ -143,6 +156,7 @@ class _ArgumentsProvider:
"get_option_def has been deprecated. It will be removed "
"in a future release.",
DeprecationWarning,
+ stacklevel=2,
)
assert self.options
for option in self.options:
@@ -169,6 +183,7 @@ class _ArgumentsProvider:
"options_by_section has been deprecated. It will be removed "
"in a future release.",
DeprecationWarning,
+ stacklevel=2,
)
sections: dict[str, list[tuple[str, OptionDict, Any]]] = {}
for optname, optdict in self.options:
@@ -190,6 +205,7 @@ class _ArgumentsProvider:
"options_and_values has been deprecated. It will be removed "
"in a future release.",
DeprecationWarning,
+ stacklevel=2,
)
if options is None:
options = self.options
diff --git a/pylint/config/callback_actions.py b/pylint/config/callback_actions.py
index 242629a88..a4c633464 100644
--- a/pylint/config/callback_actions.py
+++ b/pylint/config/callback_actions.py
@@ -378,7 +378,7 @@ class _XableAction(_AccessLinterObjectAction):
xabling_function: Callable[[str], None],
values: str | Sequence[Any] | None,
option_string: str | None,
- ):
+ ) -> None:
assert isinstance(values, (tuple, list))
for msgid in utils._check_csv(values[0]):
try:
diff --git a/pylint/config/configuration_mixin.py b/pylint/config/configuration_mixin.py
index 7854ff733..55857224a 100644
--- a/pylint/config/configuration_mixin.py
+++ b/pylint/config/configuration_mixin.py
@@ -2,29 +2,35 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+from __future__ import annotations
+
import warnings
+from typing import Any
from pylint.config.option_manager_mixin import OptionsManagerMixIn
-from pylint.config.options_provider_mixin import OptionsProviderMixIn
+from pylint.config.options_provider_mixin import ( # type: ignore[attr-defined]
+ OptionsProviderMixIn,
+)
-class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn):
+class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): # type: ignore[misc]
"""Basic mixin for simple configurations which don't need the
manager / providers model.
"""
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
# TODO: 3.0: Remove deprecated class
warnings.warn(
"ConfigurationMixIn has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
if not args:
kwargs.setdefault("usage", "")
OptionsManagerMixIn.__init__(self, *args, **kwargs)
OptionsProviderMixIn.__init__(self)
if not getattr(self, "option_groups", None):
- self.option_groups = []
+ self.option_groups: list[tuple[str, str]] = []
for _, optdict in self.options:
try:
gdef = (optdict["group"].upper(), "")
diff --git a/pylint/config/deprecation_actions.py b/pylint/config/deprecation_actions.py
index c7c3e9b17..ceef200a7 100644
--- a/pylint/config/deprecation_actions.py
+++ b/pylint/config/deprecation_actions.py
@@ -52,7 +52,7 @@ class _OldNamesAction(argparse._StoreAction):
namespace: argparse.Namespace,
values: str | Sequence[Any] | None,
option_string: str | None = None,
- ):
+ ) -> None:
assert isinstance(values, list)
setattr(namespace, self.dest, values[0])
for old_name in self.old_names:
@@ -97,7 +97,7 @@ class _NewNamesAction(argparse._StoreAction):
namespace: argparse.Namespace,
values: str | Sequence[Any] | None,
option_string: str | None = None,
- ):
+ ) -> None:
assert isinstance(values, list)
setattr(namespace, self.dest, values[0])
warnings.warn(
diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py
index 314e70e11..43e682a58 100644
--- a/pylint/config/find_default_config_files.py
+++ b/pylint/config/find_default_config_files.py
@@ -122,6 +122,7 @@ def find_pylintrc() -> str | None:
"Use find_default_config_files if you want access to pylint's configuration file "
"finding logic.",
DeprecationWarning,
+ stacklevel=2,
)
for config_file in find_default_config_files():
if str(config_file).endswith("pylintrc"):
diff --git a/pylint/config/option.py b/pylint/config/option.py
index 5043fe765..95248d6b1 100644
--- a/pylint/config/option.py
+++ b/pylint/config/option.py
@@ -9,30 +9,38 @@ import optparse # pylint: disable=deprecated-module
import pathlib
import re
import warnings
+from collections.abc import Callable, Sequence
from re import Pattern
+from typing import Any
from pylint import utils
# pylint: disable=unused-argument
-def _csv_validator(_, name, value):
+def _csv_validator(
+ _: Any, name: str, value: str | list[str] | tuple[str]
+) -> Sequence[str]:
return utils._check_csv(value)
# pylint: disable=unused-argument
-def _regexp_validator(_, name, value):
+def _regexp_validator(
+ _: Any, name: str, value: str | re.Pattern[str]
+) -> re.Pattern[str]:
if hasattr(value, "pattern"):
- return value
+ return value # type: ignore[return-value]
return re.compile(value)
# pylint: disable=unused-argument
-def _regexp_csv_validator(_, name, value):
+def _regexp_csv_validator(
+ _: Any, name: str, value: str | list[str]
+) -> list[re.Pattern[str]]:
return [_regexp_validator(_, name, val) for val in _csv_validator(_, name, value)]
def _regexp_paths_csv_validator(
- _, name: str, value: str | list[Pattern[str]]
+ _: Any, name: str, value: str | list[Pattern[str]]
) -> list[Pattern[str]]:
if isinstance(value, list):
return value
@@ -48,14 +56,14 @@ def _regexp_paths_csv_validator(
return patterns
-def _choice_validator(choices, name, value):
+def _choice_validator(choices: list[Any], name: str, value: Any) -> Any:
if value not in choices:
msg = "option %s: invalid value: %r, should be in %s"
raise optparse.OptionValueError(msg % (name, value, choices))
return value
-def _yn_validator(opt, _, value):
+def _yn_validator(opt: str, _: str, value: Any) -> bool:
if isinstance(value, int):
return bool(value)
if isinstance(value, str):
@@ -68,7 +76,7 @@ def _yn_validator(opt, _, value):
raise optparse.OptionValueError(msg % (opt, value))
-def _multiple_choice_validator(choices, name, value):
+def _multiple_choice_validator(choices: list[Any], name: str, value: Any) -> Any:
values = utils._check_csv(value)
for csv_value in values:
if csv_value not in choices:
@@ -77,18 +85,24 @@ def _multiple_choice_validator(choices, name, value):
return values
-def _non_empty_string_validator(opt, _, value): # pragma: no cover # Unused
+def _non_empty_string_validator( # pragma: no cover # Unused
+ opt: Any, _: str, value: str
+) -> str:
if not value:
msg = "indent string can't be empty."
raise optparse.OptionValueError(msg)
return utils._unquote(value)
-def _multiple_choices_validating_option(opt, name, value): # pragma: no cover # Unused
- return _multiple_choice_validator(opt.choices, name, value)
+def _multiple_choices_validating_option( # pragma: no cover # Unused
+ opt: optparse.Option, name: str, value: Any
+) -> Any:
+ return _multiple_choice_validator(
+ opt.choices, name, value # type: ignore[attr-defined]
+ )
-def _py_version_validator(_, name, value):
+def _py_version_validator(_: Any, name: str, value: Any) -> tuple[int, int, int]:
if not isinstance(value, tuple):
try:
value = tuple(int(val) for val in value.split("."))
@@ -96,10 +110,10 @@ def _py_version_validator(_, name, value):
raise optparse.OptionValueError(
f"Invalid format for {name}, should be version string. E.g., '3.8'"
) from None
- return value
+ return value # type: ignore[no-any-return]
-VALIDATORS = {
+VALIDATORS: dict[str, Callable[[Any, str, Any], Any] | Callable[[Any], Any]] = {
"string": utils._unquote,
"int": int,
"float": float,
@@ -120,21 +134,21 @@ VALIDATORS = {
}
-def _call_validator(opttype, optdict, option, value):
+def _call_validator(opttype: str, optdict: Any, option: str, value: Any) -> Any:
if opttype not in VALIDATORS:
- raise Exception(f'Unsupported type "{opttype}"')
+ raise TypeError(f'Unsupported type "{opttype}"')
try:
- return VALIDATORS[opttype](optdict, option, value)
+ return VALIDATORS[opttype](optdict, option, value) # type: ignore[call-arg]
except TypeError:
try:
- return VALIDATORS[opttype](value)
+ return VALIDATORS[opttype](value) # type: ignore[call-arg]
except Exception as e:
raise optparse.OptionValueError(
f"{option} value ({value!r}) should be of type {opttype}"
) from e
-def _validate(value, optdict, name=""):
+def _validate(value: Any, optdict: Any, name: str = "") -> Any:
"""Return a validated value for an option according to its type.
optional argument name is only used for error message formatting
@@ -171,37 +185,41 @@ class Option(optparse.Option):
TYPE_CHECKER["non_empty_string"] = _non_empty_string_validator
TYPE_CHECKER["py_version"] = _py_version_validator
- def __init__(self, *opts, **attrs):
+ def __init__(self, *opts: Any, **attrs: Any) -> None:
# TODO: 3.0: Remove deprecated class
warnings.warn(
"Option has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
super().__init__(*opts, **attrs)
if hasattr(self, "hide") and self.hide:
self.help = optparse.SUPPRESS_HELP
- def _check_choice(self):
+ def _check_choice(self) -> None:
if self.type in {"choice", "multiple_choice", "confidence"}:
- if self.choices is None:
+ if self.choices is None: # type: ignore[attr-defined]
raise optparse.OptionError(
"must supply a list of choices for type 'choice'", self
)
- if not isinstance(self.choices, (tuple, list)):
+ if not isinstance(self.choices, (tuple, list)): # type: ignore[attr-defined]
raise optparse.OptionError(
# pylint: disable-next=consider-using-f-string
"choices must be a list of strings ('%s' supplied)"
- % str(type(self.choices)).split("'")[1],
+ % str(type(self.choices)).split("'")[1], # type: ignore[attr-defined]
self,
)
- elif self.choices is not None:
+ elif self.choices is not None: # type: ignore[attr-defined]
raise optparse.OptionError(
f"must not supply choices for type {self.type!r}", self
)
optparse.Option.CHECK_METHODS[2] = _check_choice # type: ignore[index]
- def process(self, opt, value, values, parser): # pragma: no cover # Argparse
+ def process( # pragma: no cover # Argparse
+ self, opt: Any, value: Any, values: Any, parser: Any
+ ) -> int:
+ assert isinstance(self.dest, str)
if self.callback and self.callback.__module__ == "pylint.lint.run":
return 1
# First, convert the value(s) to the right type. Howl if any
diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py
index 2f0aac75f..c468f494f 100644
--- a/pylint/config/option_manager_mixin.py
+++ b/pylint/config/option_manager_mixin.py
@@ -2,6 +2,7 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+
# pylint: disable=duplicate-code
from __future__ import annotations
@@ -14,31 +15,37 @@ import optparse # pylint: disable=deprecated-module
import os
import sys
import warnings
+from collections.abc import Iterator
from pathlib import Path
-from typing import Any, TextIO
+from typing import TYPE_CHECKING, Any, TextIO
from pylint import utils
from pylint.config.option import Option
-from pylint.config.option_parser import OptionParser
+from pylint.config.option_parser import OptionParser # type: ignore[attr-defined]
from pylint.typing import OptionDict
+if TYPE_CHECKING:
+ from pylint.config.options_provider_mixin import ( # type: ignore[attr-defined]
+ OptionsProviderMixin,
+ )
+
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
-def _expand_default(self, option):
+def _expand_default(self: optparse.HelpFormatter, option: Option) -> str:
"""Patch OptionParser.expand_default with custom behaviour.
This will handle defaults to avoid overriding values in the
configuration file.
"""
if self.parser is None or not self.default_tag:
- return option.help
+ return str(option.help)
optname = option._long_opts[0][2:]
try:
- provider = self.parser.options_manager._all_options[optname]
+ provider = self.parser.options_manager._all_options[optname] # type: ignore[attr-defined]
except KeyError:
value = None
else:
@@ -48,41 +55,42 @@ def _expand_default(self, option):
value = utils._format_option_value(optdict, value)
if value is optparse.NO_DEFAULT or not value:
value = self.NO_DEFAULT_VALUE
- return option.help.replace(self.default_tag, str(value))
+ return option.help.replace(self.default_tag, str(value)) # type: ignore[union-attr]
@contextlib.contextmanager
-def _patch_optparse():
+def _patch_optparse() -> Iterator[None]:
# pylint: disable = redefined-variable-type
orig_default = optparse.HelpFormatter
try:
- optparse.HelpFormatter.expand_default = _expand_default
+ optparse.HelpFormatter.expand_default = _expand_default # type: ignore[assignment]
yield
finally:
- optparse.HelpFormatter.expand_default = orig_default
+ optparse.HelpFormatter.expand_default = orig_default # type: ignore[assignment]
class OptionsManagerMixIn:
"""Handle configuration from both a configuration file and command line options."""
- def __init__(self, usage):
+ def __init__(self, usage: str) -> None:
# TODO: 3.0: Remove deprecated class
warnings.warn(
"OptionsManagerMixIn has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
self.reset_parsers(usage)
# list of registered options providers
- self.options_providers = []
+ self.options_providers: list[OptionsProviderMixin] = []
# dictionary associating option name to checker
- self._all_options = collections.OrderedDict()
- self._short_options = {}
- self._nocallback_options = {}
- self._mygroups = {}
+ self._all_options: collections.OrderedDict[Any, Any] = collections.OrderedDict()
+ self._short_options: dict[Any, Any] = {}
+ self._nocallback_options: dict[Any, Any] = {}
+ self._mygroups: dict[Any, Any] = {}
# verbosity
self._maxlevel = 0
- def reset_parsers(self, usage=""):
+ def reset_parsers(self, usage: str = "") -> None:
# configuration file parser
self.cfgfile_parser = configparser.ConfigParser(
inline_comment_prefixes=("#", ";")
@@ -92,7 +100,9 @@ class OptionsManagerMixIn:
self.cmdline_parser.options_manager = self
self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS)
- def register_options_provider(self, provider, own_group=True):
+ def register_options_provider(
+ self, provider: OptionsProviderMixin, own_group: bool = True
+ ) -> None:
"""Register an options provider."""
self.options_providers.append(provider)
non_group_spec_options = [
@@ -118,7 +128,9 @@ class OptionsManagerMixIn:
]
self.add_option_group(gname, gdoc, goptions, provider)
- def add_option_group(self, group_name, _, options, provider):
+ def add_option_group(
+ self, group_name: str, _: Any, options: Any, provider: OptionsProviderMixin
+ ) -> None:
# add option group to the command line parser
if group_name in self._mygroups:
group = self._mygroups[group_name]
@@ -131,7 +143,7 @@ class OptionsManagerMixIn:
# add section to the config file
if (
group_name != "DEFAULT"
- and group_name not in self.cfgfile_parser._sections
+ and group_name not in self.cfgfile_parser._sections # type: ignore[attr-defined]
):
self.cfgfile_parser.add_section(group_name)
# add provider's specific options
@@ -140,13 +152,21 @@ class OptionsManagerMixIn:
optdict["action"] = "callback"
self.add_optik_option(provider, group, opt, optdict)
- def add_optik_option(self, provider, optikcontainer, opt, optdict):
+ def add_optik_option(
+ self,
+ provider: OptionsProviderMixin,
+ optikcontainer: Any,
+ opt: str,
+ optdict: OptionDict,
+ ) -> None:
args, optdict = self.optik_option(provider, opt, optdict)
option = optikcontainer.add_option(*args, **optdict)
self._all_options[opt] = provider
self._maxlevel = max(self._maxlevel, option.level or 0)
- def optik_option(self, provider, opt, optdict):
+ def optik_option(
+ self, provider: OptionsProviderMixin, opt: str, optdict: OptionDict
+ ) -> tuple[list[str], OptionDict]:
"""Get our personal option definition and return a suitable form for
use with optik/optparse.
"""
@@ -164,12 +184,12 @@ class OptionsManagerMixIn:
and optdict.get("default") is not None
and optdict["action"] not in ("store_true", "store_false")
):
- optdict["help"] += " [current: %default]"
+ optdict["help"] += " [current: %default]" # type: ignore[operator]
del optdict["default"]
args = ["--" + str(opt)]
if "short" in optdict:
self._short_options[optdict["short"]] = opt
- args.append("-" + optdict["short"])
+ args.append("-" + optdict["short"]) # type: ignore[operator]
del optdict["short"]
# cleanup option definition dict before giving it to optik
for key in list(optdict.keys()):
@@ -177,7 +197,9 @@ class OptionsManagerMixIn:
optdict.pop(key)
return args, optdict
- def cb_set_provider_option(self, option, opt, value, parser):
+ def cb_set_provider_option(
+ self, option: Option, opt: str, value: Any, parser: Any
+ ) -> None:
"""Optik callback for option setting."""
if opt.startswith("--"):
# remove -- on long option
@@ -190,7 +212,7 @@ class OptionsManagerMixIn:
value = 1
self.global_set_option(opt, value)
- def global_set_option(self, opt, value):
+ def global_set_option(self, opt: str, value: Any) -> None:
"""Set option on the correct option provider."""
self._all_options[opt].set_option(opt, value)
@@ -229,7 +251,7 @@ class OptionsManagerMixIn:
)
printed = True
- def load_provider_defaults(self):
+ def load_provider_defaults(self) -> None:
"""Initialize configuration using default values."""
for provider in self.options_providers:
provider.load_defaults()
@@ -256,11 +278,11 @@ class OptionsManagerMixIn:
with open(config_file, encoding="utf_8_sig") as fp:
parser.read_file(fp)
# normalize each section's title
- for sect, values in list(parser._sections.items()):
+ for sect, values in list(parser._sections.items()): # type: ignore[attr-defined]
if sect.startswith("pylint."):
sect = sect[len("pylint.") :]
if not sect.isupper() and values:
- parser._sections[sect.upper()] = values
+ parser._sections[sect.upper()] = values # type: ignore[attr-defined]
if not verbose:
return
@@ -302,7 +324,7 @@ class OptionsManagerMixIn:
parser.add_section(section_name)
parser.set(section_name, option, value=value)
- def load_config_file(self):
+ def load_config_file(self) -> None:
"""Dispatch values previously read from a configuration file to each
option's provider.
"""
@@ -314,17 +336,19 @@ class OptionsManagerMixIn:
except (KeyError, optparse.OptionError):
continue
- def load_configuration(self, **kwargs):
+ def load_configuration(self, **kwargs: Any) -> None:
"""Override configuration according to given parameters."""
return self.load_configuration_from_config(kwargs)
- def load_configuration_from_config(self, config):
+ def load_configuration_from_config(self, config: dict[str, Any]) -> None:
for opt, opt_value in config.items():
opt = opt.replace("_", "-")
provider = self._all_options[opt]
provider.set_option(opt, opt_value)
- def load_command_line_configuration(self, args=None) -> list[str]:
+ def load_command_line_configuration(
+ self, args: list[str] | None = None
+ ) -> list[str]:
"""Override configuration according to command line parameters.
return additional arguments
@@ -339,10 +363,10 @@ class OptionsManagerMixIn:
if value is None:
continue
setattr(config, attr, value)
- return args
+ return args # type: ignore[return-value]
- def help(self, level=0):
+ def help(self, level: int = 0) -> str:
"""Return the usage string for available options."""
self.cmdline_parser.formatter.output_level = level
with _patch_optparse():
- return self.cmdline_parser.format_help()
+ return str(self.cmdline_parser.format_help())
diff --git a/pylint/config/option_parser.py b/pylint/config/option_parser.py
index b58fad3a4..c527c4f60 100644
--- a/pylint/config/option_parser.py
+++ b/pylint/config/option_parser.py
@@ -2,6 +2,8 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+# type: ignore # Deprecated module.
+
import optparse # pylint: disable=deprecated-module
import warnings
@@ -23,6 +25,7 @@ class OptionParser(optparse.OptionParser):
warnings.warn(
"OptionParser has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
super().__init__(option_class=Option, *args, **kwargs)
diff --git a/pylint/config/options_provider_mixin.py b/pylint/config/options_provider_mixin.py
index 5b20a290f..67f64ee0a 100644
--- a/pylint/config/options_provider_mixin.py
+++ b/pylint/config/options_provider_mixin.py
@@ -2,6 +2,8 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+# type: ignore # Deprecated module.
+
import optparse # pylint: disable=deprecated-module
import warnings
@@ -27,6 +29,7 @@ class OptionsProviderMixIn:
warnings.warn(
"OptionsProviderMixIn has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
self.config = optparse.Values()
self.load_defaults()
diff --git a/pylint/config/utils.py b/pylint/config/utils.py
index bd6146336..d7cbd7c07 100644
--- a/pylint/config/utils.py
+++ b/pylint/config/utils.py
@@ -42,7 +42,8 @@ def _convert_option_to_argument(
if "level" in optdict and "hide" not in optdict:
warnings.warn(
"The 'level' key in optdicts has been deprecated. "
- "Use 'hide' with a boolean to hide an option from the help message.",
+ "Use 'hide' with a boolean to hide an option from the help message. "
+ f"optdict={optdict}",
DeprecationWarning,
)
@@ -79,7 +80,8 @@ def _convert_option_to_argument(
warnings.warn(
"An option dictionary should have a 'default' key to specify "
"the option's default value. This key will be required in pylint "
- "3.0. It is not required for 'store_true' and callable actions.",
+ "3.0. It is not required for 'store_true' and callable actions. "
+ f"optdict={optdict}",
DeprecationWarning,
)
default = None
@@ -151,7 +153,7 @@ def _parse_rich_type_value(value: Any) -> str:
if isinstance(value, (list, tuple)):
return ",".join(_parse_rich_type_value(i) for i in value)
if isinstance(value, re.Pattern):
- return value.pattern
+ return str(value.pattern)
if isinstance(value, dict):
return ",".join(f"{k}:{v}" for k, v in value.items())
return str(value)
diff --git a/pylint/constants.py b/pylint/constants.py
index 6ad5b82d3..d9ff20c46 100644
--- a/pylint/constants.py
+++ b/pylint/constants.py
@@ -168,3 +168,133 @@ TYPING_NEVER = frozenset(
"typing_extensions.Never",
)
)
+
+DUNDER_METHODS: dict[tuple[int, int], dict[str, str]] = {
+ (0, 0): {
+ "__init__": "Instantiate class directly",
+ "__del__": "Use del keyword",
+ "__repr__": "Use repr built-in function",
+ "__str__": "Use str built-in function",
+ "__bytes__": "Use bytes built-in function",
+ "__format__": "Use format built-in function, format string method, or f-string",
+ "__lt__": "Use < operator",
+ "__le__": "Use <= operator",
+ "__eq__": "Use == operator",
+ "__ne__": "Use != operator",
+ "__gt__": "Use > operator",
+ "__ge__": "Use >= operator",
+ "__hash__": "Use hash built-in function",
+ "__bool__": "Use bool built-in function",
+ "__getattr__": "Access attribute directly or use getattr built-in function",
+ "__getattribute__": "Access attribute directly or use getattr built-in function",
+ "__setattr__": "Set attribute directly or use setattr built-in function",
+ "__delattr__": "Use del keyword",
+ "__dir__": "Use dir built-in function",
+ "__get__": "Use get method",
+ "__set__": "Use set method",
+ "__delete__": "Use del keyword",
+ "__instancecheck__": "Use isinstance built-in function",
+ "__subclasscheck__": "Use issubclass built-in function",
+ "__call__": "Invoke instance directly",
+ "__len__": "Use len built-in function",
+ "__length_hint__": "Use length_hint method",
+ "__getitem__": "Access item via subscript",
+ "__setitem__": "Set item via subscript",
+ "__delitem__": "Use del keyword",
+ "__iter__": "Use iter built-in function",
+ "__next__": "Use next built-in function",
+ "__reversed__": "Use reversed built-in function",
+ "__contains__": "Use in keyword",
+ "__add__": "Use + operator",
+ "__sub__": "Use - operator",
+ "__mul__": "Use * operator",
+ "__matmul__": "Use @ operator",
+ "__truediv__": "Use / operator",
+ "__floordiv__": "Use // operator",
+ "__mod__": "Use % operator",
+ "__divmod__": "Use divmod built-in function",
+ "__pow__": "Use ** operator or pow built-in function",
+ "__lshift__": "Use << operator",
+ "__rshift__": "Use >> operator",
+ "__and__": "Use & operator",
+ "__xor__": "Use ^ operator",
+ "__or__": "Use | operator",
+ "__radd__": "Use + operator",
+ "__rsub__": "Use - operator",
+ "__rmul__": "Use * operator",
+ "__rmatmul__": "Use @ operator",
+ "__rtruediv__": "Use / operator",
+ "__rfloordiv__": "Use // operator",
+ "__rmod__": "Use % operator",
+ "__rdivmod__": "Use divmod built-in function",
+ "__rpow__": "Use ** operator or pow built-in function",
+ "__rlshift__": "Use << operator",
+ "__rrshift__": "Use >> operator",
+ "__rand__": "Use & operator",
+ "__rxor__": "Use ^ operator",
+ "__ror__": "Use | operator",
+ "__iadd__": "Use += operator",
+ "__isub__": "Use -= operator",
+ "__imul__": "Use *= operator",
+ "__imatmul__": "Use @= operator",
+ "__itruediv__": "Use /= operator",
+ "__ifloordiv__": "Use //= operator",
+ "__imod__": "Use %= operator",
+ "__ipow__": "Use **= operator",
+ "__ilshift__": "Use <<= operator",
+ "__irshift__": "Use >>= operator",
+ "__iand__": "Use &= operator",
+ "__ixor__": "Use ^= operator",
+ "__ior__": "Use |= operator",
+ "__neg__": "Multiply by -1 instead",
+ "__pos__": "Multiply by +1 instead",
+ "__abs__": "Use abs built-in function",
+ "__invert__": "Use ~ operator",
+ "__complex__": "Use complex built-in function",
+ "__int__": "Use int built-in function",
+ "__float__": "Use float built-in function",
+ "__round__": "Use round built-in function",
+ "__trunc__": "Use math.trunc function",
+ "__floor__": "Use math.floor function",
+ "__ceil__": "Use math.ceil function",
+ "__enter__": "Invoke context manager directly",
+ "__aenter__": "Invoke context manager directly",
+ "__copy__": "Use copy.copy function",
+ "__deepcopy__": "Use copy.deepcopy function",
+ "__fspath__": "Use os.fspath function instead",
+ },
+ (3, 10): {
+ "__aiter__": "Use aiter built-in function",
+ "__anext__": "Use anext built-in function",
+ },
+}
+
+EXTRA_DUNDER_METHODS = [
+ "__new__",
+ "__subclasses__",
+ "__init_subclass__",
+ "__set_name__",
+ "__class_getitem__",
+ "__missing__",
+ "__exit__",
+ "__await__",
+ "__aexit__",
+ "__getnewargs_ex__",
+ "__getnewargs__",
+ "__getstate__",
+ "__setstate__",
+ "__reduce__",
+ "__reduce_ex__",
+ "__post_init__", # part of `dataclasses` module
+]
+
+DUNDER_PROPERTIES = [
+ "__class__",
+ "__dict__",
+ "__doc__",
+ "__format__",
+ "__module__",
+ "__sizeof__",
+ "__subclasshook__",
+ "__weakref__",
+]
diff --git a/pylint/epylint.py b/pylint/epylint.py
index b6b6bf402..a69ce0d87 100755
--- a/pylint/epylint.py
+++ b/pylint/epylint.py
@@ -41,6 +41,7 @@ from __future__ import annotations
import os
import shlex
import sys
+import warnings
from collections.abc import Sequence
from io import StringIO
from subprocess import PIPE, Popen
@@ -168,6 +169,11 @@ def py_run(
To silently run Pylint on a module, and get its standard output and error:
>>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True)
"""
+ warnings.warn(
+ "'epylint' will be removed in pylint 3.0, use https://github.com/emacsorphanage/pylint instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
# Detect if we use Python as executable or not, else default to `python`
executable = sys.executable if "python" in sys.executable else "python"
@@ -198,6 +204,11 @@ def py_run(
def Run(argv: Sequence[str] | None = None) -> NoReturn:
+ warnings.warn(
+ "'epylint' will be removed in pylint 3.0, use https://github.com/emacsorphanage/pylint instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
if not argv and len(sys.argv) == 1:
print(f"Usage: {sys.argv[0]} <filename> [options]")
sys.exit(1)
diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py
index d8f797b6c..46f0a2ac6 100644
--- a/pylint/extensions/_check_docs_utils.py
+++ b/pylint/extensions/_check_docs_utils.py
@@ -43,7 +43,7 @@ def get_setters_property_name(node: nodes.FunctionDef) -> str | None:
and decorator.attrname == "setter"
and isinstance(decorator.expr, nodes.Name)
):
- return decorator.expr.name
+ return decorator.expr.name # type: ignore[no-any-return]
return None
diff --git a/pylint/extensions/bad_builtin.py b/pylint/extensions/bad_builtin.py
index 7ffaf0f6c..dd6ab3841 100644
--- a/pylint/extensions/bad_builtin.py
+++ b/pylint/extensions/bad_builtin.py
@@ -18,8 +18,8 @@ if TYPE_CHECKING:
BAD_FUNCTIONS = ["map", "filter"]
# Some hints regarding the use of bad builtins.
-BUILTIN_HINTS = {"map": "Using a list comprehension can be clearer."}
-BUILTIN_HINTS["filter"] = BUILTIN_HINTS["map"]
+LIST_COMP_MSG = "Using a list comprehension can be clearer."
+BUILTIN_HINTS = {"map": LIST_COMP_MSG, "filter": LIST_COMP_MSG}
class BadBuiltinChecker(BaseChecker):
diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py
index ef8aaea78..24eb7f667 100644
--- a/pylint/extensions/code_style.py
+++ b/pylint/extensions/code_style.py
@@ -5,12 +5,13 @@
from __future__ import annotations
import sys
-from typing import TYPE_CHECKING, Tuple, Type, Union, cast
+from typing import TYPE_CHECKING, Tuple, Type, cast
from astroid import nodes
from pylint.checkers import BaseChecker, utils
from pylint.checkers.utils import only_required_for_messages, safe_infer
+from pylint.interfaces import INFERENCE
if TYPE_CHECKING:
from pylint.lint import PyLinter
@@ -58,6 +59,16 @@ class CodeStyleChecker(BaseChecker):
"both can be combined by using an assignment expression ``:=``. "
"Requires Python 3.8 and ``py-version >= 3.8``.",
),
+ "R6104": (
+ "Use '%s' to do an augmented assign directly",
+ "consider-using-augmented-assign",
+ "Emitted when an assignment is referring to the object that it is assigning "
+ "to. This can be changed to be an augmented assign.\n"
+ "Disabled by default!",
+ {
+ "default_enabled": False,
+ },
+ ),
}
options = (
(
@@ -164,13 +175,11 @@ class CodeStyleChecker(BaseChecker):
if list_length == 0:
return
for _, dict_value in node.items[1:]:
- dict_value = cast(Union[nodes.List, nodes.Tuple], dict_value)
if len(dict_value.elts) != list_length:
return
# Make sure at least one list entry isn't a dict
for _, dict_value in node.items:
- dict_value = cast(Union[nodes.List, nodes.Tuple], dict_value)
if all(isinstance(entry, nodes.Dict) for entry in dict_value.elts):
return
@@ -303,6 +312,19 @@ class CodeStyleChecker(BaseChecker):
return True
return False
+ @only_required_for_messages("consider-using-augmented-assign")
+ def visit_assign(self, node: nodes.Assign) -> None:
+ is_aug, op = utils.is_augmented_assign(node)
+ if is_aug:
+ self.add_message(
+ "consider-using-augmented-assign",
+ args=f"{op}=",
+ node=node,
+ line=node.lineno,
+ col_offset=node.col_offset,
+ confidence=INFERENCE,
+ )
+
def register(linter: PyLinter) -> None:
linter.register_checker(CodeStyleChecker(linter))
diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py
index 8aca300d2..116bf229a 100644
--- a/pylint/extensions/comparetozero.py
+++ b/pylint/extensions/comparetozero.py
@@ -14,13 +14,18 @@ from astroid import nodes
from pylint import checkers
from pylint.checkers import utils
+from pylint.interfaces import HIGH
if TYPE_CHECKING:
from pylint.lint import PyLinter
def _is_constant_zero(node: str | nodes.NodeNG) -> bool:
- return isinstance(node, astroid.Const) and node.value == 0
+ # We have to check that node.value is not False because node.value == 0 is True
+ # when node.value is False
+ return (
+ isinstance(node, astroid.Const) and node.value == 0 and node.value is not False
+ )
class CompareToZeroChecker(checkers.BaseChecker):
@@ -35,7 +40,7 @@ class CompareToZeroChecker(checkers.BaseChecker):
name = "compare-to-zero"
msgs = {
"C2001": (
- "Avoid comparisons to zero",
+ '"%s" can be simplified to "%s" as 0 is falsey',
"compare-to-zero",
"Used when Pylint detects comparison to a 0 constant.",
)
@@ -65,12 +70,25 @@ class CompareToZeroChecker(checkers.BaseChecker):
# 0 ?? X
if _is_constant_zero(op_1) and op_2 in _operators:
error_detected = True
+ op = op_3
# X ?? 0
elif op_2 in _operators and _is_constant_zero(op_3):
error_detected = True
+ op = op_1
if error_detected:
- self.add_message("compare-to-zero", node=node)
+ original = f"{op_1.as_string()} {op_2} {op_3.as_string()}"
+ suggestion = (
+ op.as_string()
+ if op_2 in {"!=", "is not"}
+ else f"not {op.as_string()}"
+ )
+ self.add_message(
+ "compare-to-zero",
+ args=(original, suggestion),
+ node=node,
+ confidence=HIGH,
+ )
def register(linter: PyLinter) -> None:
diff --git a/pylint/extensions/dict_init_mutate.py b/pylint/extensions/dict_init_mutate.py
new file mode 100644
index 000000000..fb4c83647
--- /dev/null
+++ b/pylint/extensions/dict_init_mutate.py
@@ -0,0 +1,66 @@
+# Licensed under the GPL: https://www.gnu.org/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 dictionary mutation after initialization."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import only_required_for_messages
+from pylint.interfaces import HIGH
+
+if TYPE_CHECKING:
+ from pylint.lint.pylinter import PyLinter
+
+
+class DictInitMutateChecker(BaseChecker):
+ name = "dict-init-mutate"
+ msgs = {
+ "C3401": (
+ "Declare all known key/values when initializing the dictionary.",
+ "dict-init-mutate",
+ "Dictionaries can be initialized with a single statement "
+ "using dictionary literal syntax.",
+ )
+ }
+
+ @only_required_for_messages("dict-init-mutate")
+ def visit_assign(self, node: nodes.Assign) -> None:
+ """
+ Detect dictionary mutation immediately after initialization.
+
+ At this time, detecting nested mutation is not supported.
+ """
+ if not isinstance(node.value, nodes.Dict):
+ return
+
+ dict_name = node.targets[0]
+ if len(node.targets) != 1 or not isinstance(dict_name, nodes.AssignName):
+ return
+
+ first_sibling = node.next_sibling()
+ if (
+ not first_sibling
+ or not isinstance(first_sibling, nodes.Assign)
+ or len(first_sibling.targets) != 1
+ ):
+ return
+
+ sibling_target = first_sibling.targets[0]
+ if not isinstance(sibling_target, nodes.Subscript):
+ return
+
+ sibling_name = sibling_target.value
+ if not isinstance(sibling_name, nodes.Name):
+ return
+
+ if sibling_name.name == dict_name.name:
+ self.add_message("dict-init-mutate", node=node, confidence=HIGH)
+
+
+def register(linter: PyLinter) -> None:
+ linter.register_checker(DictInitMutateChecker(linter))
diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py
index 19d9cf890..62ada6824 100644
--- a/pylint/extensions/docparams.py
+++ b/pylint/extensions/docparams.py
@@ -298,10 +298,14 @@ class DocstringParameterChecker(BaseChecker):
doc = utils.docstringify(
func_node.doc_node, self.linter.config.default_docstring_type
)
+
+ if self.linter.config.accept_no_raise_doc and not doc.exceptions():
+ return
+
if not doc.matching_sections():
if doc.doc:
missing = {exc.name for exc in expected_excs}
- self._handle_no_raise_doc(missing, func_node)
+ self._add_raise_message(missing, func_node)
return
found_excs_full_names = doc.exceptions()
@@ -652,12 +656,6 @@ class DocstringParameterChecker(BaseChecker):
confidence=HIGH,
)
- def _handle_no_raise_doc(self, excs: set[str], node: nodes.FunctionDef) -> None:
- if self.linter.config.accept_no_raise_doc:
- return
-
- self._add_raise_message(excs, node)
-
def _add_raise_message(
self, missing_exceptions: set[str], node: nodes.FunctionDef
) -> None:
diff --git a/pylint/extensions/dunder.py b/pylint/extensions/dunder.py
new file mode 100644
index 000000000..e0e9af316
--- /dev/null
+++ b/pylint/extensions/dunder.py
@@ -0,0 +1,77 @@
+# Licensed under the GPL: https://www.gnu.org/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 __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+
+from pylint.checkers import BaseChecker
+from pylint.constants import DUNDER_METHODS, DUNDER_PROPERTIES, EXTRA_DUNDER_METHODS
+from pylint.interfaces import HIGH
+
+if TYPE_CHECKING:
+ from pylint.lint import PyLinter
+
+
+class DunderChecker(BaseChecker):
+ """Checks related to dunder methods."""
+
+ name = "dunder"
+ priority = -1
+ msgs = {
+ "W3201": (
+ "Bad or misspelled dunder method name %s.",
+ "bad-dunder-name",
+ "Used when a dunder method is misspelled or defined with a name "
+ "not within the predefined list of dunder names.",
+ ),
+ }
+ options = (
+ (
+ "good-dunder-names",
+ {
+ "default": [],
+ "type": "csv",
+ "metavar": "<comma-separated names>",
+ "help": "Good dunder names which should always be accepted.",
+ },
+ ),
+ )
+
+ def open(self) -> None:
+ self._dunder_methods = (
+ EXTRA_DUNDER_METHODS
+ + DUNDER_PROPERTIES
+ + self.linter.config.good_dunder_names
+ )
+ for since_vers, dunder_methods in DUNDER_METHODS.items():
+ if since_vers <= self.linter.config.py_version:
+ self._dunder_methods.extend(list(dunder_methods.keys()))
+
+ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
+ """Check if known dunder method is misspelled or dunder name is not one
+ of the pre-defined names.
+ """
+ # ignore module-level functions
+ if not node.is_method():
+ return
+
+ # Detect something that could be a bad dunder method
+ if (
+ node.name.startswith("_")
+ and node.name.endswith("_")
+ and node.name not in self._dunder_methods
+ ):
+ self.add_message(
+ "bad-dunder-name",
+ node=node,
+ args=(node.name),
+ confidence=HIGH,
+ )
+
+
+def register(linter: PyLinter) -> None:
+ linter.register_checker(DunderChecker(linter))
diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py
index ec2839bdd..f96a980f5 100644
--- a/pylint/extensions/emptystring.py
+++ b/pylint/extensions/emptystring.py
@@ -7,31 +7,23 @@
from __future__ import annotations
import itertools
-from collections.abc import Iterable
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING
from astroid import nodes
from pylint import checkers
from pylint.checkers import utils
+from pylint.interfaces import HIGH
if TYPE_CHECKING:
from pylint.lint import PyLinter
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!
- """
-
- # configuration section name
name = "compare-to-empty-string"
msgs = {
"C1901": (
- "Avoid comparisons to empty string",
+ '"%s" can be simplified to "%s" as an empty string is falsey',
"compare-to-empty-string",
"Used when Pylint detects comparison to an empty string constant.",
)
@@ -41,31 +33,45 @@ class CompareToEmptyStringChecker(checkers.BaseChecker):
@utils.only_required_for_messages("compare-to-empty-string")
def visit_compare(self, node: nodes.Compare) -> None:
- _operators = ["!=", "==", "is not", "is"]
- # note: astroid.Compare has the left most operand in node.left
- # while the rest are a list of tuples in node.ops
- # the format of the tuple is ('compare operator sign', node)
- # here we squash everything into `ops` to make it easier for processing later
- ops = [("", node.left)]
+ """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!
+ """
+ _operators = {"!=", "==", "is not", "is"}
+ # note: astroid.Compare has the left most operand in node.left while the rest
+ # are a list of tuples in node.ops the format of the tuple is
+ # ('compare operator sign', node) here we squash everything into `ops`
+ # to make it easier for processing later
+ ops: list[tuple[str, nodes.NodeNG | None]] = [("", node.left)]
ops.extend(node.ops)
- iter_ops: Iterable[Any] = iter(ops)
- ops = list(itertools.chain(*iter_ops))
-
+ iter_ops = iter(ops)
+ ops = list(itertools.chain(*iter_ops)) # type: ignore[arg-type]
for ops_idx in range(len(ops) - 2):
- op_1 = ops[ops_idx]
- op_2 = ops[ops_idx + 1]
- op_3 = ops[ops_idx + 2]
+ op_1: nodes.NodeNG | None = ops[ops_idx]
+ op_2: str = ops[ops_idx + 1] # type: ignore[assignment]
+ op_3: nodes.NodeNG | None = ops[ops_idx + 2]
error_detected = False
-
+ if op_1 is None or op_3 is None or op_2 not in _operators:
+ continue
+ node_name = ""
# x ?? ""
- if utils.is_empty_str_literal(op_1) and op_2 in _operators:
+ if utils.is_empty_str_literal(op_1):
error_detected = True
+ node_name = op_3.as_string()
# '' ?? X
- elif op_2 in _operators and utils.is_empty_str_literal(op_3):
+ elif utils.is_empty_str_literal(op_3):
error_detected = True
-
+ node_name = op_1.as_string()
if error_detected:
- self.add_message("compare-to-empty-string", node=node)
+ suggestion = f"not {node_name}" if op_2 in {"==", "is"} else node_name
+ self.add_message(
+ "compare-to-empty-string",
+ args=(node.as_string(), suggestion),
+ node=node,
+ confidence=HIGH,
+ )
def register(linter: PyLinter) -> None:
diff --git a/pylint/extensions/for_any_all.py b/pylint/extensions/for_any_all.py
index e6ab41c3f..257b5d37f 100644
--- a/pylint/extensions/for_any_all.py
+++ b/pylint/extensions/for_any_all.py
@@ -11,7 +11,12 @@ from typing import TYPE_CHECKING
from astroid import nodes
from pylint.checkers import BaseChecker
-from pylint.checkers.utils import only_required_for_messages, returns_bool
+from pylint.checkers.utils import (
+ assigned_bool,
+ only_required_for_messages,
+ returns_bool,
+)
+from pylint.interfaces import HIGH
if TYPE_CHECKING:
from pylint.lint.pylinter import PyLinter
@@ -36,19 +41,100 @@ class ConsiderUsingAnyOrAllChecker(BaseChecker):
return
if_children = list(node.body[0].get_children())
- if not len(if_children) == 2: # The If node has only a comparison and return
- return
- if not returns_bool(if_children[1]):
+ if any(isinstance(child, nodes.If) for child in if_children):
+ # an if node within the if-children indicates an elif clause,
+ # suggesting complex logic.
return
- # Check for terminating boolean return right after the loop
node_after_loop = node.next_sibling()
- if returns_bool(node_after_loop):
+
+ if self._assigned_reassigned_returned(node, if_children, node_after_loop):
+ final_return_bool = node_after_loop.value.name
+ suggested_string = self._build_suggested_string(node, final_return_bool)
+ self.add_message(
+ "consider-using-any-or-all",
+ node=node,
+ args=suggested_string,
+ confidence=HIGH,
+ )
+ return
+
+ if self._if_statement_returns_bool(if_children, node_after_loop):
final_return_bool = node_after_loop.value.value
suggested_string = self._build_suggested_string(node, final_return_bool)
self.add_message(
- "consider-using-any-or-all", node=node, args=suggested_string
+ "consider-using-any-or-all",
+ node=node,
+ args=suggested_string,
+ confidence=HIGH,
)
+ return
+
+ @staticmethod
+ def _if_statement_returns_bool(
+ if_children: list[nodes.NodeNG], node_after_loop: nodes.NodeNG
+ ) -> bool:
+ """Detect for-loop, if-statement, return pattern:
+
+ Ex:
+ def any_uneven(items):
+ for item in items:
+ if not item % 2 == 0:
+ return True
+ return False
+ """
+ if not len(if_children) == 2:
+ # The If node has only a comparison and return
+ return False
+ if not returns_bool(if_children[1]):
+ return False
+
+ # Check for terminating boolean return right after the loop
+ return returns_bool(node_after_loop)
+
+ @staticmethod
+ def _assigned_reassigned_returned(
+ node: nodes.For, if_children: list[nodes.NodeNG], node_after_loop: nodes.NodeNG
+ ) -> bool:
+ """Detect boolean-assign, for-loop, re-assign, return pattern:
+
+ Ex:
+ def check_lines(lines, max_chars):
+ long_line = False
+ for line in lines:
+ if len(line) > max_chars:
+ long_line = True
+ # no elif / else statement
+ return long_line
+ """
+ node_before_loop = node.previous_sibling()
+
+ if not assigned_bool(node_before_loop):
+ # node before loop isn't assigning to boolean
+ return False
+
+ assign_children = [x for x in if_children if isinstance(x, nodes.Assign)]
+ if not assign_children:
+ # if-nodes inside loop aren't assignments
+ return False
+
+ # We only care for the first assign node of the if-children. Otherwise it breaks the pattern.
+ first_target = assign_children[0].targets[0]
+ target_before_loop = node_before_loop.targets[0]
+
+ if not (
+ isinstance(first_target, nodes.AssignName)
+ and isinstance(target_before_loop, nodes.AssignName)
+ ):
+ return False
+
+ node_before_loop_name = node_before_loop.targets[0].name
+ return (
+ first_target.name == node_before_loop_name
+ and isinstance(node_after_loop, nodes.Return)
+ and isinstance(node_after_loop.value, nodes.Name)
+ and node_after_loop.value.name == node_before_loop_name
+ )
@staticmethod
def _build_suggested_string(node: nodes.For, final_return_bool: bool) -> str:
diff --git a/pylint/extensions/magic_value.py b/pylint/extensions/magic_value.py
new file mode 100644
index 000000000..161bb2c95
--- /dev/null
+++ b/pylint/extensions/magic_value.py
@@ -0,0 +1,88 @@
+# Licensed under the GPL: https://www.gnu.org/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
+
+"""Checks for magic values instead of literals."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+
+from pylint.checkers import BaseChecker, utils
+from pylint.interfaces import HIGH
+
+if TYPE_CHECKING:
+ from pylint.lint import PyLinter
+
+
+class MagicValueChecker(BaseChecker):
+ """Checks for constants in comparisons."""
+
+ name = "magic-value"
+ msgs = {
+ "R2004": (
+ "Consider using a named constant or an enum instead of '%s'.",
+ "magic-value-comparison",
+ "Using named constants instead of magic values helps improve readability and maintainability of your"
+ " code, try to avoid them in comparisons.",
+ )
+ }
+
+ options = (
+ (
+ "valid-magic-values",
+ {
+ "default": (0, -1, 1, "", "__main__"),
+ "type": "csv",
+ "metavar": "<argument names>",
+ "help": " List of valid magic values that `magic-value-compare` will not detect.",
+ },
+ ),
+ )
+
+ def _check_constants_comparison(self, node: nodes.Compare) -> None:
+ """
+ Magic values in any side of the comparison should be avoided,
+ Detects comparisons that `comparison-of-constants` core checker cannot detect.
+ """
+ const_operands = []
+ LEFT_OPERAND = 0
+ RIGHT_OPERAND = 1
+
+ left_operand = node.left
+ const_operands.append(isinstance(left_operand, nodes.Const))
+
+ right_operand = node.ops[0][1]
+ const_operands.append(isinstance(right_operand, nodes.Const))
+
+ if all(const_operands):
+ # `comparison-of-constants` avoided
+ return
+
+ operand_value = None
+ if const_operands[LEFT_OPERAND] and self._is_magic_value(left_operand):
+ operand_value = left_operand.value
+ elif const_operands[RIGHT_OPERAND] and self._is_magic_value(right_operand):
+ operand_value = right_operand.value
+ if operand_value is not None:
+ self.add_message(
+ "magic-value-comparison",
+ node=node,
+ args=(operand_value),
+ confidence=HIGH,
+ )
+
+ def _is_magic_value(self, node: nodes.NodeNG) -> bool:
+ return (not utils.is_singleton_const(node)) and (
+ node.value not in self.linter.config.valid_magic_values
+ )
+
+ @utils.only_required_for_messages("magic-comparison")
+ def visit_compare(self, node: nodes.Compare) -> None:
+ self._check_constants_comparison(node)
+
+
+def register(linter: PyLinter) -> None:
+ linter.register_checker(MagicValueChecker(linter))
diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py
index e46bd5fbd..ea64d2ebf 100644
--- a/pylint/extensions/mccabe.py
+++ b/pylint/extensions/mccabe.py
@@ -45,13 +45,13 @@ _AppendableNodeT = TypeVar(
)
-class PathGraph(Mccabe_PathGraph):
+class PathGraph(Mccabe_PathGraph): # type: ignore[misc]
def __init__(self, node: _SubGraphNodes | nodes.FunctionDef):
super().__init__(name="", entity="", lineno=1)
self.root = node
-class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor):
+class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor): # type: ignore[misc]
def __init__(self) -> None:
super().__init__()
self._bottom_counter = 0
diff --git a/pylint/extensions/private_import.py b/pylint/extensions/private_import.py
index 53e285ac3..61d37af37 100644
--- a/pylint/extensions/private_import.py
+++ b/pylint/extensions/private_import.py
@@ -195,7 +195,7 @@ class PrivateImportChecker(BaseChecker):
"""
if isinstance(node, nodes.Name) and node.name not in all_used_type_annotations:
all_used_type_annotations[node.name] = True
- return node.name
+ return node.name # type: ignore[no-any-return]
if isinstance(node, nodes.Subscript): # e.g. Optional[List[str]]
# slice is the next nested type
self._populate_type_annotations_annotation(
diff --git a/pylint/interfaces.py b/pylint/interfaces.py
index a4d1288d8..221084fab 100644
--- a/pylint/interfaces.py
+++ b/pylint/interfaces.py
@@ -7,9 +7,8 @@
from __future__ import annotations
import warnings
-from collections import namedtuple
from tokenize import TokenInfo
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, NamedTuple
from astroid import nodes
@@ -33,7 +32,12 @@ __all__ = (
"CONFIDENCE_LEVEL_NAMES",
)
-Confidence = namedtuple("Confidence", ["name", "description"])
+
+class Confidence(NamedTuple):
+ name: str
+ description: str
+
+
# Warning Certainties
HIGH = Confidence("HIGH", "Warning that is not based on inference result.")
CONTROL_FLOW = Confidence(
@@ -57,6 +61,7 @@ class Interface:
"Interface and all of its subclasses have been deprecated "
"and will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
@classmethod
@@ -78,6 +83,7 @@ def implements(
"implements has been deprecated in favour of using basic "
"inheritance patterns without using __implements__.",
DeprecationWarning,
+ stacklevel=2,
)
implements_ = getattr(obj, "__implements__", ())
if not isinstance(implements_, (list, tuple)):
diff --git a/pylint/lint/base_options.py b/pylint/lint/base_options.py
index 3ea95c745..6de88e44e 100644
--- a/pylint/lint/base_options.py
+++ b/pylint/lint/base_options.py
@@ -69,7 +69,7 @@ def _make_linter_options(linter: PyLinter) -> Options:
"default": [],
"help": "Add files or directories matching the regular expressions patterns to the "
"ignore-list. The regex matches against paths and can be in "
- "Posix or Windows format. Because '\\' represents the directory delimiter "
+ "Posix or Windows format. Because '\\\\' represents the directory delimiter "
"on Windows systems, it can't be used as an escape character.",
},
),
diff --git a/pylint/lint/caching.py b/pylint/lint/caching.py
index 573b97628..8ea8a2236 100644
--- a/pylint/lint/caching.py
+++ b/pylint/lint/caching.py
@@ -12,12 +12,14 @@ from pathlib import Path
from pylint.constants import PYLINT_HOME
from pylint.utils import LinterStats
+PYLINT_HOME_AS_PATH = Path(PYLINT_HOME)
+
def _get_pdata_path(
- base_name: Path, recurs: int, pylint_home: Path = Path(PYLINT_HOME)
+ base_name: Path, recurs: int, pylint_home: Path = PYLINT_HOME_AS_PATH
) -> Path:
- # We strip all characters that can't be used in a filename
- # Also strip '/' and '\\' because we want to create a single file, not sub-directories
+ # We strip all characters that can't be used in a filename. Also strip '/' and
+ # '\\' because we want to create a single file, not sub-directories.
underscored_name = "_".join(
str(p.replace(":", "_").replace("/", "_").replace("\\", "_"))
for p in base_name.parts
diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py
index 2f2b3581b..e43208dea 100644
--- a/pylint/lint/expand_modules.py
+++ b/pylint/lint/expand_modules.py
@@ -18,7 +18,7 @@ def _modpath_from_file(filename: str, is_namespace: bool, path: list[str]) -> li
def _is_package_cb(inner_path: str, parts: list[str]) -> bool:
return modutils.check_modpath_has_init(inner_path, parts) or is_namespace
- return modutils.modpath_from_file_with_callback(
+ return modutils.modpath_from_file_with_callback( # type: ignore[no-any-return]
filename, path=path, is_package_cb=_is_package_cb
)
diff --git a/pylint/lint/message_state_handler.py b/pylint/lint/message_state_handler.py
index 4cd40e276..7c81c2c86 100644
--- a/pylint/lint/message_state_handler.py
+++ b/pylint/lint/message_state_handler.py
@@ -361,7 +361,7 @@ class _MessageStateHandler:
match = OPTION_PO.search(content)
if match is None:
continue
- try:
+ try: # pylint: disable = too-many-try-statements
for pragma_repr in parse_pragma(match.group(2)):
if pragma_repr.action in {"disable-all", "skip-file"}:
if pragma_repr.action == "disable-all":
diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py
index 646d26994..8d0053108 100644
--- a/pylint/lint/parallel.py
+++ b/pylint/lint/parallel.py
@@ -67,7 +67,7 @@ def _worker_check_single_file(
defaultdict[str, list[Any]],
]:
if not _worker_linter:
- raise Exception("Worker linter not yet initialised")
+ raise RuntimeError("Worker linter not yet initialised")
_worker_linter.open()
_worker_linter.check_single_file_item(file_item)
mapreduce_data = defaultdict(list)
diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py
index 5a600eb5b..70f27c7c0 100644
--- a/pylint/lint/pylinter.py
+++ b/pylint/lint/pylinter.py
@@ -95,7 +95,7 @@ def _load_reporter_by_class(reporter_class: str) -> type[BaseReporter]:
class_name = qname.split(".")[-1]
klass = getattr(module, class_name)
assert issubclass(klass, BaseReporter), f"{klass} is not a BaseReporter"
- return klass
+ return klass # type: ignore[no-any-return]
# Python Linter class #########################################################
@@ -299,18 +299,6 @@ class PyLinter(
self._dynamic_plugins: dict[str, ModuleType | ModuleNotFoundError | bool] = {}
"""Set of loaded plugin names."""
- # Attributes related to registering messages and their handling
- self.msgs_store = MessageDefinitionStore()
- self.msg_status = 0
- self._by_id_managed_msgs: list[ManagedMessage] = []
-
- # Attributes related to visiting files
- self.file_state = FileState("", self.msgs_store, is_base_filestate=True)
- self.current_name: str | None = None
- self.current_file: str | None = None
- self._ignore_file = False
- self._ignore_paths: list[Pattern[str]] = []
-
# Attributes related to stats
self.stats = LinterStats()
@@ -338,6 +326,19 @@ class PyLinter(
),
("RP0003", "Messages", report_messages_stats),
)
+
+ # Attributes related to registering messages and their handling
+ self.msgs_store = MessageDefinitionStore(self.config.py_version)
+ self.msg_status = 0
+ self._by_id_managed_msgs: list[ManagedMessage] = []
+
+ # Attributes related to visiting files
+ self.file_state = FileState("", self.msgs_store, is_base_filestate=True)
+ self.current_name: str | None = None
+ self.current_file: str | None = None
+ self._ignore_file = False
+ self._ignore_paths: list[Pattern[str]] = []
+
self.register_checker(self)
@property
@@ -346,6 +347,7 @@ class PyLinter(
warnings.warn(
"The option_groups attribute has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
return self._option_groups
@@ -354,6 +356,7 @@ class PyLinter(
warnings.warn(
"The option_groups attribute has been deprecated and will be removed in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
self._option_groups = value
@@ -400,7 +403,7 @@ class PyLinter(
"bad-plugin-value", args=(modname, module_or_error), line=0
)
elif hasattr(module_or_error, "load_configuration"):
- module_or_error.load_configuration(self) # type: ignore[union-attr]
+ module_or_error.load_configuration(self)
# 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.
@@ -486,6 +489,9 @@ class PyLinter(
self.register_report(r_id, r_title, r_cb, checker)
if hasattr(checker, "msgs"):
self.msgs_store.register_messages_from_checker(checker)
+ for message in checker.messages:
+ if not message.default_enabled:
+ self.disable(message.msgid)
# Register the checker, but disable all of its messages.
if not getattr(checker, "enabled", True):
self.disable(checker.name)
@@ -605,7 +611,7 @@ class PyLinter(
# initialize msgs_state now that all messages have been registered into
# the store
for msg in self.msgs_store.messages:
- if not msg.may_be_emitted():
+ if not msg.may_be_emitted(self.config.py_version):
self._msgs_state[msg.msgid] = False
def _discover_files(self, files_or_modules: Sequence[str]) -> Iterator[str]:
@@ -658,6 +664,7 @@ class PyLinter(
warnings.warn(
"In pylint 3.0, the checkers check function will only accept sequence of string",
DeprecationWarning,
+ stacklevel=2,
)
files_or_modules = (files_or_modules,) # type: ignore[assignment]
if self.config.recursive:
@@ -729,6 +736,7 @@ class PyLinter(
"In pylint 3.0, the checkers check_single_file function will be removed. "
"Use check_single_file_item instead.",
DeprecationWarning,
+ stacklevel=2,
)
self.check_single_file_item(FileItem(name, filepath, modname))
@@ -911,6 +919,7 @@ class PyLinter(
"If unknown it should be initialized as an empty string."
),
DeprecationWarning,
+ stacklevel=2,
)
self.current_name = modname
self.current_file = filepath or modname
@@ -947,9 +956,7 @@ class PyLinter(
walker = ASTWalker(self)
_checkers = self.prepare_checkers()
tokencheckers = [
- c
- for c in _checkers
- if isinstance(c, checkers.BaseTokenChecker) and c is not self
+ c for c in _checkers if isinstance(c, checkers.BaseTokenChecker)
]
# TODO: 3.0: Remove deprecated for-loop
for c in _checkers:
diff --git a/pylint/message/message.py b/pylint/message/message.py
index 11961d9af..23dd6c082 100644
--- a/pylint/message/message.py
+++ b/pylint/message/message.py
@@ -43,6 +43,7 @@ class Message: # pylint: disable=too-many-instance-attributes
warn(
"In pylint 3.0, Messages will only accept a MessageLocationTuple as location parameter",
DeprecationWarning,
+ stacklevel=2,
)
location = MessageLocationTuple(
location[0],
diff --git a/pylint/message/message_definition.py b/pylint/message/message_definition.py
index 3b403b008..25aa87d92 100644
--- a/pylint/message/message_definition.py
+++ b/pylint/message/message_definition.py
@@ -5,6 +5,7 @@
from __future__ import annotations
import sys
+import warnings
from typing import TYPE_CHECKING, Any
from astroid import nodes
@@ -31,6 +32,7 @@ class MessageDefinition:
maxversion: tuple[int, int] | None = None,
old_names: list[tuple[str, str]] | None = None,
shared: bool = False,
+ default_enabled: bool = True,
) -> None:
self.checker_name = checker.name
self.check_msgid(msgid)
@@ -42,6 +44,7 @@ class MessageDefinition:
self.minversion = minversion
self.maxversion = maxversion
self.shared = shared
+ self.default_enabled = default_enabled
self.old_names: list[tuple[str, str]] = []
if old_names:
for old_msgid, old_symbol in old_names:
@@ -70,11 +73,24 @@ class MessageDefinition:
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."""
- if self.minversion is not None and self.minversion > sys.version_info:
+ def may_be_emitted(
+ self,
+ py_version: tuple[int, ...] | sys._version_info | None = None,
+ ) -> bool:
+ """Return True if message may be emitted using the configured py_version."""
+ if py_version is None:
+ py_version = sys.version_info
+ warnings.warn(
+ "'py_version' will be a required parameter of "
+ "'MessageDefinition.may_be_emitted' in pylint 3.0. The most likely "
+ "solution is to use 'linter.config.py_version' if you need to keep "
+ "using this function, or to use 'MessageDefinition.is_message_enabled'"
+ " instead.",
+ DeprecationWarning,
+ )
+ if self.minversion is not None and self.minversion > py_version:
return False
- if self.maxversion is not None and self.maxversion <= sys.version_info:
+ if self.maxversion is not None and self.maxversion <= py_version:
return False
return True
diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py
index ef26d648d..7bbc70a51 100644
--- a/pylint/message/message_definition_store.py
+++ b/pylint/message/message_definition_store.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import collections
import functools
+import sys
from collections.abc import Sequence, ValuesView
from typing import TYPE_CHECKING
@@ -23,7 +24,9 @@ class MessageDefinitionStore:
has no particular state during analysis.
"""
- def __init__(self) -> None:
+ def __init__(
+ self, py_version: tuple[int, ...] | sys._version_info = sys.version_info
+ ) -> None:
self.message_id_store: MessageIdStore = MessageIdStore()
# Primary registry for all active messages definitions.
# It contains the 1:1 mapping from msgid to MessageDefinition.
@@ -31,6 +34,7 @@ class MessageDefinitionStore:
self._messages_definitions: dict[str, MessageDefinition] = {}
# MessageDefinition kept by category
self._msgs_by_category: dict[str, list[str]] = collections.defaultdict(list)
+ self.py_version = py_version
@property
def messages(self) -> ValuesView[MessageDefinition]:
@@ -52,10 +56,12 @@ class MessageDefinitionStore:
self._msgs_by_category[message.msgid[0]].append(message.msgid)
# Since MessageDefinitionStore is only initialized once
- # and the arguments are relatively small in size we do not run the
+ # and the arguments are relatively small we do not run the
# risk of creating a large memory leak.
# See discussion in: https://github.com/PyCQA/pylint/pull/5673
- @functools.lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none
+ @functools.lru_cache( # pylint: disable=method-cache-max-size-none # noqa: B019
+ maxsize=None
+ )
def get_message_definitions(self, msgid_or_symbol: str) -> list[MessageDefinition]:
"""Returns the Message definition for either a numeric or symbolic id.
@@ -108,7 +114,7 @@ class MessageDefinitionStore:
emittable = []
non_emittable = []
for message in messages:
- if message.may_be_emitted():
+ if message.may_be_emitted(self.py_version):
emittable.append(message)
else:
non_emittable.append(message)
diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py
index c6939e2a1..85b23052e 100644
--- a/pylint/pyreverse/diadefslib.py
+++ b/pylint/pyreverse/diadefslib.py
@@ -36,7 +36,7 @@ class DiaDefGenerator:
title = node.name
if self.module_names:
title = f"{node.root().name}.{title}"
- return title
+ return title # type: ignore[no-any-return]
def _set_option(self, option: bool | None) -> bool:
"""Activate some options if not explicitly deactivated."""
@@ -70,7 +70,7 @@ class DiaDefGenerator:
"""True if builtins and not show_builtins."""
if self.config.show_builtin:
return True
- return node.root().name != "builtins"
+ return node.root().name != "builtins" # type: ignore[no-any-return]
def add_class(self, node: nodes.ClassDef) -> None:
"""Visit one class and add it to diagram."""
diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py
index 7b15bf816..382d76bf7 100644
--- a/pylint/pyreverse/diagrams.py
+++ b/pylint/pyreverse/diagrams.py
@@ -143,7 +143,7 @@ class ClassDiagram(Figure, FilterMixIn):
and not decorated_with_property(m)
and self.show_attr(m.name)
]
- return sorted(methods, key=lambda n: n.name)
+ return sorted(methods, key=lambda n: n.name) # type: ignore[no-any-return]
def add_object(self, title: str, node: nodes.ClassDef) -> None:
"""Create a diagram object."""
@@ -214,20 +214,35 @@ class ClassDiagram(Figure, FilterMixIn):
self.add_relationship(obj, impl_obj, "implements")
except KeyError:
continue
- # associations link
- for name, values in list(node.instance_attrs_type.items()) + list(
+
+ # associations & aggregations links
+ for name, values in list(node.aggregations_type.items()):
+ for value in values:
+ self.assign_association_relationship(
+ value, obj, name, "aggregation"
+ )
+
+ for name, values in list(node.associations_type.items()) + list(
node.locals_type.items()
):
+
for value in values:
- if value is astroid.Uninferable:
- continue
- if isinstance(value, astroid.Instance):
- value = value._proxied
- try:
- associated_obj = self.object_from_node(value)
- self.add_relationship(associated_obj, obj, "association", name)
- except KeyError:
- continue
+ self.assign_association_relationship(
+ value, obj, name, "association"
+ )
+
+ def assign_association_relationship(
+ self, value: astroid.NodeNG, obj: ClassEntity, name: str, type_relationship: str
+ ) -> None:
+ if value is astroid.Uninferable:
+ return
+ if isinstance(value, astroid.Instance):
+ value = value._proxied
+ try:
+ associated_obj = self.object_from_node(value)
+ self.add_relationship(associated_obj, obj, type_relationship, name)
+ except KeyError:
+ return
class PackageDiagram(ClassDiagram):
diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py
index 883682704..1d5f2c32b 100644
--- a/pylint/pyreverse/dot_printer.py
+++ b/pylint/pyreverse/dot_printer.py
@@ -10,6 +10,7 @@ import os
import subprocess
import sys
import tempfile
+from enum import Enum
from pathlib import Path
from astroid import nodes
@@ -17,19 +18,34 @@ from astroid import nodes
from pylint.pyreverse.printer import EdgeType, Layout, NodeProperties, NodeType, Printer
from pylint.pyreverse.utils import get_annotation_label
+
+class HTMLLabels(Enum):
+ LINEBREAK_LEFT = '<br ALIGN="LEFT"/>'
+
+
ALLOWED_CHARSETS: frozenset[str] = frozenset(("utf-8", "iso-8859-1", "latin1"))
SHAPES: dict[NodeType, str] = {
NodeType.PACKAGE: "box",
NodeType.INTERFACE: "record",
NodeType.CLASS: "record",
}
+# pylint: disable-next=consider-using-namedtuple-or-dataclass
ARROWS: dict[EdgeType, dict[str, str]] = {
- EdgeType.INHERITS: dict(arrowtail="none", arrowhead="empty"),
- EdgeType.IMPLEMENTS: dict(arrowtail="node", arrowhead="empty", style="dashed"),
- EdgeType.ASSOCIATION: dict(
- fontcolor="green", arrowtail="none", arrowhead="diamond", style="solid"
- ),
- EdgeType.USES: dict(arrowtail="none", arrowhead="open"),
+ EdgeType.INHERITS: {"arrowtail": "none", "arrowhead": "empty"},
+ EdgeType.IMPLEMENTS: {"arrowtail": "node", "arrowhead": "empty", "style": "dashed"},
+ EdgeType.ASSOCIATION: {
+ "fontcolor": "green",
+ "arrowtail": "none",
+ "arrowhead": "diamond",
+ "style": "solid",
+ },
+ EdgeType.AGGREGATION: {
+ "fontcolor": "green",
+ "arrowtail": "none",
+ "arrowhead": "odiamond",
+ "style": "solid",
+ },
+ EdgeType.USES: {"arrowtail": "none", "arrowhead": "open"},
}
@@ -73,7 +89,7 @@ class DotPrinter(Printer):
color = properties.color if properties.color is not None else self.DEFAULT_COLOR
style = "filled" if color != self.DEFAULT_COLOR else "solid"
label = self._build_label_for_node(properties)
- label_part = f', label="{label}"' if label else ""
+ label_part = f", label=<{label}>" if label else ""
fontcolor_part = (
f', fontcolor="{properties.fontcolor}"' if properties.fontcolor else ""
)
@@ -92,17 +108,22 @@ class DotPrinter(Printer):
# Add class attributes
attrs: list[str] = properties.attrs or []
- attrs_string = r"\l".join(attr.replace("|", r"\|") for attr in attrs)
- label = rf"{{{label}|{attrs_string}\l|"
+ attrs_string = rf"{HTMLLabels.LINEBREAK_LEFT.value}".join(
+ attr.replace("|", r"\|") for attr in attrs
+ )
+ label = rf"{{{label}|{attrs_string}{HTMLLabels.LINEBREAK_LEFT.value}|"
# Add class methods
methods: list[nodes.FunctionDef] = properties.methods or []
for func in methods:
args = self._get_method_arguments(func)
- label += rf"{func.name}({', '.join(args)})"
+ method_name = (
+ f"<I>{func.name}</I>" if func.is_abstract() else f"{func.name}"
+ )
+ label += rf"{method_name}({', '.join(args)})"
if func.returns:
label += ": " + get_annotation_label(func.returns)
- label += r"\l"
+ label += rf"{HTMLLabels.LINEBREAK_LEFT.value}"
label += "}"
return label
diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py
index 042d3845e..8c403ffc6 100644
--- a/pylint/pyreverse/inspector.py
+++ b/pylint/pyreverse/inspector.py
@@ -13,6 +13,7 @@ import collections
import os
import traceback
import warnings
+from abc import ABC, abstractmethod
from collections.abc import Generator
from typing import Any, Callable, Optional
@@ -123,6 +124,12 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor):
* instance_attrs_type
as locals_type but for klass member attributes (only on astroid.Class)
+ * associations_type
+ as instance_attrs_type but for association relationships
+
+ * aggregations_type
+ as instance_attrs_type but for aggregations relationships
+
* implements,
list of implemented interface _objects_ (only on astroid.Class nodes)
"""
@@ -134,6 +141,8 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor):
self.tag = tag
# visited project
self.project = project
+ self.associations_handler = AggregationsHandler()
+ self.associations_handler.set_next(OtherAssociationsHandler())
def visit_project(self, node: Project) -> None:
"""Visit a pyreverse.utils.Project node.
@@ -178,9 +187,12 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor):
baseobj.specializations = specializations
# resolve instance attributes
node.instance_attrs_type = collections.defaultdict(list)
+ node.aggregations_type = collections.defaultdict(list)
+ node.associations_type = collections.defaultdict(list)
for assignattrs in tuple(node.instance_attrs.values()):
for assignattr in assignattrs:
if not isinstance(assignattr, nodes.Unknown):
+ self.associations_handler.handle(assignattr, node)
self.handle_assignattr_type(assignattr, node)
# resolve implemented interface
try:
@@ -313,6 +325,61 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor):
mod_paths.append(mod_path)
+class AssociationHandlerInterface(ABC):
+ @abstractmethod
+ def set_next(
+ self, handler: AssociationHandlerInterface
+ ) -> AssociationHandlerInterface:
+ pass
+
+ @abstractmethod
+ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
+ pass
+
+
+class AbstractAssociationHandler(AssociationHandlerInterface):
+ """
+ Chain of Responsibility for handling types of association, useful
+ to expand in the future if we want to add more distinct associations.
+
+ Every link of the chain checks if it's a certain type of association.
+ If no association is found it's set as a generic association in `associations_type`.
+
+ The default chaining behavior is implemented inside the base handler
+ class.
+ """
+
+ _next_handler: AssociationHandlerInterface
+
+ def set_next(
+ self, handler: AssociationHandlerInterface
+ ) -> AssociationHandlerInterface:
+ self._next_handler = handler
+ return handler
+
+ @abstractmethod
+ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
+ if self._next_handler:
+ self._next_handler.handle(node, parent)
+
+
+class AggregationsHandler(AbstractAssociationHandler):
+ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
+ if isinstance(node.parent.value, astroid.node_classes.Name):
+ current = set(parent.aggregations_type[node.attrname])
+ parent.aggregations_type[node.attrname] = list(
+ current | utils.infer_node(node)
+ )
+ else:
+ super().handle(node, parent)
+
+
+class OtherAssociationsHandler(AbstractAssociationHandler):
+ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
+ current = set(parent.associations_type[node.attrname])
+ parent.associations_type[node.attrname] = list(current | utils.infer_node(node))
+
+
def project_from_files(
files: list[str],
func_wrapper: _WrapperFuncT = _astroid_wrapper,
diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py
index 043e2a3f3..72429b41a 100644
--- a/pylint/pyreverse/main.py
+++ b/pylint/pyreverse/main.py
@@ -36,14 +36,14 @@ DIRECTLY_SUPPORTED_FORMATS = (
OPTIONS: Options = (
(
"filter-mode",
- dict(
- short="f",
- default="PUB_ONLY",
- dest="mode",
- type="string",
- action="store",
- metavar="<mode>",
- help="""filter attributes and functions according to
+ {
+ "short": "f",
+ "default": "PUB_ONLY",
+ "dest": "mode",
+ "type": "string",
+ "action": "store",
+ "metavar": "<mode>",
+ "help": """filter attributes and functions according to
<mode>. Correct modes are :
'PUB_ONLY' filter all non public attributes
[DEFAULT], equivalent to PRIVATE+SPECIAL_A
@@ -52,154 +52,154 @@ OPTIONS: Options = (
except constructor
'OTHER' filter protected and private
attributes""",
- ),
+ },
),
(
"class",
- dict(
- short="c",
- action="extend",
- metavar="<class>",
- type="csv",
- dest="classes",
- default=None,
- help="create a class diagram with all classes related to <class>;\
+ {
+ "short": "c",
+ "action": "extend",
+ "metavar": "<class>",
+ "type": "csv",
+ "dest": "classes",
+ "default": None,
+ "help": "create a class diagram with all classes related to <class>;\
this uses by default the options -ASmy",
- ),
+ },
),
(
"show-ancestors",
- dict(
- short="a",
- action="store",
- metavar="<ancestor>",
- type="int",
- default=None,
- help="show <ancestor> generations of ancestor classes not in <projects>",
- ),
+ {
+ "short": "a",
+ "action": "store",
+ "metavar": "<ancestor>",
+ "type": "int",
+ "default": None,
+ "help": "show <ancestor> generations of ancestor classes not in <projects>",
+ },
),
(
"all-ancestors",
- dict(
- short="A",
- default=None,
- action="store_true",
- help="show all ancestors off all classes in <projects>",
- ),
+ {
+ "short": "A",
+ "default": None,
+ "action": "store_true",
+ "help": "show all ancestors off all classes in <projects>",
+ },
),
(
"show-associated",
- dict(
- short="s",
- action="store",
- metavar="<association_level>",
- type="int",
- default=None,
- help="show <association_level> levels of associated classes not in <projects>",
- ),
+ {
+ "short": "s",
+ "action": "store",
+ "metavar": "<association_level>",
+ "type": "int",
+ "default": None,
+ "help": "show <association_level> levels of associated classes not in <projects>",
+ },
),
(
"all-associated",
- dict(
- short="S",
- default=None,
- action="store_true",
- help="show recursively all associated off all associated classes",
- ),
+ {
+ "short": "S",
+ "default": None,
+ "action": "store_true",
+ "help": "show recursively all associated off all associated classes",
+ },
),
(
"show-builtin",
- dict(
- short="b",
- action="store_true",
- default=False,
- help="include builtin objects in representation of classes",
- ),
+ {
+ "short": "b",
+ "action": "store_true",
+ "default": False,
+ "help": "include builtin objects in representation of classes",
+ },
),
(
"module-names",
- dict(
- short="m",
- default=None,
- type="yn",
- metavar="<y or n>",
- help="include module name in representation of classes",
- ),
+ {
+ "short": "m",
+ "default": None,
+ "type": "yn",
+ "metavar": "<y or n>",
+ "help": "include module name in representation of classes",
+ },
),
(
"only-classnames",
- dict(
- short="k",
- action="store_true",
- default=False,
- help="don't show attributes and methods in the class boxes; this disables -f values",
- ),
+ {
+ "short": "k",
+ "action": "store_true",
+ "default": False,
+ "help": "don't show attributes and methods in the class boxes; this disables -f values",
+ },
),
(
"output",
- dict(
- short="o",
- dest="output_format",
- action="store",
- default="dot",
- metavar="<format>",
- type="string",
- help=(
+ {
+ "short": "o",
+ "dest": "output_format",
+ "action": "store",
+ "default": "dot",
+ "metavar": "<format>",
+ "type": "string",
+ "help": (
f"create a *.<format> 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."
),
- ),
+ },
),
(
"colorized",
- dict(
- dest="colorized",
- action="store_true",
- default=False,
- help="Use colored output. Classes/modules of the same package get the same color.",
- ),
+ {
+ "dest": "colorized",
+ "action": "store_true",
+ "default": False,
+ "help": "Use colored output. Classes/modules of the same package get the same color.",
+ },
),
(
"max-color-depth",
- dict(
- dest="max_color_depth",
- action="store",
- default=2,
- metavar="<depth>",
- type="int",
- help="Use separate colors up to package depth of <depth>",
- ),
+ {
+ "dest": "max_color_depth",
+ "action": "store",
+ "default": 2,
+ "metavar": "<depth>",
+ "type": "int",
+ "help": "Use separate colors up to package depth of <depth>",
+ },
),
(
"ignore",
- dict(
- type="csv",
- metavar="<file[,file...]>",
- dest="ignore_list",
- default=constants.DEFAULT_IGNORE_LIST,
- help="Files or directories to be skipped. They should be base names, not paths.",
- ),
+ {
+ "type": "csv",
+ "metavar": "<file[,file...]>",
+ "dest": "ignore_list",
+ "default": constants.DEFAULT_IGNORE_LIST,
+ "help": "Files or directories to be skipped. They should be base names, not paths.",
+ },
),
(
"project",
- dict(
- default="",
- type="string",
- short="p",
- metavar="<project name>",
- help="set the project name.",
- ),
+ {
+ "default": "",
+ "type": "string",
+ "short": "p",
+ "metavar": "<project name>",
+ "help": "set the project name.",
+ },
),
(
"output-directory",
- dict(
- default="",
- type="path",
- short="d",
- action="store",
- metavar="<output_directory>",
- help="set the output directory path.",
- ),
+ {
+ "default": "",
+ "type": "path",
+ "short": "d",
+ "action": "store",
+ "metavar": "<output_directory>",
+ "help": "set the output directory path.",
+ },
),
)
@@ -210,8 +210,7 @@ class Run(_ArgumentsManager, _ArgumentsProvider):
options = OPTIONS
name = "pyreverse"
- # For mypy issue, see https://github.com/python/mypy/issues/10342
- def __init__(self, args: Sequence[str]) -> NoReturn: # type: ignore[misc]
+ def __init__(self, args: Sequence[str]) -> NoReturn:
_ArgumentsManager.__init__(self, prog="pyreverse", description=__doc__)
_ArgumentsProvider.__init__(self, self)
diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py
index 9a2309a74..a8f3c576b 100644
--- a/pylint/pyreverse/mermaidjs_printer.py
+++ b/pylint/pyreverse/mermaidjs_printer.py
@@ -24,6 +24,7 @@ class MermaidJSPrinter(Printer):
EdgeType.INHERITS: "--|>",
EdgeType.IMPLEMENTS: "..|>",
EdgeType.ASSOCIATION: "--*",
+ EdgeType.AGGREGATION: "--o",
EdgeType.USES: "-->",
}
@@ -54,6 +55,7 @@ class MermaidJSPrinter(Printer):
for func in properties.methods:
args = self._get_method_arguments(func)
line = f"{func.name}({', '.join(args)})"
+ line += "*" if func.is_abstract() else ""
if func.returns:
line += f" {get_annotation_label(func.returns)}"
body.append(line)
diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py
index 45106152d..56463165d 100644
--- a/pylint/pyreverse/plantuml_printer.py
+++ b/pylint/pyreverse/plantuml_printer.py
@@ -24,6 +24,7 @@ class PlantUmlPrinter(Printer):
EdgeType.INHERITS: "--|>",
EdgeType.IMPLEMENTS: "..|>",
EdgeType.ASSOCIATION: "--*",
+ EdgeType.AGGREGATION: "--o",
EdgeType.USES: "-->",
}
@@ -66,7 +67,8 @@ class PlantUmlPrinter(Printer):
if properties.methods:
for func in properties.methods:
args = self._get_method_arguments(func)
- line = f"{func.name}({', '.join(args)})"
+ line = "{abstract}" if func.is_abstract() else ""
+ line += f"{func.name}({', '.join(args)})"
if func.returns:
line += " -> " + get_annotation_label(func.returns)
body.append(line)
diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py
index 55ce2c8b1..cdbf7e3c8 100644
--- a/pylint/pyreverse/printer.py
+++ b/pylint/pyreverse/printer.py
@@ -25,6 +25,7 @@ class EdgeType(Enum):
INHERITS = "inherits"
IMPLEMENTS = "implements"
ASSOCIATION = "association"
+ AGGREGATION = "aggregation"
USES = "uses"
diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py
index a1de38685..078bc1b7e 100644
--- a/pylint/pyreverse/utils.py
+++ b/pylint/pyreverse/utils.py
@@ -74,12 +74,12 @@ def get_visibility(name: str) -> str:
def is_interface(node: nodes.ClassDef) -> bool:
# bw compatibility
- return node.type == "interface"
+ return node.type == "interface" # type: ignore[no-any-return]
def is_exception(node: nodes.ClassDef) -> bool:
# bw compatibility
- return node.type == "exception"
+ return node.type == "exception" # type: ignore[no-any-return]
# Helpers #####################################################################
@@ -170,9 +170,9 @@ class LocalsVisitor:
def get_annotation_label(ann: nodes.Name | nodes.NodeNG) -> str:
if isinstance(ann, nodes.Name) and ann.name is not None:
- return ann.name
+ return ann.name # type: ignore[no-any-return]
if isinstance(ann, nodes.NodeNG):
- return ann.as_string()
+ return ann.as_string() # type: ignore[no-any-return]
return ""
diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py
index ec7152baa..b9e2e94f3 100644
--- a/pylint/pyreverse/vcg_printer.py
+++ b/pylint/pyreverse/vcg_printer.py
@@ -154,20 +154,34 @@ SHAPES: dict[NodeType, str] = {
NodeType.CLASS: "box",
NodeType.INTERFACE: "ellipse",
}
-ARROWS: dict[EdgeType, dict] = {
- EdgeType.USES: dict(arrowstyle="solid", backarrowstyle="none", backarrowsize=0),
- EdgeType.INHERITS: dict(
- arrowstyle="solid", backarrowstyle="none", backarrowsize=10
- ),
- EdgeType.IMPLEMENTS: dict(
- arrowstyle="solid",
- backarrowstyle="none",
- linestyle="dotted",
- backarrowsize=10,
- ),
- EdgeType.ASSOCIATION: dict(
- arrowstyle="solid", backarrowstyle="none", textcolor="green"
- ),
+# pylint: disable-next=consider-using-namedtuple-or-dataclass
+ARROWS: dict[EdgeType, dict[str, str | int]] = {
+ EdgeType.USES: {
+ "arrowstyle": "solid",
+ "backarrowstyle": "none",
+ "backarrowsize": 0,
+ },
+ EdgeType.INHERITS: {
+ "arrowstyle": "solid",
+ "backarrowstyle": "none",
+ "backarrowsize": 10,
+ },
+ EdgeType.IMPLEMENTS: {
+ "arrowstyle": "solid",
+ "backarrowstyle": "none",
+ "linestyle": "dotted",
+ "backarrowsize": 10,
+ },
+ EdgeType.ASSOCIATION: {
+ "arrowstyle": "solid",
+ "backarrowstyle": "none",
+ "textcolor": "green",
+ },
+ EdgeType.AGGREGATION: {
+ "arrowstyle": "solid",
+ "backarrowstyle": "none",
+ "textcolor": "green",
+ },
}
ORIENTATION: dict[Layout, str] = {
Layout.LEFT_TO_RIGHT: "left_to_right",
@@ -265,13 +279,15 @@ class VCGPrinter(Printer):
)
self.emit("}")
- def _write_attributes(self, attributes_dict: Mapping[str, Any], **args) -> None:
+ def _write_attributes(
+ self, attributes_dict: Mapping[str, Any], **args: Any
+ ) -> None:
"""Write graph, node or edge attributes."""
for key, value in args.items():
try:
_type = attributes_dict[key]
except KeyError as e:
- raise Exception(
+ raise AttributeError(
f"no such attribute {key}\npossible attributes are {attributes_dict.keys()}"
) from e
@@ -282,6 +298,6 @@ class VCGPrinter(Printer):
elif value in _type:
self.emit(f"{key}:{value}\n")
else:
- raise Exception(
+ raise ValueError(
f"value {value} isn't correct for attribute {key} correct values are {type}"
)
diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py
index 12a76df9b..68a49eea1 100644
--- a/pylint/pyreverse/writer.py
+++ b/pylint/pyreverse/writer.py
@@ -92,7 +92,7 @@ class DiagramWriter:
def write_classes(self, diagram: ClassDiagram) -> None:
"""Write a class diagram."""
# sorted to get predictable (hence testable) results
- for obj in sorted(diagram.objects, key=lambda x: x.title):
+ for obj in sorted(diagram.objects, key=lambda x: x.title): # type: ignore[no-any-return]
obj.fig_id = obj.node.qname()
type_ = NodeType.INTERFACE if obj.shape == "interface" else NodeType.CLASS
self.printer.emit_node(
@@ -120,6 +120,14 @@ class DiagramWriter:
label=rel.name,
type_=EdgeType.ASSOCIATION,
)
+ # generate aggregations
+ for rel in diagram.get_relationships("aggregation"):
+ self.printer.emit_edge(
+ rel.from_object.fig_id,
+ rel.to_object.fig_id,
+ label=rel.name,
+ type_=EdgeType.AGGREGATION,
+ )
def set_printer(self, file_name: str, basename: str) -> None:
"""Set printer."""
diff --git a/pylint/reporters/base_reporter.py b/pylint/reporters/base_reporter.py
index 0b4507e5c..3df970d80 100644
--- a/pylint/reporters/base_reporter.py
+++ b/pylint/reporters/base_reporter.py
@@ -36,6 +36,7 @@ class BaseReporter:
"Using the __implements__ inheritance pattern for BaseReporter is no "
"longer supported. Child classes should only inherit BaseReporter",
DeprecationWarning,
+ stacklevel=2,
)
self.linter: PyLinter
self.section = 0
@@ -54,6 +55,7 @@ class BaseReporter:
warn(
"'set_output' will be removed in 3.0, please use 'reporter.out = stream' instead",
DeprecationWarning,
+ stacklevel=2,
)
self.out = output or sys.stdout
diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py
index 29bd46798..546b33378 100644
--- a/pylint/reporters/text.py
+++ b/pylint/reporters/text.py
@@ -135,6 +135,7 @@ def colorize_ansi(
warnings.warn(
"In pylint 3.0, the colorize_ansi function of Text reporters will only accept a MessageStyle parameter",
DeprecationWarning,
+ stacklevel=2,
)
color = kwargs.get("color")
style_attrs = tuple(_splitstrip(style))
@@ -225,6 +226,7 @@ class ParseableTextReporter(TextReporter):
warnings.warn(
f"{self.name} output format is deprecated. This is equivalent to --msg-template={self.line_format}",
DeprecationWarning,
+ stacklevel=2,
)
super().__init__(output)
@@ -265,6 +267,7 @@ class ColorizedTextReporter(TextReporter):
warnings.warn(
"In pylint 3.0, the ColorizedTextReporter will only accept ColorMappingDict as color_mapping parameter",
DeprecationWarning,
+ stacklevel=2,
)
temp_color_mapping: ColorMappingDict = {}
for key, value in color_mapping.items():
diff --git a/pylint/testutils/_primer/package_to_lint.py b/pylint/testutils/_primer/package_to_lint.py
index b6ccc8b8b..09ecb4456 100644
--- a/pylint/testutils/_primer/package_to_lint.py
+++ b/pylint/testutils/_primer/package_to_lint.py
@@ -5,11 +5,17 @@
from __future__ import annotations
import logging
+import sys
from pathlib import Path
from git.cmd import Git
from git.repo import Repo
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from typing_extensions import Literal
+
PRIMER_DIRECTORY_PATH = Path("tests") / ".pylint_primer_tests"
@@ -34,6 +40,9 @@ class PackageToLint:
pylintrc_relpath: str | None
"""Path relative to project's main directory to the pylintrc if it exists."""
+ minimum_python: str | None
+ """Minimum python version supported by the package."""
+
def __init__(
self,
url: str,
@@ -42,6 +51,7 @@ class PackageToLint:
commit: str | None = None,
pylint_additional_args: list[str] | None = None,
pylintrc_relpath: str | None = None,
+ minimum_python: str | None = None,
) -> None:
self.url = url
self.branch = branch
@@ -49,11 +59,13 @@ class PackageToLint:
self.commit = commit
self.pylint_additional_args = pylint_additional_args or []
self.pylintrc_relpath = pylintrc_relpath
+ self.minimum_python = minimum_python
@property
- def pylintrc(self) -> Path | None:
+ def pylintrc(self) -> Path | Literal[""]:
if self.pylintrc_relpath is None:
- return None
+ # Fall back to "" to ensure pylint's own pylintrc is not discovered
+ return ""
return self.clone_directory / self.pylintrc_relpath
@property
@@ -70,9 +82,8 @@ class PackageToLint:
@property
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 exist
- options += [f"--rcfile={self.pylintrc}"]
+ # 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
def lazy_clone(self) -> str: # pragma: no cover
diff --git a/pylint/testutils/_primer/primer.py b/pylint/testutils/_primer/primer.py
index 417bb1988..7d08f1df5 100644
--- a/pylint/testutils/_primer/primer.py
+++ b/pylint/testutils/_primer/primer.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import argparse
import json
+import sys
from pathlib import Path
from pylint.testutils._primer import PackageToLint
@@ -92,9 +93,18 @@ class Primer:
self.command.run()
@staticmethod
+ def _minimum_python_supported(package_data: dict[str, str]) -> bool:
+ min_python_str = package_data.get("minimum_python", None)
+ if not min_python_str:
+ return True
+ min_python_tuple = tuple(int(n) for n in min_python_str.split("."))
+ return min_python_tuple <= sys.version_info[:2]
+
+ @staticmethod
def _get_packages_to_lint_from_json(json_path: Path) -> dict[str, PackageToLint]:
with open(json_path, encoding="utf8") as f:
return {
name: PackageToLint(**package_data)
for name, package_data in json.load(f).items()
+ if Primer._minimum_python_supported(package_data)
}
diff --git a/pylint/testutils/_primer/primer_compare_command.py b/pylint/testutils/_primer/primer_compare_command.py
index baf28d8b7..442ffa227 100644
--- a/pylint/testutils/_primer/primer_compare_command.py
+++ b/pylint/testutils/_primer/primer_compare_command.py
@@ -63,15 +63,14 @@ class CompareCommand(PrimerCommand):
comment += self._create_comment_for_package(
package, new_messages, missing_messages
)
- if comment == "":
- comment = (
+ comment = (
+ f"🤖 **Effect of this PR on checked open source code:** 🤖\n\n{comment}"
+ if comment
+ else (
"🤖 According to the primer, this change has **no effect** on the"
" checked open source code. 🤖🎉\n\n"
)
- else:
- comment = (
- f"🤖 **Effect of this PR on checked open source code:** 🤖\n\n{comment}"
- )
+ )
return self._truncate_comment(comment)
def _create_comment_for_package(
diff --git a/pylint/testutils/_primer/primer_prepare_command.py b/pylint/testutils/_primer/primer_prepare_command.py
index 83b3a2b96..e69e55b95 100644
--- a/pylint/testutils/_primer/primer_prepare_command.py
+++ b/pylint/testutils/_primer/primer_prepare_command.py
@@ -3,6 +3,8 @@
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
from __future__ import annotations
+import sys
+
from git.cmd import Git
from git.repo import Repo
@@ -12,6 +14,7 @@ from pylint.testutils._primer.primer_command import PrimerCommand
class PrepareCommand(PrimerCommand):
def run(self) -> None:
commit_string = ""
+ version_string = ".".join(str(x) for x in sys.version_info[:2])
if self.config.clone:
for package, data in self.packages.items():
local_commit = data.lazy_clone()
@@ -31,12 +34,14 @@ class PrepareCommand(PrimerCommand):
commit_string += remote_sha1_commit + "_"
elif self.config.read_commit_string:
with open(
- self.primer_directory / "commit_string.txt", encoding="utf-8"
+ self.primer_directory / f"commit_string_{version_string}.txt",
+ encoding="utf-8",
) as f:
print(f.read())
-
if commit_string:
with open(
- self.primer_directory / "commit_string.txt", "w", encoding="utf-8"
+ self.primer_directory / f"commit_string_{version_string}.txt",
+ "w",
+ encoding="utf-8",
) as f:
f.write(commit_string)
diff --git a/pylint/testutils/_primer/primer_run_command.py b/pylint/testutils/_primer/primer_run_command.py
index d2fce7793..cd17d6b1d 100644
--- a/pylint/testutils/_primer/primer_run_command.py
+++ b/pylint/testutils/_primer/primer_run_command.py
@@ -85,7 +85,7 @@ class RunCommand(PrimerCommand):
try:
Run(arguments, reporter=reporter)
except SystemExit as e:
- pylint_exit_code = int(e.code)
+ pylint_exit_code = int(e.code) # type: ignore[arg-type]
readable_messages: str = output.getvalue()
messages: list[OldJsonExport] = json.loads(readable_messages)
fatal_msgs: list[Message] = []
diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py
index 0c24648b3..291f52002 100644
--- a/pylint/testutils/checker_test_case.py
+++ b/pylint/testutils/checker_test_case.py
@@ -85,6 +85,7 @@ class CheckerTestCase:
f"the expected value in {expected_msg}. In pylint 3.0 correct end_line "
"attributes will be required for MessageTest.",
DeprecationWarning,
+ stacklevel=2,
)
if not expected_msg.end_col_offset == gotten_msg.end_col_offset:
warnings.warn( # pragma: no cover
@@ -92,6 +93,7 @@ class CheckerTestCase:
f"the expected value in {expected_msg}. In pylint 3.0 correct end_col_offset "
"attributes will be required for MessageTest.",
DeprecationWarning,
+ stacklevel=2,
)
def walk(self, node: nodes.NodeNG) -> None:
diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py
index 7b86ee642..200cee7ec 100644
--- a/pylint/testutils/functional/find_functional_tests.py
+++ b/pylint/testutils/functional/find_functional_tests.py
@@ -38,9 +38,11 @@ def get_functional_test_files_from_directory(
_check_functional_tests_structure(Path(input_dir))
- for dirpath, _, filenames in os.walk(input_dir):
+ for dirpath, dirnames, filenames in os.walk(input_dir):
if dirpath.endswith("__pycache__"):
continue
+ dirnames.sort()
+ filenames.sort()
for filename in filenames:
if filename != "__init__.py" and filename.endswith(".py"):
suite.append(FunctionalTestFile(dirpath, filename))
diff --git a/pylint/testutils/functional_test_file.py b/pylint/testutils/functional_test_file.py
index fc1cdcbb1..e2bd7f59b 100644
--- a/pylint/testutils/functional_test_file.py
+++ b/pylint/testutils/functional_test_file.py
@@ -20,4 +20,5 @@ warnings.warn(
"'pylint.testutils.functional_test_file' will be accessible from"
" the 'pylint.testutils.functional' namespace in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py
index 0d3dbb0bf..d05f7e481 100644
--- a/pylint/testutils/lint_module_test.py
+++ b/pylint/testutils/lint_module_test.py
@@ -145,7 +145,7 @@ class LintModuleTest:
self._runTest()
def _should_be_skipped_due_to_version(self) -> bool:
- return (
+ return ( # type: ignore[no-any-return]
sys.version_info < self._linter.config.min_pyver
or sys.version_info > self._linter.config.max_pyver
)
diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py
index 6a2a2325f..a2c417621 100644
--- a/pylint/testutils/output_line.py
+++ b/pylint/testutils/output_line.py
@@ -98,6 +98,7 @@ class OutputLine(NamedTuple):
"expected confidence level, expected end_line and expected end_column. "
"An OutputLine should thus have a length of 8.",
DeprecationWarning,
+ stacklevel=2,
)
return cls(
row[0],
@@ -115,6 +116,7 @@ class OutputLine(NamedTuple):
"expected end_line and expected end_column. An OutputLine should thus have "
"a length of 8.",
DeprecationWarning,
+ stacklevel=2,
)
return cls(
row[0], int(row[1]), column, None, None, row[3], row[4], row[5]
diff --git a/pylint/testutils/utils.py b/pylint/testutils/utils.py
index 4d5b82867..292e991c2 100644
--- a/pylint/testutils/utils.py
+++ b/pylint/testutils/utils.py
@@ -93,7 +93,7 @@ def create_files(paths: list[str], chroot: str = ".") -> None:
path = os.path.join(chroot, path)
filename = os.path.basename(path)
# path is a directory path
- if filename == "":
+ if not filename:
dirs.add(path)
# path is a filename path
else:
diff --git a/pylint/typing.py b/pylint/typing.py
index 224e0bd6b..d62618605 100644
--- a/pylint/typing.py
+++ b/pylint/typing.py
@@ -24,12 +24,13 @@ from typing import (
)
if sys.version_info >= (3, 8):
- from typing import Literal, TypedDict
+ from typing import Literal, Protocol, TypedDict
else:
- from typing_extensions import Literal, TypedDict
+ from typing_extensions import Literal, Protocol, TypedDict
if TYPE_CHECKING:
from pylint.config.callback_actions import _CallbackAction
+ from pylint.pyreverse.inspector import Project
from pylint.reporters.ureports.nodes import Section
from pylint.utils import LinterStats
@@ -124,11 +125,16 @@ class ExtraMessageOptions(TypedDict, total=False):
maxversion: tuple[int, int]
minversion: tuple[int, int]
shared: bool
+ default_enabled: bool
MessageDefinitionTuple = Union[
Tuple[str, str, str],
Tuple[str, str, str, ExtraMessageOptions],
]
-# Mypy doesn't support recursive types (yet), see https://github.com/python/mypy/issues/731
-DirectoryNamespaceDict = Dict[Path, Tuple[argparse.Namespace, "DirectoryNamespaceDict"]] # type: ignore[misc]
+DirectoryNamespaceDict = Dict[Path, Tuple[argparse.Namespace, "DirectoryNamespaceDict"]]
+
+
+class GetProjectCallable(Protocol):
+ def __call__(self, module: str, name: str | None = "No Name") -> Project:
+ ... # pragma: no cover
diff --git a/pylint/utils/ast_walker.py b/pylint/utils/ast_walker.py
index cc387d860..4d552d995 100644
--- a/pylint/utils/ast_walker.py
+++ b/pylint/utils/ast_walker.py
@@ -37,7 +37,7 @@ class ASTWalker:
def _is_method_enabled(self, method: AstCallback) -> bool:
if not hasattr(method, "checks_msgs"):
return True
- return any(self.linter.is_message_enabled(m) for m in method.checks_msgs) # type: ignore[attr-defined]
+ return any(self.linter.is_message_enabled(m) for m in method.checks_msgs)
def add_checker(self, checker: BaseChecker) -> None:
"""Walk to the checker's dir and collect visit and leave methods."""
diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py
index 9624174ad..19122b373 100644
--- a/pylint/utils/file_state.py
+++ b/pylint/utils/file_state.py
@@ -47,12 +47,14 @@ class FileState:
"FileState needs a string as modname argument. "
"This argument will be required in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
if msg_store is None:
warnings.warn(
"FileState needs a 'MessageDefinitionStore' as msg_store argument. "
"This argument will be required in pylint 3.0",
DeprecationWarning,
+ stacklevel=2,
)
self.base_name = modname
self._module_msgs_state: MessageStateDict = {}
@@ -79,6 +81,7 @@ class FileState:
warnings.warn(
"'collect_block_lines' has been deprecated and will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
for msg, lines in self._module_msgs_state.items():
self._raw_module_msgs_state[msg] = lines.copy()
@@ -292,4 +295,4 @@ class FileState:
)
def get_effective_max_line_number(self) -> int | None:
- return self._effective_max_line_number
+ return self._effective_max_line_number # type: ignore[no-any-return]
diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py
index 8e34fa693..df3627380 100644
--- a/pylint/utils/pragma_parser.py
+++ b/pylint/utils/pragma_parser.py
@@ -5,8 +5,8 @@
from __future__ import annotations
import re
-from collections import namedtuple
from collections.abc import Generator
+from typing import NamedTuple
# Allow stopping after the first semicolon/hash encountered,
# so that an option can be continued with the reasons
@@ -27,7 +27,9 @@ OPTION_RGX = r"""
OPTION_PO = re.compile(OPTION_RGX, re.VERBOSE)
-PragmaRepresenter = namedtuple("PragmaRepresenter", "action messages")
+class PragmaRepresenter(NamedTuple):
+ action: str
+ messages: list[str]
ATOMIC_KEYWORDS = frozenset(("disable-all", "skip-file"))
diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py
index 6a4277642..054d307bc 100644
--- a/pylint/utils/utils.py
+++ b/pylint/utils/utils.py
@@ -6,6 +6,7 @@ from __future__ import annotations
try:
import isort.api
+ import isort.settings
HAS_ISORT_5 = True
except ImportError: # isort < 5
@@ -280,6 +281,7 @@ def get_global_option(
"get_global_option has been deprecated. You can use "
"checker.linter.config to get all global options instead.",
DeprecationWarning,
+ stacklevel=2,
)
return getattr(checker.linter.config, option.replace("-", "_"))
@@ -366,6 +368,7 @@ def format_section(
warnings.warn(
"format_section has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
if doc:
print(_comment(doc), file=stream)
@@ -380,6 +383,7 @@ def _ini_format(stream: TextIO, options: list[tuple[str, OptionDict, Any]]) -> N
warnings.warn(
"_ini_format has been deprecated. It will be removed in pylint 3.0.",
DeprecationWarning,
+ stacklevel=2,
)
for optname, optdict, value in options:
# Skip deprecated option
@@ -413,7 +417,7 @@ class IsortDriver:
def __init__(self, config: argparse.Namespace) -> None:
if HAS_ISORT_5:
- self.isort5_config = isort.api.Config(
+ self.isort5_config = isort.settings.Config(
# 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
@@ -423,7 +427,7 @@ class IsortDriver:
)
else:
# pylint: disable-next=no-member
- self.isort4_obj = isort.SortImports(
+ self.isort4_obj = isort.SortImports( # type: ignore[attr-defined]
file_contents="",
known_standard_library=config.known_standard_library,
known_third_party=config.known_third_party,
@@ -432,4 +436,4 @@ class IsortDriver:
def place_module(self, package: str) -> str:
if HAS_ISORT_5:
return isort.api.place_module(package, self.isort5_config)
- return self.isort4_obj.place_module(package)
+ return self.isort4_obj.place_module(package) # type: ignore[no-any-return]
diff --git a/pylintrc b/pylintrc
index 3323052d4..a91481ef7 100644
--- a/pylintrc
+++ b/pylintrc
@@ -32,6 +32,8 @@ load-plugins=
pylint.extensions.typing,
pylint.extensions.redefined_variable_type,
pylint.extensions.comparison_placement,
+ pylint.extensions.broad_try_clause,
+ pylint.extensions.dict_init_mutate,
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
@@ -320,7 +322,7 @@ method-naming-style=snake_case
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,}$
-# Regular expression which can overwrite the naming style set by typevar-naming-style.
+# Regular expression matching correct type variable names
#typevar-rgx=
# Regular expression which should only match function or class names that do
@@ -444,6 +446,9 @@ max-public-methods=25
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
+# Maximum number of statements in a try-block
+max-try-statements = 14
+
# List of regular expressions of class ancestor names to
# ignore when counting public methods (see R0903).
exclude-too-few-public-methods=
@@ -510,7 +515,7 @@ preferred-modules=
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
-overgeneral-exceptions=Exception
+overgeneral-exceptions=builtins.Exception
[TYPING]
diff --git a/pyproject.toml b/pyproject.toml
index 90153e9de..0dadcf6c3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Debuggers",
@@ -78,3 +79,53 @@ pylint = ["testutils/testing_pylintrc"]
[tool.setuptools.dynamic]
version = {attr = "pylint.__pkginfo__.__version__"}
+
+[tool.aliases]
+test = "pytest"
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["*test_*.py"]
+addopts = "--strict-markers"
+filterwarnings = "error"
+markers = [
+ "primer_stdlib: Checks for crashes and errors when running pylint on stdlib",
+ "primer_external_batch_one: Checks for crashes and errors when running pylint on external libs (batch one)",
+ "benchmark: Baseline of pylint performance, if this regress something serious happened",
+ "timeout: Marks from pytest-timeout.",
+ "needs_two_cores: Checks that need 2 or more cores to be meaningful",
+]
+
+[tool.isort]
+profile = "black"
+known_third_party = ["platformdirs", "astroid", "sphinx", "isort", "pytest", "mccabe", "six", "toml"]
+skip_glob = ["tests/functional/**", "tests/input/**", "tests/extensions/data/**", "tests/regrtest_data/**", "tests/data/**", "astroid/**", "venv/**"]
+src_paths = ["pylint"]
+
+[tool.mypy]
+scripts_are_modules = true
+warn_unused_ignores = true
+show_error_codes = true
+enable_error_code = "ignore-without-code"
+strict = true
+# TODO: Remove this once pytest has annotations
+disallow_untyped_decorators = false
+
+[[tool.mypy.overrides]]
+ignore_missing_imports = true
+module = [
+ "_pytest.*",
+ "_string",
+ "astroid.*",
+ # `colorama` ignore is needed for Windows environment
+ "colorama",
+ "contributors_txt",
+ "coverage",
+ "dill",
+ "enchant.*",
+ "git.*",
+ "mccabe",
+ "pytest_benchmark.*",
+ "pytest",
+ "sphinx.*",
+]
diff --git a/requirements_test.txt b/requirements_test.txt
index 6ebb575cb..f5bfb9f45 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,13 +1,13 @@
-r requirements_test_pre_commit.txt
-r requirements_test_min.txt
coveralls~=3.3
-coverage~=6.4
+coverage~=6.5
pre-commit~=2.20
tbump~=6.9.0
contributors-txt>=0.9.0
-pytest-cov~=3.0
+pytest-cov~=4.0
pytest-profiling~=1.7
-pytest-xdist~=2.5
+pytest-xdist~=3.0
# Type packages for mypy
types-pkg_resources==0.1.3
tox>=3
diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt
index 54241ebbb..14daa70da 100644
--- a/requirements_test_pre_commit.txt
+++ b/requirements_test_pre_commit.txt
@@ -1,7 +1,8 @@
# Everything in this file should reflect the pre-commit configuration
# in .pre-commit-config.yaml
-black==22.6.0
+black==22.10.0
flake8==5.0.4
-flake8-typing-imports==1.13.0
+flake8-bugbear==22.10.27
+flake8-typing-imports==1.14.0
isort==5.10.1
-mypy==0.971
+mypy==0.991
diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json
index 6563c71d4..44ca36e20 100644
--- a/script/.contributors_aliases.json
+++ b/script/.contributors_aliases.json
@@ -602,5 +602,9 @@
"yileiyang@google.com": {
"mails": ["yileiyang@google.com"],
"name": "Yilei \"Dolee\" Yang"
+ },
+ "hofrob@protonmail.com": {
+ "mails": ["hofrob@protonmail.com"],
+ "name": "Robert Hofer"
}
}
diff --git a/script/check_newsfragments.py b/script/check_newsfragments.py
index 577b4ba88..3327d2d5d 100644
--- a/script/check_newsfragments.py
+++ b/script/check_newsfragments.py
@@ -51,14 +51,14 @@ def check_file(file: Path, verbose: bool) -> bool:
if match:
issue = match.group("issue")
if file.stem != issue:
- print(
+ echo(
f"{file} must be named '{issue}.<fragmenttype>', after the issue it references."
)
return False
if verbose:
- print(f"Checked '{file}': LGTM 🤖👍")
+ echo(f"Checked '{file}': LGTM 🤖👍")
return True
- print(
+ echo(
f"""\
{file}: does not respect the standard format 🤖👎
@@ -82,5 +82,11 @@ Refs #1234
return False
+def echo(msg: str) -> None:
+ # To support non-UTF-8 environments like Windows, we need
+ # to explicitly encode the message instead of using plain print()
+ sys.stdout.buffer.write(f"{msg}\n".encode())
+
+
if __name__ == "__main__":
sys.exit(main())
diff --git a/script/create_contributor_list.py b/script/create_contributor_list.py
index 7db3923f5..4502f824d 100644
--- a/script/create_contributor_list.py
+++ b/script/create_contributor_list.py
@@ -12,7 +12,7 @@ ALIASES_FILE = (BASE_DIRECTORY / "script/.contributors_aliases.json").relative_t
DEFAULT_CONTRIBUTOR_PATH = (BASE_DIRECTORY / "CONTRIBUTORS.txt").relative_to(CWD)
-def main():
+def main() -> None:
create_contributors_txt(
aliases_file=ALIASES_FILE, output=DEFAULT_CONTRIBUTOR_PATH, verbose=True
)
diff --git a/script/fix_documentation.py b/script/fix_documentation.py
index 1c97459c8..e8def2f73 100644
--- a/script/fix_documentation.py
+++ b/script/fix_documentation.py
@@ -36,12 +36,7 @@ def changelog_insert_empty_lines(file_content: str, subtitle_text: str) -> str:
for i, line in enumerate(lines):
if line.startswith(subtitle_text):
subtitle_count += 1
- if (
- subtitle_count == 1
- or i < 2
- or lines[i - 1] == ""
- and lines[i - 2] == ""
- ):
+ if subtitle_count == 1 or i < 2 or not lines[i - 1] and not lines[i - 2]:
continue
lines.insert(i, "")
return "\n".join(lines)
diff --git a/setup.cfg b/setup.cfg
index 2c13527cb..43de692ae 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,26 +8,6 @@ license_files =
LICENSE
CONTRIBUTORS.txt
-[aliases]
-test = pytest
-
-[tool:pytest]
-testpaths = tests
-python_files = *test_*.py
-addopts = --strict-markers
-markers =
- primer_stdlib: Checks for crashes and errors when running pylint on stdlib
- primer_external_batch_one: Checks for crashes and errors when running pylint on external libs (batch one)
- benchmark: Baseline of pylint performance, if this regress something serious happened
- timeout: Marks from pytest-timeout.
- needs_two_cores: Checks that need 2 or more cores to be meaningful
-
-[isort]
-profile = black
-known_third_party = platformdirs, astroid, sphinx, isort, pytest, mccabe, six, toml
-skip_glob = tests/functional/**,tests/input/**,tests/extensions/data/**,tests/regrtest_data/**,tests/data/**,astroid/**,venv/**
-src_paths = pylint
-
[flake8]
ignore =
E203, W503, # Incompatible with black see https://github.com/ambv/black/issues/315
@@ -37,58 +17,3 @@ max-complexity=39
# Required for flake8-typing-imports (v1.12.0)
# The plugin doesn't yet read the value from pyproject.toml
min_python_version = 3.7.2
-
-[mypy]
-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
-
-[mypy-tests.*]
-ignore_missing_imports = True
-
-[mypy-contributors_txt]
-ignore_missing_imports = True
-
-[mypy-coverage]
-ignore_missing_imports = True
-
-[mypy-enchant.*]
-ignore_missing_imports = True
-
-[mypy-isort.*]
-ignore_missing_imports = True
-
-[mypy-mccabe]
-ignore_missing_imports = True
-
-[mypy-pytest]
-ignore_missing_imports = True
-
-[mypy-_pytest.*]
-ignore_missing_imports = True
-
-[mypy-setuptools]
-ignore_missing_imports = True
-
-[mypy-_string]
-ignore_missing_imports = True
-
-[mypy-git.*]
-ignore_missing_imports = True
-
-[mypy-tomlkit]
-ignore_missing_imports = True
-
-[mypy-sphinx.*]
-ignore_missing_imports = True
-
-[mypy-dill]
-ignore_missing_imports = True
-
-[mypy-colorama]
-ignore_missing_imports = True
diff --git a/tbump.toml b/tbump.toml
index 166afddb6..3b61c031d 100644
--- a/tbump.toml
+++ b/tbump.toml
@@ -1,7 +1,7 @@
github_url = "https://github.com/PyCQA/pylint"
[version]
-current = "2.15.7"
+current = "2.16.0-dev"
regex = '''
^(?P<major>0|[1-9]\d*)
\.
diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py
index 6fb1cdf18..42521b593 100644
--- a/tests/benchmark/test_baseline_benchmarks.py
+++ b/tests/benchmark/test_baseline_benchmarks.py
@@ -13,6 +13,7 @@ from unittest.mock import patch
import pytest
from astroid import nodes
+from pytest_benchmark.fixture import BenchmarkFixture
from pylint.checkers import BaseRawFileChecker
from pylint.lint import PyLinter, check_parallel
@@ -22,7 +23,7 @@ from pylint.typing import FileItem
from pylint.utils import register_plugins
-def _empty_filepath():
+def _empty_filepath() -> str:
return os.path.abspath(
os.path.join(
os.path.dirname(__file__), "..", "input", "benchmark_minimal_file.py"
@@ -114,7 +115,7 @@ class TestEstablishBaselineBenchmarks:
)
lot_of_files = 500
- def test_baseline_benchmark_j1(self, benchmark):
+ def test_baseline_benchmark_j1(self, benchmark: BenchmarkFixture) -> None:
"""Establish a baseline of pylint performance with no work.
We will add extra Checkers in other benchmarks.
@@ -131,7 +132,7 @@ class TestEstablishBaselineBenchmarks:
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
@pytest.mark.needs_two_cores
- def test_baseline_benchmark_j2(self, benchmark):
+ def test_baseline_benchmark_j2(self, benchmark: BenchmarkFixture) -> None:
"""Establish a baseline of pylint performance with no work across threads.
Same as `test_baseline_benchmark_j1` but we use -j2 with 2 fake files to
@@ -154,7 +155,9 @@ class TestEstablishBaselineBenchmarks:
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
@pytest.mark.needs_two_cores
- def test_baseline_benchmark_check_parallel_j2(self, benchmark):
+ def test_baseline_benchmark_check_parallel_j2(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""Should demonstrate times very close to `test_baseline_benchmark_j2`."""
linter = PyLinter(reporter=Reporter())
@@ -167,7 +170,7 @@ class TestEstablishBaselineBenchmarks:
linter.msg_status == 0
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
- def test_baseline_lots_of_files_j1(self, benchmark):
+ def test_baseline_lots_of_files_j1(self, benchmark: BenchmarkFixture) -> None:
"""Establish a baseline with only 'main' checker being run in -j1.
We do not register any checkers except the default 'main', so the cost is just
@@ -187,7 +190,7 @@ class TestEstablishBaselineBenchmarks:
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
@pytest.mark.needs_two_cores
- def test_baseline_lots_of_files_j2(self, benchmark):
+ def test_baseline_lots_of_files_j2(self, benchmark: BenchmarkFixture) -> None:
"""Establish a baseline with only 'main' checker being run in -j2.
As with the -j1 variant above `test_baseline_lots_of_files_j1`, we do not
@@ -207,7 +210,9 @@ class TestEstablishBaselineBenchmarks:
linter.msg_status == 0
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
- def test_baseline_lots_of_files_j1_empty_checker(self, benchmark):
+ def test_baseline_lots_of_files_j1_empty_checker(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""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
@@ -228,7 +233,9 @@ class TestEstablishBaselineBenchmarks:
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
@pytest.mark.needs_two_cores
- def test_baseline_lots_of_files_j2_empty_checker(self, benchmark):
+ def test_baseline_lots_of_files_j2_empty_checker(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""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
@@ -248,7 +255,9 @@ class TestEstablishBaselineBenchmarks:
linter.msg_status == 0
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
- def test_baseline_benchmark_j1_single_working_checker(self, benchmark):
+ def test_baseline_benchmark_j1_single_working_checker(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""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
@@ -275,7 +284,9 @@ class TestEstablishBaselineBenchmarks:
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
@pytest.mark.needs_two_cores
- def test_baseline_benchmark_j2_single_working_checker(self, benchmark):
+ def test_baseline_benchmark_j2_single_working_checker(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""Establishes baseline of multi-worker performance for PyLinter/check_parallel.
We expect this benchmark to take less time that test_baseline_benchmark_j1,
@@ -302,7 +313,9 @@ class TestEstablishBaselineBenchmarks:
linter.msg_status == 0
), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}"
- def test_baseline_benchmark_j1_all_checks_single_file(self, benchmark):
+ def test_baseline_benchmark_j1_all_checks_single_file(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""Runs a single file, with -j1, against all checkers/Extensions."""
args = [self.empty_filepath, "--enable=all", "--enable-all-extensions"]
runner = benchmark(Run, args, reporter=Reporter(), exit=False)
@@ -314,7 +327,9 @@ class TestEstablishBaselineBenchmarks:
runner.linter.msg_status == 0
), 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):
+ def test_baseline_benchmark_j1_all_checks_lots_of_files(
+ self, benchmark: BenchmarkFixture
+ ) -> None:
"""Runs lots of files, with -j1, against all plug-ins.
... that's the intent at least.
diff --git a/tests/checkers/base/unittest_base.py b/tests/checkers/base/unittest_base.py
index 05a7271cd..99a8f659e 100644
--- a/tests/checkers/base/unittest_base.py
+++ b/tests/checkers/base/unittest_base.py
@@ -9,7 +9,7 @@ import unittest
class TestNoSix(unittest.TestCase):
@unittest.skip("too many dependencies need six :(")
- def test_no_six(self):
+ def test_no_six(self) -> None:
try:
has_six = True
except ImportError:
diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py
index 1c5fc2b16..a63ebfc20 100644
--- a/tests/checkers/unittest_deprecated.py
+++ b/tests/checkers/unittest_deprecated.py
@@ -129,7 +129,7 @@ class TestDeprecatedChecker(CheckerTestCase):
line=9,
col_offset=0,
end_line=9,
- end_col_offset=21,
+ end_col_offset=12,
)
):
self.checker.visit_call(node)
diff --git a/tests/checkers/unittest_non_ascii_name.py b/tests/checkers/unittest_non_ascii_name.py
index 1830dd7ec..4f854dddc 100644
--- a/tests/checkers/unittest_non_ascii_name.py
+++ b/tests/checkers/unittest_non_ascii_name.py
@@ -23,7 +23,7 @@ class TestNonAsciiChecker(pylint.testutils.CheckerTestCase):
@pytest.mark.skipif(
sys.version_info < (3, 8), reason="requires python3.8 or higher"
)
- def test_kwargs_and_position_only(self):
+ def test_kwargs_and_position_only(self) -> None:
"""Even the new position only and keyword only should be found."""
node = astroid.extract_node(
"""
@@ -136,7 +136,7 @@ class TestNonAsciiChecker(pylint.testutils.CheckerTestCase):
self,
code: str,
assign_type: str,
- ):
+ ) -> None:
"""Variables defined no matter where, should be checked for non ascii."""
assign_node = astroid.extract_node(code)
@@ -261,7 +261,7 @@ class TestNonAsciiChecker(pylint.testutils.CheckerTestCase):
),
],
)
- def test_check_import(self, import_statement: str, wrong_name: str | None):
+ def test_check_import(self, import_statement: str, wrong_name: str | None) -> None:
"""We expect that for everything that user can change there is a message."""
node = astroid.extract_node(f"{import_statement} #@")
diff --git a/tests/checkers/unittest_spelling.py b/tests/checkers/unittest_spelling.py
index b07212a19..abeb9dcf8 100644
--- a/tests/checkers/unittest_spelling.py
+++ b/tests/checkers/unittest_spelling.py
@@ -308,10 +308,10 @@ class TestSpellingChecker(CheckerTestCase): # pylint:disable=too-many-public-me
# to show up in the pytest output as part of the test name
# when running parameterized tests.
self,
- misspelled_portion_of_directive,
- second_portion_of_directive,
- description,
- ):
+ misspelled_portion_of_directive: str,
+ second_portion_of_directive: str,
+ description: str,
+ ) -> None:
full_comment = f"# {misspelled_portion_of_directive}{second_portion_of_directive} {misspelled_portion_of_directive}"
with self.assertAddsMessages(
MessageTest(
@@ -386,7 +386,7 @@ class TestSpellingChecker(CheckerTestCase): # pylint:disable=too-many-public-me
spelling_dict=spell_dict,
spelling_ignore_comment_directives="newdirective:,noqa",
)
- def test_skip_directives_specified_in_pylintrc(self):
+ def test_skip_directives_specified_in_pylintrc(self) -> None:
full_comment = "# newdirective: do this newdirective"
with self.assertAddsMessages(
MessageTest(
diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py
index 2f47a4075..66747deb9 100644
--- a/tests/checkers/unittest_stdlib.py
+++ b/tests/checkers/unittest_stdlib.py
@@ -6,23 +6,26 @@ from __future__ import annotations
import contextlib
from collections.abc import Callable, Iterator
-from typing import Any
+from typing import Any, Type
import astroid
from astroid import nodes
+from astroid.context import InferenceContext
from astroid.manager import AstroidManager
from pylint.checkers import stdlib
from pylint.testutils import CheckerTestCase
+_NodeNGT = Type[nodes.NodeNG]
+
@contextlib.contextmanager
def _add_transform(
manager: AstroidManager,
- node: type,
- transform: Callable,
+ node: _NodeNGT,
+ transform: Callable[[_NodeNGT], _NodeNGT],
predicate: Any | None = None,
-) -> Iterator:
+) -> Iterator[None]:
manager.register_transform(node, transform, predicate)
try:
yield
@@ -43,8 +46,8 @@ class TestStdlibChecker(CheckerTestCase):
def infer_func(
inner_node: nodes.Name,
- context: Any | None = None, # pylint: disable=unused-argument
- ) -> Iterator[Iterator | Iterator[nodes.AssignAttr]]:
+ context: InferenceContext | None = None, # pylint: disable=unused-argument
+ ) -> Iterator[nodes.AssignAttr]:
new_node = nodes.AssignAttr(attrname="alpha", parent=inner_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 41445e226..7746ce4ae 100644
--- a/tests/checkers/unittest_unicode/unittest_bad_chars.py
+++ b/tests/checkers/unittest_unicode/unittest_bad_chars.py
@@ -41,7 +41,9 @@ def bad_char_file_generator(tmp_path: Path) -> Callable[[str, bool, str], Path]:
"# Invalid char esc: \x1B",
)
- def _bad_char_file_generator(codec: str, add_invalid_bytes: bool, line_ending: str):
+ def _bad_char_file_generator(
+ codec: str, add_invalid_bytes: bool, line_ending: str
+ ) -> Path:
byte_suffix = b""
if add_invalid_bytes:
if codec == "utf-8":
@@ -120,7 +122,7 @@ class TestBadCharsChecker(pylint.testutils.CheckerTestCase):
codec_and_msg: tuple[str, tuple[pylint.testutils.MessageTest]],
line_ending: str,
add_invalid_bytes: bool,
- ):
+ ) -> None:
"""All combinations of bad characters that are accepted by Python at the moment
are tested in all possible combinations of
- line ending
@@ -215,7 +217,7 @@ class TestBadCharsChecker(pylint.testutils.CheckerTestCase):
char: str,
msg_id: str,
codec_and_msg: tuple[str, tuple[pylint.testutils.MessageTest]],
- ):
+ ) -> None:
"""Special test for a file containing chars that lead to
Python or Astroid crashes (which causes Pylint to exit early)
"""
diff --git a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py
index c450db211..6b11dcfef 100644
--- a/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py
+++ b/tests/checkers/unittest_unicode/unittest_bidirectional_unicode.py
@@ -78,7 +78,7 @@ class TestBidirectionalUnicodeChecker(pylint.testutils.CheckerTestCase):
)
],
)
- def test_find_bidi_string(self, bad_string: str, codec: str):
+ def test_find_bidi_string(self, bad_string: str, codec: str) -> None:
"""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 c2fef9357..0c809ccdc 100644
--- a/tests/checkers/unittest_unicode/unittest_functions.py
+++ b/tests/checkers/unittest_unicode/unittest_functions.py
@@ -105,8 +105,10 @@ SEARCH_DICT_BYTE_UTF8 = {
def test_map_positions_to_result(
line: pylint.checkers.unicode._StrLike,
expected: dict[int, pylint.checkers.unicode._BadChar],
- search_dict,
-):
+ search_dict: dict[
+ pylint.checkers.unicode._StrLike, pylint.checkers.unicode._BadChar
+ ],
+) -> None:
"""Test all possible outcomes for map position function in UTF-8 and ASCII."""
if isinstance(line, bytes):
newline = b"\n"
@@ -133,7 +135,7 @@ def test_map_positions_to_result(
pytest.param(b"12345678\n\r", id="wrong_order_byte"),
],
)
-def test_line_length(line: pylint.checkers.unicode._StrLike):
+def test_line_length(line: pylint.checkers.unicode._StrLike) -> None:
assert pylint.checkers.unicode._line_length(line, "utf-8") == 10
@@ -146,7 +148,7 @@ def test_line_length(line: pylint.checkers.unicode._StrLike):
pytest.param("12345678\n\r", id="wrong_order"),
],
)
-def test_line_length_utf16(line: str):
+def test_line_length_utf16(line: str) -> None:
assert pylint.checkers.unicode._line_length(line.encode("utf-16"), "utf-16") == 10
@@ -159,7 +161,7 @@ def test_line_length_utf16(line: str):
pytest.param("12345678\n\r", id="wrong_order"),
],
)
-def test_line_length_utf32(line: str):
+def test_line_length_utf32(line: str) -> None:
assert pylint.checkers.unicode._line_length(line.encode("utf-32"), "utf-32") == 10
@@ -186,7 +188,7 @@ def test_line_length_utf32(line: str):
("ASCII", "ascii"),
],
)
-def test__normalize_codec_name(codec: str, expected: str):
+def test__normalize_codec_name(codec: str, expected: str) -> None:
assert pylint.checkers.unicode._normalize_codec_name(codec) == expected
@@ -216,7 +218,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
-):
+) -> None:
"""Content of stream should be the same as should be the length."""
def decode_line(line: bytes, codec: str) -> str:
@@ -260,5 +262,5 @@ def test___fix_utf16_32_line_stream(
("ascii", 1),
],
)
-def test__byte_to_str_length(codec: str, expected: int):
+def test__byte_to_str_length(codec: str, expected: int) -> None:
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
index d2807301c..e8695a74f 100644
--- a/tests/checkers/unittest_unicode/unittest_invalid_encoding.py
+++ b/tests/checkers/unittest_unicode/unittest_invalid_encoding.py
@@ -52,7 +52,9 @@ class TestInvalidEncoding(pylint.testutils.CheckerTestCase):
("pep_bidirectional_utf_32_bom.txt", 1),
],
)
- def test_invalid_unicode_files(self, tmp_path: Path, test_file: str, line_no: int):
+ def test_invalid_unicode_files(
+ self, tmp_path: Path, test_file: str, line_no: int
+ ) -> None:
test_file_path = UNICODE_TESTS / test_file
target = shutil.copy(
test_file_path, tmp_path / test_file.replace(".txt", ".py")
@@ -126,7 +128,7 @@ class TestInvalidEncoding(pylint.testutils.CheckerTestCase):
),
],
)
- def test__determine_codec(self, content: bytes, codec: str, line: int):
+ def test__determine_codec(self, content: bytes, codec: str, line: int) -> None:
"""The codec determined should be exact no matter what we throw at it."""
assert self.checker._determine_codec(io.BytesIO(content)) == (codec, line)
@@ -139,6 +141,8 @@ class TestInvalidEncoding(pylint.testutils.CheckerTestCase):
"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]):
+ def test___check_codec(
+ self, codec: str, msg: tuple[pylint.testutils.MessageTest]
+ ) -> None:
with self.assertAddsMessages(*msg):
self.checker._check_codec(codec, 1)
diff --git a/tests/checkers/unittest_utils.py b/tests/checkers/unittest_utils.py
index f68a48dbb..08b9c188d 100644
--- a/tests/checkers/unittest_utils.py
+++ b/tests/checkers/unittest_utils.py
@@ -25,7 +25,7 @@ from pylint.checkers.base_checker import BaseChecker
("mybuiltin", False),
],
)
-def testIsBuiltin(name, expected):
+def testIsBuiltin(name: str, expected: bool) -> None:
assert utils.is_builtin(name) == expected
@@ -491,7 +491,7 @@ def test_deprecation_check_messages() -> None:
)
-def test_is_typing_literal() -> None:
+def test_is_typing_member() -> None:
code = astroid.extract_node(
"""
from typing import Literal as Lit, Set as Literal
@@ -503,9 +503,9 @@ def test_is_typing_literal() -> None:
"""
)
- assert not utils.is_typing_literal(code[0])
- assert utils.is_typing_literal(code[1])
- assert utils.is_typing_literal(code[2])
+ assert not utils.is_typing_member(code[0], ("Literal",))
+ assert utils.is_typing_member(code[1], ("Literal",))
+ assert utils.is_typing_member(code[2], ("Literal",))
code = astroid.extract_node(
"""
@@ -513,5 +513,5 @@ def test_is_typing_literal() -> None:
typing.Literal #@
"""
)
- assert not utils.is_typing_literal(code[0])
- assert not utils.is_typing_literal(code[1])
+ assert not utils.is_typing_member(code[0], ("Literal",))
+ assert not utils.is_typing_member(code[1], ("Literal",))
diff --git a/tests/config/pylint_config/test_pylint_config_generate.py b/tests/config/pylint_config/test_pylint_config_generate.py
index 4650ab1fb..65fc05557 100644
--- a/tests/config/pylint_config/test_pylint_config_generate.py
+++ b/tests/config/pylint_config/test_pylint_config_generate.py
@@ -23,6 +23,9 @@ def test_generate_interactive_exitcode(monkeypatch: MonkeyPatch) -> None:
"pylint.config._pylint_config.utils.get_and_validate_format", lambda: "toml"
)
monkeypatch.setattr(
+ "pylint.config._pylint_config.utils.get_minimal_setting", lambda: False
+ )
+ monkeypatch.setattr(
"pylint.config._pylint_config.utils.get_and_validate_output_file",
lambda: (False, Path()),
)
@@ -42,6 +45,9 @@ def test_format_of_output(
"""Check that we output the correct format."""
# Monkeypatch everything we don't want to check in this test
monkeypatch.setattr(
+ "pylint.config._pylint_config.utils.get_minimal_setting", lambda: False
+ )
+ monkeypatch.setattr(
"pylint.config._pylint_config.utils.get_and_validate_output_file",
lambda: (False, Path()),
)
@@ -90,6 +96,9 @@ def test_writing_to_output_file(
monkeypatch.setattr(
"pylint.config._pylint_config.utils.get_and_validate_format", lambda: "toml"
)
+ monkeypatch.setattr(
+ "pylint.config._pylint_config.utils.get_minimal_setting", lambda: False
+ )
# Set up a temporary file to write to
tempfile_name = Path(tempfile.gettempdir()) / "CONFIG"
@@ -150,3 +159,34 @@ def test_writing_to_output_file(
Run(["generate", "--interactive"], exit=False)
captured = capsys.readouterr()
assert last_modified != tempfile_name.stat().st_mtime
+
+
+def test_writing_minimal_file(
+ monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]
+) -> None:
+ """Check that we can write a minimal file."""
+ # Monkeypatch everything we don't want to check in this test
+ monkeypatch.setattr(
+ "pylint.config._pylint_config.utils.get_and_validate_format", lambda: "toml"
+ )
+ monkeypatch.setattr(
+ "pylint.config._pylint_config.utils.get_and_validate_output_file",
+ lambda: (False, Path()),
+ )
+
+ # Set the answers needed for the input() calls
+ answers = iter(["no", "yes"])
+ monkeypatch.setattr("builtins.input", lambda x: next(answers))
+
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", message="NOTE:.*", category=UserWarning)
+ # Check not minimal has comments
+ Run(["generate", "--interactive"], exit=False)
+ captured = capsys.readouterr()
+ assert any(line.startswith("#") for line in captured.out.splitlines())
+
+ # Check minimal doesn't have comments and no default values
+ Run(["--accept-no-return-doc=y", "generate", "--interactive"], exit=False)
+ captured = capsys.readouterr()
+ assert not any(i.startswith("#") for i in captured.out.split("\n"))
+ assert "accept-no-return-doc" not in captured.out
diff --git a/tests/config/test_argparse_config.py b/tests/config/test_argparse_config.py
index 3bad5e8fa..a9d7f70c2 100644
--- a/tests/config/test_argparse_config.py
+++ b/tests/config/test_argparse_config.py
@@ -43,7 +43,10 @@ class TestArgparseOptionsProviderMixin:
def test_logger_rcfile() -> None:
"""Check that we parse the rcfile for the logging checker correctly."""
with pytest.raises(SystemExit) as ex:
- Run([LOGGING_TEST, f"--rcfile={LOGGING_TEST.replace('.py', '.rc')}"])
+ # replace only the last .py in the string with .rc
+ # we do so by inverting the string and replace the first occurrence (of the inverted tokens!)
+ _rcfile = LOGGING_TEST[::-1].replace("yp.", "cr.", 1)[::-1]
+ Run([LOGGING_TEST, f"--rcfile={_rcfile}"])
assert ex.value.code == 0
diff --git a/tests/config/test_find_default_config_files.py b/tests/config/test_find_default_config_files.py
index 10484be1d..2fd66544d 100644
--- a/tests/config/test_find_default_config_files.py
+++ b/tests/config/test_find_default_config_files.py
@@ -233,7 +233,7 @@ disable = logging-not-lazy,logging-format-interpolation
],
],
)
-def test_cfg_has_config(content: str, expected: str, tmp_path: Path) -> None:
+def test_cfg_has_config(content: str, expected: bool, tmp_path: Path) -> None:
"""Test that a cfg file has a pylint config."""
fake_cfg = tmp_path / "fake.cfg"
with open(fake_cfg, "w", encoding="utf8") as f:
diff --git a/tests/config/test_functional_config_loading.py b/tests/config/test_functional_config_loading.py
index 64227df79..432bdc1a1 100644
--- a/tests/config/test_functional_config_loading.py
+++ b/tests/config/test_functional_config_loading.py
@@ -64,9 +64,9 @@ def test_functional_config_loading(
configuration_path: str,
default_configuration: PylintConfiguration,
file_to_lint_path: str,
- capsys: CaptureFixture,
+ capsys: CaptureFixture[str],
caplog: LogCaptureFixture,
-):
+) -> None:
"""Functional tests for configurations."""
# logging is helpful to see what's expected and why. The output of the
# program is checked during the test so printing messes with the result.
diff --git a/tests/config/test_per_directory_config.py b/tests/config/test_per_directory_config.py
index 9bc0ef9bc..e0bf75e70 100644
--- a/tests/config/test_per_directory_config.py
+++ b/tests/config/test_per_directory_config.py
@@ -2,17 +2,16 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
-
-from py._path.local import LocalPath # type: ignore[import]
+from pathlib import Path
from pylint.lint import Run
-def test_fall_back_on_base_config(tmpdir: LocalPath) -> None:
+def test_fall_back_on_base_config(tmp_path: Path) -> None:
"""Test that we correctly fall back on the base config."""
# A file under the current dir should fall back to the highest level
# For pylint this is ./pylintrc
- test_file = tmpdir / "test.py"
+ test_file = tmp_path / "test.py"
runner = Run([__name__], exit=False)
assert id(runner.linter.config) == id(runner.linter._base_config)
diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py
index a75de41bb..343663602 100644
--- a/tests/config/unittest_config.py
+++ b/tests/config/unittest_config.py
@@ -17,19 +17,19 @@ from pylint.typing import MessageDefinitionTuple
def test__regexp_validator_valid() -> None:
- result = config.option._regexp_validator(None, None, "test_.*")
+ result = config.option._regexp_validator(None, "", "test_.*")
assert isinstance(result, re.Pattern)
assert result.pattern == "test_.*"
def test__regexp_validator_invalid() -> None:
with pytest.raises(re.error):
- config.option._regexp_validator(None, None, "test_)")
+ config.option._regexp_validator(None, "", "test_)")
def test__csv_validator_no_spaces() -> None:
values = ["One", "Two", "Three"]
- result = config.option._csv_validator(None, None, ",".join(values))
+ result = config.option._csv_validator(None, "", ",".join(values))
assert isinstance(result, list)
assert len(result) == 3
for i, value in enumerate(values):
@@ -38,7 +38,7 @@ def test__csv_validator_no_spaces() -> None:
def test__csv_validator_spaces() -> None:
values = ["One", "Two", "Three"]
- result = config.option._csv_validator(None, None, ", ".join(values))
+ result = config.option._csv_validator(None, "", ", ".join(values))
assert isinstance(result, list)
assert len(result) == 3
for i, value in enumerate(values):
@@ -47,7 +47,7 @@ def test__csv_validator_spaces() -> None:
def test__regexp_csv_validator_valid() -> None:
pattern_strings = ["test_.*", "foo\\.bar", "^baz$"]
- result = config.option._regexp_csv_validator(None, None, ",".join(pattern_strings))
+ result = config.option._regexp_csv_validator(None, "", ",".join(pattern_strings))
for i, regex in enumerate(result):
assert isinstance(regex, re.Pattern)
assert regex.pattern == pattern_strings[i]
@@ -56,7 +56,7 @@ def test__regexp_csv_validator_valid() -> None:
def test__regexp_csv_validator_invalid() -> None:
pattern_strings = ["test_.*", "foo\\.bar", "^baz)$"]
with pytest.raises(re.error):
- config.option._regexp_csv_validator(None, None, ",".join(pattern_strings))
+ config.option._regexp_csv_validator(None, "", ",".join(pattern_strings))
class TestPyLinterOptionSetters(CheckerTestCase):
diff --git a/tests/conftest.py b/tests/conftest.py
index 1ebe62e97..a35e5cc14 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,13 +7,16 @@
from __future__ import annotations
import os
+from collections.abc import Callable
from pathlib import Path
import pytest
from pylint import checkers
+from pylint.checkers import BaseChecker
from pylint.lint import PyLinter
from pylint.lint.run import _cpu_count
+from pylint.reporters import BaseReporter
from pylint.testutils import MinimalTestReporter
HERE = Path(__file__).parent
@@ -25,7 +28,13 @@ def tests_directory() -> Path:
@pytest.fixture
-def linter(checker, register, enable, disable, reporter):
+def linter(
+ checker: type[BaseChecker] | None,
+ register: Callable[[PyLinter], None] | None,
+ enable: str | None,
+ disable: str | None,
+ reporter: type[BaseReporter],
+) -> PyLinter:
_linter = PyLinter()
_linter.set_reporter(reporter())
checkers.initialize(_linter)
@@ -44,31 +53,31 @@ def linter(checker, register, enable, disable, reporter):
@pytest.fixture(scope="module")
-def checker():
+def checker() -> None:
return None
@pytest.fixture(scope="module")
-def register():
+def register() -> None:
return None
@pytest.fixture(scope="module")
-def enable():
+def enable() -> None:
return None
@pytest.fixture(scope="module")
-def disable():
+def disable() -> None:
return None
@pytest.fixture(scope="module")
-def reporter():
+def reporter() -> type[MinimalTestReporter]:
return MinimalTestReporter
-def pytest_addoption(parser) -> None:
+def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
"--primer-stdlib",
action="store_true",
diff --git a/tests/extensions/test_check_docs_utils.py b/tests/extensions/test_check_docs_utils.py
index 3e70ffbfd..692c14859 100644
--- a/tests/extensions/test_check_docs_utils.py
+++ b/tests/extensions/test_check_docs_utils.py
@@ -3,8 +3,12 @@
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
"""Unit tests for utils functions in :mod:`pylint.extensions._check_docs_utils`."""
+
+from __future__ import annotations
+
import astroid
import pytest
+from astroid import nodes
from pylint.extensions import _check_docs_utils as utils
@@ -134,7 +138,7 @@ def test_space_indentation(string: str, count: int) -> None:
),
],
)
-def test_exception(raise_node, expected):
+def test_exception(raise_node: nodes.NodeNG, expected: set[str]) -> None:
found_nodes = utils.possible_exc_types(raise_node)
for node in found_nodes:
assert isinstance(node, astroid.nodes.ClassDef)
diff --git a/tests/extensions/test_private_import.py b/tests/extensions/test_private_import.py
index d2d79947f..c82f51a42 100644
--- a/tests/extensions/test_private_import.py
+++ b/tests/extensions/test_private_import.py
@@ -4,7 +4,7 @@
"""Tests the local module directory comparison logic which requires mocking file directories"""
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
import astroid
@@ -19,7 +19,7 @@ class TestPrivateImport(CheckerTestCase):
CHECKER_CLASS = private_import.PrivateImportChecker
@patch("pathlib.Path.parent")
- def test_internal_module(self, parent) -> None:
+ def test_internal_module(self, parent: MagicMock) -> None:
parent.parts = ("", "dir", "module")
import_from = astroid.extract_node("""from module import _file""")
@@ -27,7 +27,7 @@ class TestPrivateImport(CheckerTestCase):
self.checker.visit_importfrom(import_from)
@patch("pathlib.Path.parent")
- def test_external_module_nested(self, parent) -> None:
+ def test_external_module_nested(self, parent: MagicMock) -> None:
parent.parts = ("", "dir", "module", "module_files", "util")
import_from = astroid.extract_node("""from module import _file""")
@@ -36,7 +36,7 @@ class TestPrivateImport(CheckerTestCase):
self.checker.visit_importfrom(import_from)
@patch("pathlib.Path.parent")
- def test_external_module_dot_import(self, parent) -> None:
+ def test_external_module_dot_import(self, parent: MagicMock) -> None:
parent.parts = ("", "dir", "outer", "inner", "module_files", "util")
import_from = astroid.extract_node("""from outer.inner import _file""")
@@ -45,7 +45,7 @@ class TestPrivateImport(CheckerTestCase):
self.checker.visit_importfrom(import_from)
@patch("pathlib.Path.parent")
- def test_external_module_dot_import_outer_only(self, parent) -> None:
+ def test_external_module_dot_import_outer_only(self, parent: MagicMock) -> None:
parent.parts = ("", "dir", "outer", "extensions")
import_from = astroid.extract_node("""from outer.inner import _file""")
@@ -54,7 +54,7 @@ class TestPrivateImport(CheckerTestCase):
self.checker.visit_importfrom(import_from)
@patch("pathlib.Path.parent")
- def test_external_module(self, parent) -> None:
+ def test_external_module(self, parent: MagicMock) -> None:
parent.parts = ("", "dir", "other")
import_from = astroid.extract_node("""from module import _file""")
diff --git a/tests/functional/a/abstract/abstract_method.txt b/tests/functional/a/abstract/abstract_method.txt
index 2b4ea9a2e..f2b2b6c74 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: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
+abstract-method:47:0:47:14:Concrete:Method 'bbbb' is abstract in class 'Abstract' but is not overridden in child class 'Concrete':INFERENCE
+abstract-method:70:0:70:15:Container:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE
+abstract-method:70:0:70:15:Container:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE
+abstract-method:70:0:70:15:Container:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE
+abstract-method:76:0:76:13:Sizable:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE
+abstract-method:76:0:76:13:Sizable:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE
+abstract-method:76:0:76:13:Sizable:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE
+abstract-method:82:0:82:14:Hashable:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE
+abstract-method:82:0:82:14:Hashable:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE
+abstract-method:82:0:82:14:Hashable:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE
+abstract-method:87:0:87:14:Iterator:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE
+abstract-method:87:0:87:14:Iterator:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE
+abstract-method:87:0:87:14:Iterator:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE
+abstract-method:106:0:106:19:BadComplexMro:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'BadComplexMro':INFERENCE
+abstract-method:106:0:106:19:BadComplexMro:Method '__len__' is abstract in class 'AbstractSizable' but is not overridden in child class 'BadComplexMro':INFERENCE
+abstract-method:106:0:106:19:BadComplexMro:Method 'length' is abstract in class 'AbstractSizable' but is not overridden in child class 'BadComplexMro':INFERENCE
diff --git a/tests/functional/a/alternative/alternative_union_syntax.py b/tests/functional/a/alternative/alternative_union_syntax.py
index 4492323a2..d25d9e8f4 100644
--- a/tests/functional/a/alternative/alternative_union_syntax.py
+++ b/tests/functional/a/alternative/alternative_union_syntax.py
@@ -1,6 +1,6 @@
"""Test PEP 604 - Alternative Union syntax"""
# pylint: disable=missing-function-docstring,unused-argument,invalid-name,missing-class-docstring
-# pylint: disable=inherit-non-class,too-few-public-methods,unnecessary-direct-lambda-call
+# pylint: disable=inherit-non-class,too-few-public-methods,unnecessary-direct-lambda-call,superfluous-parens
import dataclasses
import typing
from dataclasses import dataclass
diff --git a/tests/functional/a/arguments_differ.txt b/tests/functional/a/arguments_differ.txt
index bb1abb2eb..54a75e84e 100644
--- a/tests/functional/a/arguments_differ.txt
+++ b/tests/functional/a/arguments_differ.txt
@@ -1,13 +1,13 @@
-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: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
+arguments-differ:12:4:12:12:Child.test:Number of parameters was 1 in 'Parent.test' and is now 2 in overriding '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 overriding '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 overriding 'ClassmethodChild.func' method:UNDEFINED
+arguments-differ:68:4:68:18:VarargsChild.has_kwargs:Variadics removed in overriding 'VarargsChild.has_kwargs' method:UNDEFINED
+arguments-renamed:71:4:71:17:VarargsChild.no_kwargs:Parameter 'args' has been renamed to 'arg' in overriding '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 overriding '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 overriding 'SecondChangesArgs.test' 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 overriding '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 overriding '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 overriding '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 overriding 'Foo.kwonly_4' method:UNDEFINED
+arguments-differ:319:4:319:16:Foo.kwonly_5:Variadics removed in overriding '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 overriding 'ClassWithNewNonDefaultKeywordOnly.method' method:UNDEFINED
diff --git a/tests/functional/a/arguments_renamed.txt b/tests/functional/a/arguments_renamed.txt
index 47d4188dd..10fe4c207 100644
--- a/tests/functional/a/arguments_renamed.txt
+++ b/tests/functional/a/arguments_renamed.txt
@@ -1,10 +1,10 @@
-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-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: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
+arguments-renamed:17:4:17:12:Orange.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overriding 'Orange.brew' method:UNDEFINED
+arguments-renamed:20:4:20:26:Orange.eat_with_condiment:Parameter 'fruit_name' has been renamed to 'orange_name' in overriding '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 overriding 'Banana.eat_with_condiment' method:UNDEFINED
+arguments-renamed:40:4:40:12:Child.test:Parameter 'arg' has been renamed to 'arg1' in overriding 'Child.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 overriding 'Child.kwargs_test' method:UNDEFINED
+arguments-renamed:48:4:48:12:Child2.test:Parameter 'arg' has been renamed to 'var' in overriding '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 overriding 'Child2.kwargs_test' method:UNDEFINED
+arguments-renamed:67:4:67:13:ChildDefaults.test1:Parameter 'barg' has been renamed to 'param2' in overriding 'ChildDefaults.test1' method:UNDEFINED
+arguments-renamed:95:8:95:16:FruitOverrideConditional.brew:Parameter 'fruit_name' has been renamed to 'orange_name' in overriding '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 overriding 'FruitOverrideConditional.eat_with_condiment' method:UNDEFINED
diff --git a/tests/functional/a/assert_on_tuple.py b/tests/functional/a/assert_on_tuple.py
index 3ceb6e167..cf785d53a 100644
--- a/tests/functional/a/assert_on_tuple.py
+++ b/tests/functional/a/assert_on_tuple.py
@@ -1,11 +1,11 @@
'''Assert check example'''
-# pylint: disable=comparison-with-itself, comparison-of-constants
-assert (1 == 1, 2 == 2), "no error"
+# pylint: disable=comparison-with-itself, comparison-of-constants, line-too-long
+assert (1 == 1, 2 == 2), "message is raised even when there is an assert message" # [assert-on-tuple]
assert (1 == 1, 2 == 2) # [assert-on-tuple]
assert 1 == 1, "no error"
-assert (1 == 1, ), "no error"
-assert (1 == 1, )
-assert (1 == 1, 2 == 2, 3 == 5), "no error"
+assert (1 == 1, ), "message is raised even when there is an assert message" # [assert-on-tuple]
+assert (1 == 1, ) # [assert-on-tuple]
+assert (1 == 1, 2 == 2, 3 == 5), "message is raised even when there is an assert message" # [assert-on-tuple]
assert ()
assert (True, 'error msg') # [assert-on-tuple]
diff --git a/tests/functional/a/assert_on_tuple.txt b/tests/functional/a/assert_on_tuple.txt
index 85929cd42..d3e263f23 100644
--- a/tests/functional/a/assert_on_tuple.txt
+++ b/tests/functional/a/assert_on_tuple.txt
@@ -1,2 +1,6 @@
-assert-on-tuple:5:0:5:23::Assert called on a 2-item-tuple. Did you mean 'assert x,y'?:UNDEFINED
-assert-on-tuple:11:0:11:26::Assert called on a 2-item-tuple. Did you mean 'assert x,y'?:UNDEFINED
+assert-on-tuple:4:0:4:81::Assert called on a populated tuple. Did you mean 'assert x,y'?:HIGH
+assert-on-tuple:5:0:5:23::Assert called on a populated tuple. Did you mean 'assert x,y'?:HIGH
+assert-on-tuple:7:0:7:75::Assert called on a populated tuple. Did you mean 'assert x,y'?:HIGH
+assert-on-tuple:8:0:8:17::Assert called on a populated tuple. Did you mean 'assert x,y'?:HIGH
+assert-on-tuple:9:0:9:89::Assert called on a populated tuple. Did you mean 'assert x,y'?:HIGH
+assert-on-tuple:11:0:11:26::Assert called on a populated tuple. Did you mean 'assert x,y'?:HIGH
diff --git a/tests/functional/b/bad_except_order.txt b/tests/functional/b/bad_except_order.txt
index c6e6b4471..70443408f 100644
--- a/tests/functional/b/bad_except_order.txt
+++ b/tests/functional/b/bad_except_order.txt
@@ -1,5 +1,5 @@
-bad-except-order:9:7:9:16::Bad except clauses order (Exception is an ancestor class of TypeError):UNDEFINED
-bad-except-order:16:7:16:17::Bad except clauses order (LookupError is an ancestor class of IndexError):UNDEFINED
-bad-except-order:23:7:23:38::Bad except clauses order (LookupError is an ancestor class of IndexError):UNDEFINED
-bad-except-order:23:7:23:38::Bad except clauses order (NameError is an ancestor class of UnboundLocalError):UNDEFINED
-bad-except-order:26:0:31:8::Bad except clauses order (empty except clause should always appear last):UNDEFINED
+bad-except-order:9:7:9:16::Bad except clauses order (Exception is an ancestor class of TypeError):INFERENCE
+bad-except-order:16:7:16:17::Bad except clauses order (LookupError is an ancestor class of IndexError):INFERENCE
+bad-except-order:23:7:23:38::Bad except clauses order (LookupError is an ancestor class of IndexError):INFERENCE
+bad-except-order:23:7:23:38::Bad except clauses order (NameError is an ancestor class of UnboundLocalError):INFERENCE
+bad-except-order:26:0:31:8::Bad except clauses order (empty except clause should always appear last):HIGH
diff --git a/tests/functional/b/bad_exception_cause.py b/tests/functional/b/bad_exception_cause.py
index fd9a9cca0..8d8db3677 100644
--- a/tests/functional/b/bad_exception_cause.py
+++ b/tests/functional/b/bad_exception_cause.py
@@ -28,4 +28,4 @@ def function():
try:
pass
except function as exc: # [catching-non-exception]
- raise Exception from exc # [bad-exception-cause]
+ raise Exception from exc # [bad-exception-cause, broad-exception-raised]
diff --git a/tests/functional/b/bad_exception_cause.txt b/tests/functional/b/bad_exception_cause.txt
index ef8ce8831..3aa50fa37 100644
--- a/tests/functional/b/bad_exception_cause.txt
+++ b/tests/functional/b/bad_exception_cause.txt
@@ -3,3 +3,4 @@ bad-exception-cause:16:4:16:34:test:Exception cause set to something which is no
bad-exception-cause:22:4:22:36:test:Exception cause set to something which is not an exception, nor None:INFERENCE
catching-non-exception:30:7:30:15::"Catching an exception which doesn't inherit from Exception: function":UNDEFINED
bad-exception-cause:31:4:31:28::Exception cause set to something which is not an exception, nor None:INFERENCE
+broad-exception-raised:31:4:31:28::"Raising too general exception: Exception":INFERENCE
diff --git a/tests/functional/b/bad_reversed_sequence_py37.txt b/tests/functional/b/bad_reversed_sequence_py37.txt
index d87c84690..6fbbd2c59 100644
--- a/tests/functional/b/bad_reversed_sequence_py37.txt
+++ b/tests/functional/b/bad_reversed_sequence_py37.txt
@@ -1,2 +1,2 @@
-bad-reversed-sequence:5:::The first reversed() argument is not a sequence
-bad-reversed-sequence:12:::The first reversed() argument is not a sequence
+bad-reversed-sequence:5:0:5:26::The first reversed() argument is not a sequence:UNDEFINED
+bad-reversed-sequence:12:0:12:39::The first reversed() argument is not a sequence:UNDEFINED
diff --git a/tests/functional/b/bad_thread_instantiation.py b/tests/functional/b/bad_thread_instantiation.py
index e7e02eaed..3c9aa5e55 100644
--- a/tests/functional/b/bad_thread_instantiation.py
+++ b/tests/functional/b/bad_thread_instantiation.py
@@ -1,8 +1,24 @@
-# pylint: disable=missing-docstring
+# pylint: disable=missing-docstring, redundant-keyword-arg, invalid-name, line-too-long
import threading
threading.Thread(lambda: None).run() # [bad-thread-instantiation]
threading.Thread(None, lambda: None)
+threading.Thread(lambda: None, group=None) # [bad-thread-instantiation]
+threading.Thread() # [bad-thread-instantiation]
+
threading.Thread(group=None, target=lambda: None).run()
-threading.Thread() # [bad-thread-instantiation]
+threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
+threading.Thread(None, None, "name")
+
+def thread_target(n):
+ print(n ** 2)
+
+
+thread = threading.Thread(thread_target, args=(10,)) # [bad-thread-instantiation]
+
+
+kw = {'target_typo': lambda x: x}
+threading.Thread(None, **kw) # [unexpected-keyword-arg, bad-thread-instantiation]
+
+threading.Thread(None, target_typo=lambda x: x) # [unexpected-keyword-arg, bad-thread-instantiation]
diff --git a/tests/functional/b/bad_thread_instantiation.txt b/tests/functional/b/bad_thread_instantiation.txt
index e969a2473..91358d30a 100644
--- a/tests/functional/b/bad_thread_instantiation.txt
+++ b/tests/functional/b/bad_thread_instantiation.txt
@@ -1,2 +1,8 @@
-bad-thread-instantiation:5:0:5:30::threading.Thread needs the target function:UNDEFINED
-bad-thread-instantiation:8:0:8:18::threading.Thread needs the target function:UNDEFINED
+bad-thread-instantiation:5:0:5:30::threading.Thread needs the target function:HIGH
+bad-thread-instantiation:7:0:7:42::threading.Thread needs the target function:HIGH
+bad-thread-instantiation:8:0:8:18::threading.Thread needs the target function:HIGH
+bad-thread-instantiation:18:9:18:52::threading.Thread needs the target function:HIGH
+bad-thread-instantiation:22:0:22:28::threading.Thread needs the target function:HIGH
+unexpected-keyword-arg:22:0:22:28::Unexpected keyword argument 'target_typo' in constructor call:UNDEFINED
+bad-thread-instantiation:24:0:24:47::threading.Thread needs the target function:HIGH
+unexpected-keyword-arg:24:0:24:47::Unexpected keyword argument 'target_typo' in constructor call:UNDEFINED
diff --git a/tests/functional/b/bare_except.txt b/tests/functional/b/bare_except.txt
index 584f1be6d..7957bc144 100644
--- a/tests/functional/b/bare_except.txt
+++ b/tests/functional/b/bare_except.txt
@@ -1 +1 @@
-bare-except:5:0:6:8::No exception type(s) specified:UNDEFINED
+bare-except:5:0:6:8::No exception type(s) specified:HIGH
diff --git a/tests/functional/b/boolean_datetime.py b/tests/functional/b/boolean_datetime.py
new file mode 100644
index 000000000..cde355c01
--- /dev/null
+++ b/tests/functional/b/boolean_datetime.py
@@ -0,0 +1,15 @@
+"""Test boolean-datetime
+
+'py-version' needs to be set to <= '3.5'.
+"""
+import datetime
+
+if datetime.time(0, 0, 0): # [boolean-datetime]
+ print("datetime.time(0,0,0) is not a bug!")
+else:
+ print("datetime.time(0,0,0) is a bug!")
+
+if datetime.time(0, 0, 1): # [boolean-datetime]
+ print("datetime.time(0,0,1) is not a bug!")
+else:
+ print("datetime.time(0,0,1) is a bug!")
diff --git a/tests/functional/b/boolean_datetime.rc b/tests/functional/b/boolean_datetime.rc
new file mode 100644
index 000000000..068be2d4c
--- /dev/null
+++ b/tests/functional/b/boolean_datetime.rc
@@ -0,0 +1,2 @@
+[MAIN]
+py-version=3.4
diff --git a/tests/functional/b/boolean_datetime.txt b/tests/functional/b/boolean_datetime.txt
new file mode 100644
index 000000000..316453a6e
--- /dev/null
+++ b/tests/functional/b/boolean_datetime.txt
@@ -0,0 +1,2 @@
+boolean-datetime:7:3:7:25::Using datetime.time in a boolean context.:UNDEFINED
+boolean-datetime:12:3:12:25::Using datetime.time in a boolean context.:UNDEFINED
diff --git a/tests/functional/b/broad_except.py b/tests/functional/b/broad_except.py
deleted file mode 100644
index b38b6f8dc..000000000
--- a/tests/functional/b/broad_except.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# pylint: disable=missing-docstring
-__revision__ = 0
-
-try:
- __revision__ += 1
-except Exception: # [broad-except]
- print('error')
-
-
-try:
- __revision__ += 1
-except BaseException: # [broad-except]
- print('error')
diff --git a/tests/functional/b/broad_except.txt b/tests/functional/b/broad_except.txt
deleted file mode 100644
index 9a795d3c6..000000000
--- a/tests/functional/b/broad_except.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-broad-except:6:7:6:16::Catching too general exception Exception:UNDEFINED
-broad-except:12:7:12:20::Catching too general exception BaseException:UNDEFINED
diff --git a/tests/functional/b/broad_exception_caught.py b/tests/functional/b/broad_exception_caught.py
new file mode 100644
index 000000000..0a69a7015
--- /dev/null
+++ b/tests/functional/b/broad_exception_caught.py
@@ -0,0 +1,39 @@
+# pylint: disable=missing-docstring
+__revision__ = 0
+
+class CustomBroadException(Exception):
+ pass
+
+
+class CustomNarrowException(CustomBroadException):
+ pass
+
+
+try:
+ __revision__ += 1
+except Exception: # [broad-exception-caught]
+ print('error')
+
+
+try:
+ __revision__ += 1
+except BaseException: # [broad-exception-caught]
+ print('error')
+
+
+try:
+ __revision__ += 1
+except ValueError:
+ print('error')
+
+
+try:
+ __revision__ += 1
+except CustomBroadException: # [broad-exception-caught]
+ print('error')
+
+
+try:
+ __revision__ += 1
+except CustomNarrowException:
+ print('error')
diff --git a/tests/functional/b/broad_exception_caught.rc b/tests/functional/b/broad_exception_caught.rc
new file mode 100644
index 000000000..e0e1a7b6c
--- /dev/null
+++ b/tests/functional/b/broad_exception_caught.rc
@@ -0,0 +1,4 @@
+[EXCEPTIONS]
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception,
+ functional.b.broad_exception_caught.CustomBroadException
diff --git a/tests/functional/b/broad_exception_caught.txt b/tests/functional/b/broad_exception_caught.txt
new file mode 100644
index 000000000..386423b63
--- /dev/null
+++ b/tests/functional/b/broad_exception_caught.txt
@@ -0,0 +1,3 @@
+broad-exception-caught:14:7:14:16::Catching too general exception Exception:INFERENCE
+broad-exception-caught:20:7:20:20::Catching too general exception BaseException:INFERENCE
+broad-exception-caught:32:7:32:27::Catching too general exception CustomBroadException:INFERENCE
diff --git a/tests/functional/b/broad_exception_raised.py b/tests/functional/b/broad_exception_raised.py
new file mode 100644
index 000000000..c6ce64b46
--- /dev/null
+++ b/tests/functional/b/broad_exception_raised.py
@@ -0,0 +1,52 @@
+# pylint: disable=missing-docstring, unreachable
+
+ExceptionAlias = Exception
+
+class CustomBroadException(Exception):
+ pass
+
+
+class CustomNarrowException(CustomBroadException):
+ pass
+
+
+def exploding_apple(apple):
+ print(f"{apple} is about to explode")
+ raise Exception("{apple} exploded !") # [broad-exception-raised]
+
+
+def raise_and_catch():
+ try:
+ raise Exception("Oh No!!") # [broad-exception-raised]
+ except Exception as ex: # [broad-exception-caught]
+ print(ex)
+
+
+def raise_catch_reraise():
+ try:
+ exploding_apple("apple")
+ except Exception as ex:
+ print(ex)
+ raise ex
+
+
+def raise_catch_raise():
+ try:
+ exploding_apple("apple")
+ except Exception as ex:
+ print(ex)
+ raise Exception() from None # [broad-exception-raised]
+
+
+def raise_catch_raise_using_alias():
+ try:
+ exploding_apple("apple")
+ except Exception as ex:
+ print(ex)
+ raise ExceptionAlias() from None # [broad-exception-raised]
+
+raise Exception() # [broad-exception-raised]
+raise BaseException() # [broad-exception-raised]
+raise CustomBroadException() # [broad-exception-raised]
+raise IndexError from None
+raise CustomNarrowException() from None
diff --git a/tests/functional/b/broad_exception_raised.rc b/tests/functional/b/broad_exception_raised.rc
new file mode 100644
index 000000000..4f85d2933
--- /dev/null
+++ b/tests/functional/b/broad_exception_raised.rc
@@ -0,0 +1,4 @@
+[EXCEPTIONS]
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception,
+ functional.b.broad_exception_raised.CustomBroadException
diff --git a/tests/functional/b/broad_exception_raised.txt b/tests/functional/b/broad_exception_raised.txt
new file mode 100644
index 000000000..1e27b23f9
--- /dev/null
+++ b/tests/functional/b/broad_exception_raised.txt
@@ -0,0 +1,8 @@
+broad-exception-raised:15:4:15:41:exploding_apple:"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:20:8:20:34:raise_and_catch:"Raising too general exception: Exception":INFERENCE
+broad-exception-caught:21:11:21:20:raise_and_catch:Catching too general exception Exception:INFERENCE
+broad-exception-raised:38:8:38:35:raise_catch_raise:"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:46:8:46:40:raise_catch_raise_using_alias:"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:48:0:48:17::"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:49:0:49:21::"Raising too general exception: BaseException":INFERENCE
+broad-exception-raised:50:0:50:28::"Raising too general exception: CustomBroadException":INFERENCE
diff --git a/tests/functional/c/class_members_py30.py b/tests/functional/c/class_members_py30.py
index 0d65331f9..4566ff44e 100644
--- a/tests/functional/c/class_members_py30.py
+++ b/tests/functional/c/class_members_py30.py
@@ -37,7 +37,7 @@ class TestMetaclass(metaclass=ABCMeta):
class Metaclass(type):
""" metaclass """
@classmethod
- def test(cls):
+ def test(mcs):
""" classmethod """
class UsingMetaclass(metaclass=Metaclass):
diff --git a/tests/functional/c/comparison_with_callable.py b/tests/functional/c/comparison_with_callable.py
index 1e8ea5d90..7006d4960 100644
--- a/tests/functional/c/comparison_with_callable.py
+++ b/tests/functional/c/comparison_with_callable.py
@@ -1,4 +1,4 @@
-# pylint: disable = disallowed-name, missing-docstring, useless-return, invalid-name, line-too-long, comparison-of-constants
+# pylint: disable = disallowed-name, missing-docstring, useless-return, invalid-name, line-too-long, comparison-of-constants, broad-exception-raised
def foo():
return None
diff --git a/tests/functional/c/consider/consider_iterating_dictionary.py b/tests/functional/c/consider/consider_iterating_dictionary.py
index fdb76e869..8c75b4e3e 100644
--- a/tests/functional/c/consider/consider_iterating_dictionary.py
+++ b/tests/functional/c/consider/consider_iterating_dictionary.py
@@ -92,3 +92,25 @@ class AClass:
print("a" in another_metadata.keys()) # [consider-iterating-dictionary]
return inner_function()
return InnerClass().another_function()
+
+a_dict = {"a": 1, "b": 2, "c": 3}
+a_set = {"c", "d"}
+
+# Test bitwise operations. These should not raise msg because removing `.keys()`
+# either gives error or ends in a different result
+print(a_dict.keys() | a_set)
+
+if "a" in a_dict.keys() | a_set:
+ pass
+
+if "a" in a_dict.keys() & a_set:
+ pass
+
+if 1 in a_dict.keys() ^ [1, 2]:
+ pass
+
+if "a" in a_dict.keys() or a_set: # [consider-iterating-dictionary]
+ pass
+
+if "a" in a_dict.keys() and a_set: # [consider-iterating-dictionary]
+ pass
diff --git a/tests/functional/c/consider/consider_iterating_dictionary.txt b/tests/functional/c/consider/consider_iterating_dictionary.txt
index f251fa286..5190e7789 100644
--- a/tests/functional/c/consider/consider_iterating_dictionary.txt
+++ b/tests/functional/c/consider/consider_iterating_dictionary.txt
@@ -1,26 +1,28 @@
-consider-iterating-dictionary:25:16:25:25::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:26:16:26:25::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:27:16:27:25::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:28:21:28:30::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:29:24:29:33::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:30:24:30:33::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:31:24:31:33::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:32:29:32:38::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:33:11:33:20::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:38:24:38:35::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:38:55:38:66::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:39:31:39:42::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:39:61:39:72::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:40:30:40:41::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:40:60:40:71::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:43:8:43:21::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:45:8:45:17::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:65:11:65:20::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:73:19:73:34::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:75:14:75:29::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:77:15:77:30::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:79:10:79:25::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:89:42:89:65:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:90:37:90:60:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:91:38:91:61:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
-consider-iterating-dictionary:92:33:92:56:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
+consider-iterating-dictionary:25:16:25:25::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:26:16:26:25::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:27:16:27:25::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:28:21:28:30::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:29:24:29:33::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:30:24:30:33::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:31:24:31:33::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:32:29:32:38::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:33:11:33:20::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:38:24:38:35::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:38:55:38:66::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:39:31:39:42::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:39:61:39:72::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:40:30:40:41::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:40:60:40:71::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:43:8:43:21::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:45:8:45:17::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:65:11:65:20::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:73:19:73:34::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:75:14:75:29::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:77:15:77:30::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:79:10:79:25::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:89:42:89:65:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:90:37:90:60:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:91:38:91:61:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:92:33:92:56:AClass.a_function.InnerClass.another_function.inner_function:Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:112:10:112:23::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+consider-iterating-dictionary:115:10:115:23::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
diff --git a/tests/functional/c/consider/consider_join.py b/tests/functional/c/consider/consider_join.py
index 24cd6dd49..9f785b64d 100644
--- a/tests/functional/c/consider/consider_join.py
+++ b/tests/functional/c/consider/consider_join.py
@@ -17,6 +17,33 @@ another_result = result = ''
for number in ['1', '2', '3']:
result += number # [consider-using-join]
+result = 'a'
+for number in ['1', '2', '3']:
+ result += f'b{number}' # [consider-using-join]
+assert result == 'ab1b2b3'
+assert result == 'b'.join(['a', '1', '2', '3'])
+
+result = 'a'
+for number in ['1', '2', '3']:
+ result += f'{number}c' # [consider-using-join]
+assert result == 'a1c2c3c'
+assert result == 'a' + 'c'.join(['1', '2', '3']) + 'c'
+
+result = 'a'
+for number in ['1', '2', '3']:
+ result += f'b{number}c' # [consider-using-join]
+assert result == 'ab1cb2cb3c'
+assert result == 'ab' + 'cb'.join(['1', '2', '3']) + 'c'
+
+result = ''
+for number in ['1', '2', '3']:
+ result += number # [consider-using-join]
+
+result = ''
+for number in ['1', '2', '3']:
+ result += f"{number}, " # [consider-using-join]
+result = result[:-2]
+
result = 0 # result is not a string
for number in ['1', '2', '3']:
result += number
@@ -124,3 +151,11 @@ for number in ['1']:
result['context'] = 0
for number in ['1']:
result['context'] += 24
+
+result = ''
+for number in ['1', '2', '3']:
+ result += f' {result}' # f-string contains wrong name
+
+result = ''
+for number in ['1', '2', '3']:
+ result += f' {number} {number} {number} ' # f-string contains several names
diff --git a/tests/functional/c/consider/consider_join.txt b/tests/functional/c/consider/consider_join.txt
index baea768be..fa9b427e3 100644
--- a/tests/functional/c/consider/consider_join.txt
+++ b/tests/functional/c/consider/consider_join.txt
@@ -2,10 +2,15 @@ consider-using-join:6:4:6:20::Consider using str.join(sequence) for concatenatin
consider-using-join:10:4:10:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
consider-using-join:14:4:14:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
consider-using-join:18:4:18:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:58:4:58:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:62:4:62:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:66:4:66:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:71:4:71:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:75:4:75:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:79:4:79:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
-consider-using-join:110:31:110:47::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:22:4:22:26::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:28:4:28:26::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:34:4:34:27::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:40:4:40:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:44:4:44:27::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:85:4:85:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:89:4:89:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:93:4:93:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:98:4:98:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:102:4:102:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:106:4:106:20::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
+consider-using-join:137:31:137:47::Consider using str.join(sequence) for concatenating strings from an iterable:UNDEFINED
diff --git a/tests/functional/c/consider/consider_using_dict_items.txt b/tests/functional/c/consider/consider_using_dict_items.txt
index d43c0f0cf..280ffecf3 100644
--- a/tests/functional/c/consider/consider_using_dict_items.txt
+++ b/tests/functional/c/consider/consider_using_dict_items.txt
@@ -3,7 +3,7 @@ consider-using-dict-items:9:4:10:30:bad:Consider iterating with .items():UNDEFIN
consider-using-dict-items:21:4:22:35:another_bad:Consider iterating with .items():UNDEFINED
consider-using-dict-items:40:0:42:18::Consider iterating with .items():UNDEFINED
consider-using-dict-items:44:0:45:20::Consider iterating with .items():UNDEFINED
-consider-iterating-dictionary:47:10:47:23::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
+consider-iterating-dictionary:47:10:47:23::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
consider-using-dict-items:47:0:48:20::Consider iterating with .items():UNDEFINED
consider-using-dict-items:54:0:55:24::Consider iterating with .items():UNDEFINED
consider-using-dict-items:67:0:None:None::Consider iterating with .items():UNDEFINED
@@ -11,6 +11,6 @@ consider-using-dict-items:68:0:None:None::Consider iterating with .items():UNDEF
consider-using-dict-items:71:0:None:None::Consider iterating with .items():UNDEFINED
consider-using-dict-items:72:0:None:None::Consider iterating with .items():UNDEFINED
consider-using-dict-items:75:0:None:None::Consider iterating with .items():UNDEFINED
-consider-iterating-dictionary:86:25:86:42::Consider iterating the dictionary directly instead of calling .keys():UNDEFINED
+consider-iterating-dictionary:86:25:86:42::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
consider-using-dict-items:86:0:None:None::Consider iterating with .items():UNDEFINED
consider-using-dict-items:103:0:105:24::Consider iterating with .items():UNDEFINED
diff --git a/tests/functional/c/ctor_arguments.py b/tests/functional/c/ctor_arguments.py
index 954d9b8b2..d87732f1d 100644
--- a/tests/functional/c/ctor_arguments.py
+++ b/tests/functional/c/ctor_arguments.py
@@ -65,8 +65,8 @@ ClassNew(one=2) # [no-value-for-parameter,unexpected-keyword-arg]
class Metaclass(type):
- def __new__(cls, name, bases, namespace):
- return type.__new__(cls, name, bases, namespace)
+ def __new__(mcs, name, bases, namespace):
+ return type.__new__(mcs, name, bases, namespace)
def with_metaclass(meta, base=object):
"""Create a new type that can be used as a metaclass."""
diff --git a/tests/functional/d/dangerous_default_value.py b/tests/functional/d/dangerous_default_value.py
index 161eaceed..a7ef4c389 100644
--- a/tests/functional/d/dangerous_default_value.py
+++ b/tests/functional/d/dangerous_default_value.py
@@ -109,3 +109,14 @@ def function23(value=collections.UserList()): # [dangerous-default-value]
def function24(*, value=[]): # [dangerous-default-value]
"""dangerous default value in kwarg."""
return value
+
+
+class Clazz:
+ # pylint: disable=too-few-public-methods
+ def __init__( # [dangerous-default-value]
+ self,
+ arg: str = None,
+ *,
+ kk: dict = {},
+ ) -> None:
+ pass
diff --git a/tests/functional/d/dangerous_default_value.txt b/tests/functional/d/dangerous_default_value.txt
index 98d55c2b6..2376b8e29 100644
--- a/tests/functional/d/dangerous_default_value.txt
+++ b/tests/functional/d/dangerous_default_value.txt
@@ -20,3 +20,4 @@ dangerous-default-value:97:0:97:14:function21:Dangerous default value defaultdic
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
+dangerous-default-value:116:4:116:16:Clazz.__init__:Dangerous default value {} as argument:UNDEFINED
diff --git a/tests/functional/d/dataclass_kw_only.py b/tests/functional/d/dataclass_kw_only.py
new file mode 100644
index 000000000..9cc5a23bb
--- /dev/null
+++ b/tests/functional/d/dataclass_kw_only.py
@@ -0,0 +1,26 @@
+"""Test the behaviour of the kw_only keyword."""
+
+# pylint: disable=invalid-name
+
+from dataclasses import dataclass
+
+
+@dataclass(kw_only=True)
+class FooBar:
+ """Simple dataclass with a kw_only parameter."""
+
+ a: int
+ b: str
+
+
+@dataclass(kw_only=False)
+class BarFoo(FooBar):
+ """Simple dataclass with a negated kw_only parameter."""
+
+ c: int
+
+
+BarFoo(1, a=2, b="")
+BarFoo( # [missing-kwoa,missing-kwoa,redundant-keyword-arg,too-many-function-args]
+ 1, 2, c=2
+)
diff --git a/tests/functional/d/dataclass_kw_only.rc b/tests/functional/d/dataclass_kw_only.rc
new file mode 100644
index 000000000..68a8c8ef1
--- /dev/null
+++ b/tests/functional/d/dataclass_kw_only.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.10
diff --git a/tests/functional/d/dataclass_kw_only.txt b/tests/functional/d/dataclass_kw_only.txt
new file mode 100644
index 000000000..9954596f6
--- /dev/null
+++ b/tests/functional/d/dataclass_kw_only.txt
@@ -0,0 +1,4 @@
+missing-kwoa:24:0:26:1::Missing mandatory keyword argument 'a' in constructor call:UNDEFINED
+missing-kwoa:24:0:26:1::Missing mandatory keyword argument 'b' in constructor call:UNDEFINED
+redundant-keyword-arg:24:0:26:1::Argument 'c' passed by position and keyword in constructor call:UNDEFINED
+too-many-function-args:24:0:26:1::Too many positional arguments for constructor call:UNDEFINED
diff --git a/tests/functional/d/duplicate_except.txt b/tests/functional/d/duplicate_except.txt
index 8753f44b1..2bd56881a 100644
--- a/tests/functional/d/duplicate_except.txt
+++ b/tests/functional/d/duplicate_except.txt
@@ -1 +1 @@
-duplicate-except:9:11:9:21:main:Catching previously caught exception type ValueError:UNDEFINED
+duplicate-except:9:11:9:21:main:Catching previously caught exception type ValueError:INFERENCE
diff --git a/tests/functional/e/e1101_9588_base_attr_aug_assign.py b/tests/functional/e/e1101_9588_base_attr_aug_assign.py
index 9fe95fd4b..131dfc2c9 100644
--- a/tests/functional/e/e1101_9588_base_attr_aug_assign.py
+++ b/tests/functional/e/e1101_9588_base_attr_aug_assign.py
@@ -31,7 +31,7 @@ class NegativeClass(BaseClass):
def __init__(self):
"Ordinary assignment is OK."
BaseClass.__init__(self)
- self.e1101 = self.e1101 + 1
+ self.e1101 += self.e1101
def countup(self):
"No problem."
diff --git a/tests/functional/e/enum_subclasses.py b/tests/functional/e/enum_subclasses.py
index c8493da78..6ad453a4b 100644
--- a/tests/functional/e/enum_subclasses.py
+++ b/tests/functional/e/enum_subclasses.py
@@ -1,5 +1,5 @@
# pylint: disable=missing-docstring, invalid-name
-from enum import Enum, IntEnum, auto
+from enum import Enum, Flag, IntEnum, auto
class Issue1932(IntEnum):
@@ -60,6 +60,7 @@ class MyEnum(BaseEnum):
print(MyEnum.FOO.value)
+
class TestBase(Enum):
"""Adds a special method to enums."""
@@ -77,3 +78,18 @@ class TestEnum(TestBase):
test_enum = TestEnum.a
assert test_enum.hello_pylint() == test_enum.name
+
+
+# Check combinations of Flag members using the bitwise operators (&, |, ^, ~)
+# https://github.com/PyCQA/pylint/issues/7381
+class Colour(Flag):
+ NONE = 0
+ RED = 2
+ GREEN = 2
+ BLUE = 4
+
+
+and_expr = Colour.RED & Colour.GREEN & Colour.BLUE
+and_expr_with_complement = ~Colour.RED & ~Colour.GREEN & ~Colour.BLUE
+or_expr = Colour.RED | Colour.GREEN | Colour.BLUE
+xor_expr = Colour.RED ^ Colour.GREEN ^ Colour.BLUE
diff --git a/tests/functional/e/exception_is_binary_op.txt b/tests/functional/e/exception_is_binary_op.txt
index 4871a71d2..de371e42e 100644
--- a/tests/functional/e/exception_is_binary_op.txt
+++ b/tests/functional/e/exception_is_binary_op.txt
@@ -1,4 +1,4 @@
-binary-op-exception:5:0:6:20::"Exception to catch is the result of a binary ""or"" operation":UNDEFINED
-binary-op-exception:7:0:8:20::"Exception to catch is the result of a binary ""and"" operation":UNDEFINED
-binary-op-exception:9:0:10:20::"Exception to catch is the result of a binary ""or"" operation":UNDEFINED
-binary-op-exception:11:0:12:20::"Exception to catch is the result of a binary ""or"" operation":UNDEFINED
+binary-op-exception:5:0:6:20::"Exception to catch is the result of a binary ""or"" operation":HIGH
+binary-op-exception:7:0:8:20::"Exception to catch is the result of a binary ""and"" operation":HIGH
+binary-op-exception:9:0:10:20::"Exception to catch is the result of a binary ""or"" operation":HIGH
+binary-op-exception:11:0:12:20::"Exception to catch is the result of a binary ""or"" operation":HIGH
diff --git a/tests/functional/ext/bad_dunder/bad_dunder_name.py b/tests/functional/ext/bad_dunder/bad_dunder_name.py
new file mode 100644
index 000000000..48247aba0
--- /dev/null
+++ b/tests/functional/ext/bad_dunder/bad_dunder_name.py
@@ -0,0 +1,54 @@
+# pylint: disable=missing-module-docstring, missing-class-docstring,
+# pylint: disable=missing-function-docstring, unused-private-member
+
+
+class Apples:
+ __slots__ = ("a", "b")
+
+ def __hello__(self): # [bad-dunder-name]
+ # not one of the explicitly defined dunder name methods
+ print("hello")
+
+ def hello(self):
+ print("hello")
+
+ def __init__(self):
+ pass
+
+ def init(self):
+ # valid name even though someone could accidentally mean __init__
+ pass
+
+ def __init_(self): # [bad-dunder-name]
+ # author likely unintentionally misspelled the correct init dunder.
+ pass
+
+ def _init_(self): # [bad-dunder-name]
+ # author likely unintentionally misspelled the correct init dunder.
+ pass
+
+ def ___neg__(self): # [bad-dunder-name]
+ # author likely accidentally added an additional `_`
+ pass
+
+ def __inv__(self): # [bad-dunder-name]
+ # author likely meant to call the invert dunder method
+ pass
+
+ def __allowed__(self):
+ # user-configured allowed dunder name
+ pass
+
+ def _protected_method(self):
+ print("Protected")
+
+ def __private_method(self):
+ print("Private")
+
+ @property
+ def __doc__(self):
+ return "Docstring"
+
+
+def __increase_me__(val):
+ return val + 1
diff --git a/tests/functional/ext/bad_dunder/bad_dunder_name.rc b/tests/functional/ext/bad_dunder/bad_dunder_name.rc
new file mode 100644
index 000000000..0b449f3a3
--- /dev/null
+++ b/tests/functional/ext/bad_dunder/bad_dunder_name.rc
@@ -0,0 +1,4 @@
+[MAIN]
+load-plugins=pylint.extensions.dunder
+
+good-dunder-names = __allowed__,
diff --git a/tests/functional/ext/bad_dunder/bad_dunder_name.txt b/tests/functional/ext/bad_dunder/bad_dunder_name.txt
new file mode 100644
index 000000000..bb1d1e692
--- /dev/null
+++ b/tests/functional/ext/bad_dunder/bad_dunder_name.txt
@@ -0,0 +1,5 @@
+bad-dunder-name:8:4:8:17:Apples.__hello__:Bad or misspelled dunder method name __hello__.:HIGH
+bad-dunder-name:22:4:22:15:Apples.__init_:Bad or misspelled dunder method name __init_.:HIGH
+bad-dunder-name:26:4:26:14:Apples._init_:Bad or misspelled dunder method name _init_.:HIGH
+bad-dunder-name:30:4:30:16:Apples.___neg__:Bad or misspelled dunder method name ___neg__.:HIGH
+bad-dunder-name:34:4:34:15:Apples.__inv__:Bad or misspelled dunder method name __inv__.:HIGH
diff --git a/tests/functional/ext/code_style/cs_consider_using_augmented_assign.py b/tests/functional/ext/code_style/cs_consider_using_augmented_assign.py
new file mode 100644
index 000000000..e4af95686
--- /dev/null
+++ b/tests/functional/ext/code_style/cs_consider_using_augmented_assign.py
@@ -0,0 +1,77 @@
+"""Tests for consider-using-augmented-assign."""
+
+# pylint: disable=invalid-name,too-few-public-methods,import-error,consider-using-f-string
+
+from unknown import Unknown
+
+x = 1
+x = x + 1 # [consider-using-augmented-assign]
+x = 1 + x # [consider-using-augmented-assign]
+x, y = 1 + x, 2 + x
+# We don't warn on intricate expressions as we lack knowledge
+# of simplifying such expressions which is necessary to see
+# if they can become augmented
+x = 1 + x - 2
+x = 1 + x + 2
+
+# For anything other than a float or an int we only want to warn on
+# assignments where the 'itself' is on the left side of the assignment
+my_list = [2, 3, 4]
+my_list = [1] + my_list
+
+
+class MyClass:
+ """Simple base class."""
+
+ def __init__(self) -> None:
+ self.x = 1
+ self.x = self.x + 1 # [consider-using-augmented-assign]
+ self.x = 1 + self.x # [consider-using-augmented-assign]
+
+ x = 1 # [redefined-outer-name]
+ self.x = x
+
+
+instance = MyClass()
+
+x = instance.x + 1
+
+my_str = ""
+my_str = my_str + "foo" # [consider-using-augmented-assign]
+my_str = "foo" + my_str
+
+my_bytes = b""
+my_bytes = my_bytes + b"foo" # [consider-using-augmented-assign]
+my_bytes = b"foo" + my_bytes
+
+
+def return_str() -> str:
+ """Return a string."""
+ return ""
+
+
+# Currently we disregard all calls
+my_str = return_str() + my_str
+
+my_str = my_str % return_str()
+my_str = my_str % 1 # [consider-using-augmented-assign]
+my_str = my_str % (1, 2) # [consider-using-augmented-assign]
+my_str = "%s" % my_str
+my_str = return_str() % my_str
+my_str = Unknown % my_str
+my_str = my_str % Unknown # [consider-using-augmented-assign]
+
+x = x - 3 # [consider-using-augmented-assign]
+x = x * 3 # [consider-using-augmented-assign]
+x = x / 3 # [consider-using-augmented-assign]
+x = x // 3 # [consider-using-augmented-assign]
+x = x << 3 # [consider-using-augmented-assign]
+x = x >> 3 # [consider-using-augmented-assign]
+x = x % 3 # [consider-using-augmented-assign]
+x = x**3 # [consider-using-augmented-assign]
+x = x ^ 3 # [consider-using-augmented-assign]
+x = x & 3 # [consider-using-augmented-assign]
+x = x > 3
+x = x < 3
+x = x >= 3
+x = x <= 3
diff --git a/tests/functional/ext/code_style/cs_consider_using_augmented_assign.rc b/tests/functional/ext/code_style/cs_consider_using_augmented_assign.rc
new file mode 100644
index 000000000..584602294
--- /dev/null
+++ b/tests/functional/ext/code_style/cs_consider_using_augmented_assign.rc
@@ -0,0 +1,3 @@
+[MAIN]
+load-plugins=pylint.extensions.code_style
+enable=consider-using-augmented-assign
diff --git a/tests/functional/ext/code_style/cs_consider_using_augmented_assign.txt b/tests/functional/ext/code_style/cs_consider_using_augmented_assign.txt
new file mode 100644
index 000000000..1684953e9
--- /dev/null
+++ b/tests/functional/ext/code_style/cs_consider_using_augmented_assign.txt
@@ -0,0 +1,20 @@
+consider-using-augmented-assign:8:0:8:9::Use '+=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:9:0:9:9::Use '+=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:28:8:28:27:MyClass.__init__:Use '+=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:29:8:29:27:MyClass.__init__:Use '+=' to do an augmented assign directly:INFERENCE
+redefined-outer-name:31:8:31:9:MyClass.__init__:Redefining name 'x' from outer scope (line 7):UNDEFINED
+consider-using-augmented-assign:40:0:40:23::Use '+=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:44:0:44:28::Use '+=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:57:0:57:19::Use '%=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:58:0:58:24::Use '%=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:62:0:62:25::Use '%=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:64:0:64:9::Use '-=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:65:0:65:9::Use '*=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:66:0:66:9::Use '/=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:67:0:67:10::Use '//=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:68:0:68:10::Use '<<=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:69:0:69:10::Use '>>=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:70:0:70:9::Use '%=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:71:0:71:8::Use '**=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:72:0:72:9::Use '^=' to do an augmented assign directly:INFERENCE
+consider-using-augmented-assign:73:0:73:9::Use '&=' to do an augmented assign directly:INFERENCE
diff --git a/tests/functional/ext/code_style/cs_default.py b/tests/functional/ext/code_style/cs_default.py
new file mode 100644
index 000000000..bd4edab36
--- /dev/null
+++ b/tests/functional/ext/code_style/cs_default.py
@@ -0,0 +1,6 @@
+"""Test default configuration for code-style checker."""
+# pylint: disable=invalid-name
+
+# consider-using-augmented-assign is disabled by default
+x = 1
+x = x + 1
diff --git a/tests/functional/ext/code_style/cs_default.rc b/tests/functional/ext/code_style/cs_default.rc
new file mode 100644
index 000000000..8663ab085
--- /dev/null
+++ b/tests/functional/ext/code_style/cs_default.rc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.code_style
diff --git a/tests/functional/ext/comparetozero/comparetozero.py b/tests/functional/ext/comparetozero/compare_to_zero.py
index 29fd13994..6a14b8bc9 100644
--- a/tests/functional/ext/comparetozero/comparetozero.py
+++ b/tests/functional/ext/comparetozero/compare_to_zero.py
@@ -1,4 +1,4 @@
-# pylint: disable=literal-comparison,missing-docstring
+# pylint: disable=literal-comparison,missing-docstring, singleton-comparison
X = 123
Y = len('test')
@@ -6,15 +6,33 @@ Y = len('test')
if X is 0: # [compare-to-zero]
pass
+if X is False:
+ pass
+
if Y is not 0: # [compare-to-zero]
pass
+if Y is not False:
+ pass
+
if X == 0: # [compare-to-zero]
pass
+if X == False:
+ pass
+
+if 0 == Y: # [compare-to-zero]
+ pass
+
if Y != 0: # [compare-to-zero]
pass
+if 0 != X: # [compare-to-zero]
+ pass
+
+if Y != False:
+ pass
+
if X > 0:
pass
diff --git a/tests/functional/ext/comparetozero/comparetozero.rc b/tests/functional/ext/comparetozero/compare_to_zero.rc
index 70c6171b5..70c6171b5 100644
--- a/tests/functional/ext/comparetozero/comparetozero.rc
+++ b/tests/functional/ext/comparetozero/compare_to_zero.rc
diff --git a/tests/functional/ext/comparetozero/compare_to_zero.txt b/tests/functional/ext/comparetozero/compare_to_zero.txt
new file mode 100644
index 000000000..a413a3268
--- /dev/null
+++ b/tests/functional/ext/comparetozero/compare_to_zero.txt
@@ -0,0 +1,6 @@
+compare-to-zero:6:3:6:9::"""X is 0"" can be simplified to ""not X"" as 0 is falsey":HIGH
+compare-to-zero:12:3:12:13::"""Y is not 0"" can be simplified to ""Y"" as 0 is falsey":HIGH
+compare-to-zero:18:3:18:9::"""X == 0"" can be simplified to ""not X"" as 0 is falsey":HIGH
+compare-to-zero:24:3:24:9::"""0 == Y"" can be simplified to ""not Y"" as 0 is falsey":HIGH
+compare-to-zero:27:3:27:9::"""Y != 0"" can be simplified to ""Y"" as 0 is falsey":HIGH
+compare-to-zero:30:3:30:9::"""0 != X"" can be simplified to ""X"" as 0 is falsey":HIGH
diff --git a/tests/functional/ext/comparetozero/comparetozero.txt b/tests/functional/ext/comparetozero/comparetozero.txt
deleted file mode 100644
index 34f76c94e..000000000
--- a/tests/functional/ext/comparetozero/comparetozero.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-compare-to-zero:6:3:6:9::Avoid comparisons to zero:UNDEFINED
-compare-to-zero:9:3:9:13::Avoid comparisons to zero:UNDEFINED
-compare-to-zero:12:3:12:9::Avoid comparisons to zero:UNDEFINED
-compare-to-zero:15:3:15:9::Avoid comparisons to zero:UNDEFINED
diff --git a/tests/functional/ext/dict_init_mutate.py b/tests/functional/ext/dict_init_mutate.py
new file mode 100644
index 000000000..f3372bd7e
--- /dev/null
+++ b/tests/functional/ext/dict_init_mutate.py
@@ -0,0 +1,38 @@
+"""Example cases for dict-init-mutate"""
+# pylint: disable=use-dict-literal, invalid-name
+
+base = {}
+
+fruits = {}
+for fruit in ["apple", "orange"]:
+ fruits[fruit] = 1
+ fruits[fruit] += 1
+
+count = 10
+fruits = {"apple": 1}
+fruits["apple"] += count
+
+config = {} # [dict-init-mutate]
+config['pwd'] = 'hello'
+
+config = {} # [dict-init-mutate]
+config['dir'] = 'bin'
+config['user'] = 'me'
+config['workers'] = 5
+print(config)
+
+config = {} # Not flagging calls to update for now
+config.update({"dir": "bin"})
+
+config = {} # [dict-init-mutate]
+config['options'] = {} # Identifying nested assignment not supporting this yet.
+config['options']['debug'] = False
+config['options']['verbose'] = True
+
+
+config = {}
+def update_dict(di):
+ """Update a dictionary"""
+ di["one"] = 1
+
+update_dict(config)
diff --git a/tests/functional/ext/dict_init_mutate.rc b/tests/functional/ext/dict_init_mutate.rc
new file mode 100644
index 000000000..bbe6bd1f7
--- /dev/null
+++ b/tests/functional/ext/dict_init_mutate.rc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.dict_init_mutate,
diff --git a/tests/functional/ext/dict_init_mutate.txt b/tests/functional/ext/dict_init_mutate.txt
new file mode 100644
index 000000000..c3702491f
--- /dev/null
+++ b/tests/functional/ext/dict_init_mutate.txt
@@ -0,0 +1,3 @@
+dict-init-mutate:15:0:15:11::Declare all known key/values when initializing the dictionary.:HIGH
+dict-init-mutate:18:0:18:11::Declare all known key/values when initializing the dictionary.:HIGH
+dict-init-mutate:27:0:27:11::Declare all known key/values when initializing the dictionary.:HIGH
diff --git a/tests/functional/ext/docparams/docparams.py b/tests/functional/ext/docparams/docparams.py
index 3798ee3ac..051bdd24d 100644
--- a/tests/functional/ext/docparams/docparams.py
+++ b/tests/functional/ext/docparams/docparams.py
@@ -1,5 +1,5 @@
"""Fixture for testing missing documentation in docparams."""
-
+# pylint: disable=broad-exception-raised
def _private_func1( # [missing-return-doc, missing-return-type-doc, missing-any-param-doc]
param1,
diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.rc b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.rc
index 671820dbc..2900924f8 100644
--- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.rc
+++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Sphinx.rc
@@ -3,5 +3,6 @@ load-plugins = pylint.extensions.docparams
[BASIC]
accept-no-param-doc=no
+accept-no-raise-doc=no
no-docstring-rgx=^$
docstring-min-length: -1
diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc.txt b/tests/functional/ext/docparams/raise/missing_raises_doc.txt
index 38b0437fc..d770776ef 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:HIGH
missing-raises-doc:28:0:28:45:test_ignores_returns_from_inner_functions:"""RuntimeError"" not documented as being raised":HIGH
unreachable:42:4:42:25:test_ignores_returns_from_inner_functions:Unreachable code:HIGH
-raising-bad-type:54:4:54:22:test_ignores_returns_use_only_names:Raising int while only classes or instances are allowed:UNDEFINED
+raising-bad-type:54:4:54:22:test_ignores_returns_use_only_names:Raising int while only classes or instances are allowed:INFERENCE
diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_options.py b/tests/functional/ext/docparams/raise/missing_raises_doc_options.py
new file mode 100644
index 000000000..eb3cd3ac3
--- /dev/null
+++ b/tests/functional/ext/docparams/raise/missing_raises_doc_options.py
@@ -0,0 +1,15 @@
+"""Minimal example where a W9006 message is displayed even if the
+accept-no-raise-doc option is set to True.
+
+Requires at least one matching section (`Docstring.matching_sections`).
+
+Taken from https://github.com/PyCQA/pylint/issues/7208
+"""
+
+
+def w9006issue(dummy: int):
+ """Sample function.
+
+ :param dummy: Unused
+ """
+ raise AssertionError()
diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_options.rc b/tests/functional/ext/docparams/raise/missing_raises_doc_options.rc
new file mode 100644
index 000000000..b36bb87a3
--- /dev/null
+++ b/tests/functional/ext/docparams/raise/missing_raises_doc_options.rc
@@ -0,0 +1,5 @@
+[MAIN]
+load-plugins = pylint.extensions.docparams
+
+[BASIC]
+accept-no-raise-doc = yes
diff --git a/tests/functional/ext/emptystring/empty_string_comparison.py b/tests/functional/ext/emptystring/empty_string_comparison.py
index c6dcf8ea8..b61caeff6 100644
--- a/tests/functional/ext/emptystring/empty_string_comparison.py
+++ b/tests/functional/ext/emptystring/empty_string_comparison.py
@@ -14,3 +14,9 @@ if X == "": # [compare-to-empty-string]
if Y != '': # [compare-to-empty-string]
pass
+
+if "" == Y: # [compare-to-empty-string]
+ pass
+
+if '' != X: # [compare-to-empty-string]
+ pass
diff --git a/tests/functional/ext/emptystring/empty_string_comparison.txt b/tests/functional/ext/emptystring/empty_string_comparison.txt
index 5b259bc46..be9c91bc5 100644
--- a/tests/functional/ext/emptystring/empty_string_comparison.txt
+++ b/tests/functional/ext/emptystring/empty_string_comparison.txt
@@ -1,4 +1,6 @@
-compare-to-empty-string:6:3:6:10::Avoid comparisons to empty string:UNDEFINED
-compare-to-empty-string:9:3:9:14::Avoid comparisons to empty string:UNDEFINED
-compare-to-empty-string:12:3:12:10::Avoid comparisons to empty string:UNDEFINED
-compare-to-empty-string:15:3:15:10::Avoid comparisons to empty string:UNDEFINED
+compare-to-empty-string:6:3:6:10::"""X is ''"" can be simplified to ""not X"" as an empty string is falsey":HIGH
+compare-to-empty-string:9:3:9:14::"""Y is not ''"" can be simplified to ""Y"" as an empty string is falsey":HIGH
+compare-to-empty-string:12:3:12:10::"""X == ''"" can be simplified to ""not X"" as an empty string is falsey":HIGH
+compare-to-empty-string:15:3:15:10::"""Y != ''"" can be simplified to ""Y"" as an empty string is falsey":HIGH
+compare-to-empty-string:18:3:18:10::"""'' == Y"" can be simplified to ""not Y"" as an empty string is falsey":HIGH
+compare-to-empty-string:21:3:21:10::"""'' != X"" can be simplified to ""X"" as an empty string is falsey":HIGH
diff --git a/tests/functional/ext/for_any_all/for_any_all.py b/tests/functional/ext/for_any_all/for_any_all.py
index 8b4c7275c..649739c37 100644
--- a/tests/functional/ext/for_any_all/for_any_all.py
+++ b/tests/functional/ext/for_any_all/for_any_all.py
@@ -1,4 +1,5 @@
"""Functional test"""
+# pylint: disable=missing-function-docstring, invalid-name
def any_even(items):
"""Return True if the list contains any even numbers"""
@@ -144,3 +145,94 @@ def is_from_decorator(node):
if parent in parent.selected_annotations:
return False
return False
+
+def optimized_any_with_break(split_lines, max_chars):
+ """False negative found in https://github.com/PyCQA/pylint/pull/7697"""
+ potential_line_length_warning = False
+ for line in split_lines: # [consider-using-any-or-all]
+ if len(line) > max_chars:
+ potential_line_length_warning = True
+ break
+ return potential_line_length_warning
+
+def optimized_any_without_break(split_lines, max_chars):
+ potential_line_length_warning = False
+ for line in split_lines: # [consider-using-any-or-all]
+ if len(line) > max_chars:
+ potential_line_length_warning = True
+ return potential_line_length_warning
+
+def print_line_without_break(split_lines, max_chars):
+ potential_line_length_warning = False
+ for line in split_lines:
+ print(line)
+ if len(line) > max_chars:
+ potential_line_length_warning = True
+ return potential_line_length_warning
+
+def print_line_without_reassign(split_lines, max_chars):
+ potential_line_length_warning = False
+ for line in split_lines:
+ if len(line) > max_chars:
+ print(line)
+ return potential_line_length_warning
+
+def multiple_flags(split_lines, max_chars):
+ potential_line_length_warning = False
+ for line in split_lines:
+ if len(line) > max_chars:
+ num = 1
+ print(num)
+ potential_line_length_warning = True
+ return potential_line_length_warning
+
+s = ["hi", "hello", "goodbye", None]
+
+flag = True
+for i, elem in enumerate(s):
+ if elem is None:
+ continue
+ cnt_s = cnt_t = 0
+ for j in range(i, len(s)):
+ if s[j] == elem:
+ cnt_s += 1
+ s[j] = None
+ Flag = False
+
+def with_elif(split_lines, max_chars):
+ """
+ Do not raise consider-using-any-or-all because the intent in this code
+ is to iterate over all the lines (not short-circuit) and see what
+ the last value would be.
+ """
+ last_longest_line = False
+ for line in split_lines:
+ if len(line) > max_chars:
+ last_longest_line = True
+ elif len(line) == max_chars:
+ last_longest_line = False
+ return last_longest_line
+
+def first_even(items):
+ """Return first even number"""
+ for item in items:
+ if item % 2 == 0:
+ return item
+ return None
+
+def even(items):
+ for item in items:
+ if item % 2 == 0:
+ return True
+ return None
+
+def iterate_leaves(leaves, current_node):
+ results = []
+
+ current_node.was_checked = True
+ for leaf in leaves:
+ if isinstance(leaf, bool):
+ current_node.was_checked = False
+ else:
+ results.append(leaf)
+ return results
diff --git a/tests/functional/ext/for_any_all/for_any_all.txt b/tests/functional/ext/for_any_all/for_any_all.txt
index bc09876e4..dca0ad3d3 100644
--- a/tests/functional/ext/for_any_all/for_any_all.txt
+++ b/tests/functional/ext/for_any_all/for_any_all.txt
@@ -1,12 +1,14 @@
-consider-using-any-or-all:5:4:7:23:any_even:`for` loop could be `any(item % 2 == 0 for item in items)`:UNDEFINED
-consider-using-any-or-all:12:4:14:24:all_even:`for` loop could be `all(item % 2 == 0 for item in items)`:UNDEFINED
-consider-using-any-or-all:19:4:21:23:any_uneven:`for` loop could be `not all(item % 2 == 0 for item in items)`:UNDEFINED
-consider-using-any-or-all:26:4:28:24:all_uneven:`for` loop could be `not any(item % 2 == 0 for item in items)`:UNDEFINED
-consider-using-any-or-all:33:4:35:23:is_from_string:`for` loop could be `any(isinstance(parent, str) for parent in item.parents())`:UNDEFINED
-consider-using-any-or-all:40:4:42:23:is_not_from_string:`for` loop could be `not all(isinstance(parent, str) for parent in item.parents())`:UNDEFINED
-consider-using-any-or-all:49:8:51:28:nested_check:`for` loop could be `not any(item in (1, 2, 3) for item in items)`:UNDEFINED
-consider-using-any-or-all:58:4:60:23:words_contains_word:`for` loop could be `any(word == 'word' for word in words)`:UNDEFINED
-consider-using-any-or-all:65:4:67:24:complicated_condition_check:`for` loop could be `not any(item % 2 == 0 and (item % 3 == 0 or item > 15) for item in items)`:UNDEFINED
-consider-using-any-or-all:72:4:77:23:is_from_decorator1:`for` loop could be `any(ancestor.name in ('Exception', 'BaseException') and ancestor.root().name == 'Exception' for ancestor in node)`:UNDEFINED
-consider-using-any-or-all:82:4:84:24:is_from_decorator2:`for` loop could be `all(item % 2 == 0 and (item % 3 == 0 or item > 15) for item in items)`:UNDEFINED
-consider-using-any-or-all:89:4:94:23:is_from_decorator3:`for` loop could be `not all(ancestor.name in ('Exception', 'BaseException') and ancestor.root().name == 'Exception' for ancestor in node)`:UNDEFINED
+consider-using-any-or-all:6:4:8:23:any_even:`for` loop could be `any(item % 2 == 0 for item in items)`:HIGH
+consider-using-any-or-all:13:4:15:24:all_even:`for` loop could be `all(item % 2 == 0 for item in items)`:HIGH
+consider-using-any-or-all:20:4:22:23:any_uneven:`for` loop could be `not all(item % 2 == 0 for item in items)`:HIGH
+consider-using-any-or-all:27:4:29:24:all_uneven:`for` loop could be `not any(item % 2 == 0 for item in items)`:HIGH
+consider-using-any-or-all:34:4:36:23:is_from_string:`for` loop could be `any(isinstance(parent, str) for parent in item.parents())`:HIGH
+consider-using-any-or-all:41:4:43:23:is_not_from_string:`for` loop could be `not all(isinstance(parent, str) for parent in item.parents())`:HIGH
+consider-using-any-or-all:50:8:52:28:nested_check:`for` loop could be `not any(item in (1, 2, 3) for item in items)`:HIGH
+consider-using-any-or-all:59:4:61:23:words_contains_word:`for` loop could be `any(word == 'word' for word in words)`:HIGH
+consider-using-any-or-all:66:4:68:24:complicated_condition_check:`for` loop could be `not any(item % 2 == 0 and (item % 3 == 0 or item > 15) for item in items)`:HIGH
+consider-using-any-or-all:73:4:78:23:is_from_decorator1:`for` loop could be `any(ancestor.name in ('Exception', 'BaseException') and ancestor.root().name == 'Exception' for ancestor in node)`:HIGH
+consider-using-any-or-all:83:4:85:24:is_from_decorator2:`for` loop could be `all(item % 2 == 0 and (item % 3 == 0 or item > 15) for item in items)`:HIGH
+consider-using-any-or-all:90:4:95:23:is_from_decorator3:`for` loop could be `not all(ancestor.name in ('Exception', 'BaseException') and ancestor.root().name == 'Exception' for ancestor in node)`:HIGH
+consider-using-any-or-all:152:4:155:17:optimized_any_with_break:`for` loop could be `not any(len(line) > max_chars for line in split_lines)`:HIGH
+consider-using-any-or-all:160:4:162:48:optimized_any_without_break:`for` loop could be `not any(len(line) > max_chars for line in split_lines)`:HIGH
diff --git a/tests/functional/ext/magic_value_comparison/magic_value_comparison.py b/tests/functional/ext/magic_value_comparison/magic_value_comparison.py
new file mode 100644
index 000000000..50c175af5
--- /dev/null
+++ b/tests/functional/ext/magic_value_comparison/magic_value_comparison.py
@@ -0,0 +1,33 @@
+"""
+Checks that magic values are not used in comparisons
+"""
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,import-error,wrong-import-position
+
+from enum import Enum
+
+
+class Christmas(Enum):
+ EVE = 25
+ DAY = 26
+ MONTH = 12
+
+
+var = 7
+if var > 5: # [magic-value-comparison]
+ pass
+
+if (var + 5) > 10: # [magic-value-comparison]
+ pass
+
+is_big = 100 < var # [magic-value-comparison]
+
+shouldnt_raise = 5 > 7 # [comparison-of-constants]
+shouldnt_raise = var == '__main__'
+shouldnt_raise = var == 1
+shouldnt_raise = var == 0
+shouldnt_raise = var == -1
+shouldnt_raise = var == True # [singleton-comparison]
+shouldnt_raise = var == False # [singleton-comparison]
+shouldnt_raise = var == None # [singleton-comparison]
+celebration_started = Christmas.EVE.value == Christmas.MONTH.value
+shouldnt_raise = var == ""
diff --git a/tests/functional/ext/magic_value_comparison/magic_value_comparison.rc b/tests/functional/ext/magic_value_comparison/magic_value_comparison.rc
new file mode 100644
index 000000000..f69139825
--- /dev/null
+++ b/tests/functional/ext/magic_value_comparison/magic_value_comparison.rc
@@ -0,0 +1,2 @@
+[MAIN]
+load-plugins=pylint.extensions.magic_value,
diff --git a/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt b/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt
new file mode 100644
index 000000000..63976ff68
--- /dev/null
+++ b/tests/functional/ext/magic_value_comparison/magic_value_comparison.txt
@@ -0,0 +1,7 @@
+magic-value-comparison:16:3:16:10::Consider using a named constant or an enum instead of '5'.:HIGH
+magic-value-comparison:19:3:19:17::Consider using a named constant or an enum instead of '10'.:HIGH
+magic-value-comparison:22:9:22:18::Consider using a named constant or an enum instead of '100'.:HIGH
+comparison-of-constants:24:17:24:22::"Comparison between constants: '5 > 7' has a constant value":HIGH
+singleton-comparison:29:17:29:28::Comparison 'var == True' should be 'var is True' if checking for the singleton value True, or 'bool(var)' if testing for truthiness:UNDEFINED
+singleton-comparison:30:17:30:29::Comparison 'var == False' should be 'var is False' if checking for the singleton value False, or 'not var' if testing for falsiness:UNDEFINED
+singleton-comparison:31:17:31:28::Comparison 'var == None' should be 'var is None':UNDEFINED
diff --git a/tests/functional/ext/mccabe/mccabe.py b/tests/functional/ext/mccabe/mccabe.py
index e3d11c5c8..92623cd1c 100644
--- a/tests/functional/ext/mccabe/mccabe.py
+++ b/tests/functional/ext/mccabe/mccabe.py
@@ -1,7 +1,7 @@
# pylint: disable=invalid-name,unnecessary-pass,no-else-return,useless-else-on-loop
# pylint: disable=undefined-variable,consider-using-sys-exit,unused-variable,too-many-return-statements
# pylint: disable=redefined-outer-name,using-constant-test,unused-argument
-# pylint: disable=broad-except, not-context-manager, no-method-argument, unspecified-encoding
+# pylint: disable=broad-except, not-context-manager, no-method-argument, unspecified-encoding, broad-exception-raised
"""Checks use of "too-complex" check"""
diff --git a/tests/functional/ext/set_membership/use_set_membership.py b/tests/functional/ext/set_membership/use_set_membership.py
index 50e07f4dd..7872d7f98 100644
--- a/tests/functional/ext/set_membership/use_set_membership.py
+++ b/tests/functional/ext/set_membership/use_set_membership.py
@@ -33,7 +33,7 @@ True == x in [1, 2, 3] # [use-set-for-membership] # noqa: E712
x in (1, "Hello World", False, None) # [use-set-for-membership]
x in (1, []) # List is not hashable
-if some_var:
+if x:
var2 = 2
else:
var2 = []
diff --git a/tests/functional/ext/typing/typing_broken_noreturn.py b/tests/functional/ext/typing/typing_broken_noreturn.py
index 4d10ed13a..e7b5643ae 100644
--- a/tests/functional/ext/typing/typing_broken_noreturn.py
+++ b/tests/functional/ext/typing/typing_broken_noreturn.py
@@ -1,10 +1,10 @@
"""
-'typing.NoReturn' is broken inside compond types for Python 3.7.0
+'typing.NoReturn' is broken inside compound 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
+# pylint: disable=missing-docstring, broad-exception-raised
import typing
from typing import TYPE_CHECKING, Callable, NoReturn, Union
diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.py b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py
index 4743750bc..e0ea7761b 100644
--- a/tests/functional/ext/typing/typing_broken_noreturn_future_import.py
+++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py
@@ -7,7 +7,7 @@ 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
+# pylint: disable=missing-docstring, broad-exception-raised
from __future__ import annotations
import typing
diff --git a/tests/functional/ext/typing/typing_broken_noreturn_py372.py b/tests/functional/ext/typing/typing_broken_noreturn_py372.py
index 4ff1a71b7..6bd31f069 100644
--- a/tests/functional/ext/typing/typing_broken_noreturn_py372.py
+++ b/tests/functional/ext/typing/typing_broken_noreturn_py372.py
@@ -6,7 +6,7 @@ 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
+# pylint: disable=missing-docstring, broad-exception-raised
import typing
from typing import TYPE_CHECKING, Callable, NoReturn, Union
diff --git a/tests/functional/f/first_arg.py b/tests/functional/f/first_arg.py
index d8007e144..ac5c6bcf6 100644
--- a/tests/functional/f/first_arg.py
+++ b/tests/functional/f/first_arg.py
@@ -31,7 +31,7 @@ class Meta(type):
pass
# C0205, metaclass classmethod
- def class1(cls):
+ def class1(mcs):
pass
class1 = classmethod(class1) # [no-classmethod-decorator]
diff --git a/tests/functional/f/first_arg.txt b/tests/functional/f/first_arg.txt
index 26aabd22e..3bfb8a0f5 100644
--- a/tests/functional/f/first_arg.txt
+++ b/tests/functional/f/first_arg.txt
@@ -2,8 +2,8 @@ bad-classmethod-argument:8:4:8:15:Obj.__new__:Class method __new__ should have '
no-classmethod-decorator:14:4:14:10:Obj:Consider using a decorator instead of calling classmethod:UNDEFINED
bad-classmethod-argument:16:4:16:14:Obj.class2:Class method class2 should have 'cls' as first argument:UNDEFINED
no-classmethod-decorator:18:4:18:10:Obj:Consider using a decorator instead of calling classmethod:UNDEFINED
-bad-mcs-classmethod-argument:23:4:23:15:Meta.__new__:Metaclass class method __new__ should have 'cls' as first argument:UNDEFINED
+bad-mcs-classmethod-argument:23:4:23:15:Meta.__new__:Metaclass class method __new__ should have 'mcs' as first argument:UNDEFINED
bad-mcs-method-argument:30:4:30:15:Meta.method2:Metaclass method method2 should have 'cls' as first argument:UNDEFINED
no-classmethod-decorator:36:4:36:10:Meta:Consider using a decorator instead of calling classmethod:UNDEFINED
-bad-mcs-classmethod-argument:38:4:38:14:Meta.class2:Metaclass class method class2 should have 'cls' as first argument:UNDEFINED
+bad-mcs-classmethod-argument:38:4:38:14:Meta.class2:Metaclass class method class2 should have 'mcs' as first argument:UNDEFINED
no-classmethod-decorator:40:4:40:10:Meta:Consider using a decorator instead of calling classmethod:UNDEFINED
diff --git a/tests/functional/g/generic_alias/generic_alias_collections.txt b/tests/functional/g/generic_alias/generic_alias_collections.txt
index 663b81abb..4abaa0338 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: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
+abstract-method:74:0:74:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedHashable':INFERENCE
+abstract-method:77:0:77:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedIterable':INFERENCE
+abstract-method:80:0:80:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:80:0:80:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:80:0:80:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:99:0:99:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:99:0:99:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:104:0:104:24:CustomAbstractCls2:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
+abstract-method:104:0:104:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
+abstract-method:106:0:106:26:CustomImplementation:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'CustomImplementation':INFERENCE
+abstract-method:106:0:106:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomImplementation':INFERENCE
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 84a217d2f..72104b4be 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:77:21: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 in child class 'DerivedHashable':INFERENCE
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: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
+abstract-method:102:0:102:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:102:0:102:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:107:0:107:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
unsubscriptable-object:107:48:107:72:CustomAbstractCls2:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED
-abstract-method:109:0:109:26: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 in child class 'CustomImplementation':INFERENCE
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 ee1407bdb..0dd989f2e 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:79:21: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 in child class 'DerivedHashable':INFERENCE
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: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
+abstract-method:104:0:104:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:104:0:104:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:109:0:109:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
unsubscriptable-object:109:48:109:72:CustomAbstractCls2:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED
-abstract-method:111:0:111:26: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 in child class 'CustomImplementation':INFERENCE
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 2bafe20ed..188039c60 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: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
+abstract-method:34:0:34:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedHashable':INFERENCE
+abstract-method:37:0:37:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedIterable':INFERENCE
+abstract-method:40:0:40:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:40:0:40:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:40:0:40:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedCollection':INFERENCE
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 06dbdc197..58b7e9860 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: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
+abstract-method:29:0:29:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedHashable':INFERENCE
+abstract-method:32:0:32:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedIterable':INFERENCE
+abstract-method:35:0:35:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:35:0:35:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:35:0:35:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedCollection':INFERENCE
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 d481f7ac6..cbf46bfef 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:82:21: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 in child class 'DerivedHashable':INFERENCE
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,9 +47,9 @@ 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: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
+abstract-method:107:0:107:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:107:0:107:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:112:0:112:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
unsubscriptable-object:112:48:112:72:CustomAbstractCls2:Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED
-abstract-method:114:0:114:26: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 in child class 'CustomImplementation':INFERENCE
unsubscriptable-object:188:8:188:9:B:Value 'A' 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 d13f75fa7..b2123350c 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:53:13: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 in child class 'Derived':INFERENCE
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 114376f5e..c24c0f98b 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:55:13: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 in child class 'Derived':INFERENCE
diff --git a/tests/functional/g/generic_alias/generic_alias_typing.txt b/tests/functional/g/generic_alias/generic_alias_typing.txt
index f33f49f91..2a433cd24 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: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
+abstract-method:72:0:72:21:DerivedHashable:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedHashable':INFERENCE
+abstract-method:75:0:75:21:DerivedIterable:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedIterable':INFERENCE
+abstract-method:78:0:78:23:DerivedCollection:Method '__contains__' is abstract in class 'Container' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:78:0:78:23:DerivedCollection:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:78:0:78:23:DerivedCollection:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedCollection':INFERENCE
+abstract-method:100:0:100:21:DerivedMultiple:Method '__hash__' is abstract in class 'Hashable' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:100:0:100:21:DerivedMultiple:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'DerivedMultiple':INFERENCE
+abstract-method:105:0:105:24:CustomAbstractCls2:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
+abstract-method:105:0:105:24:CustomAbstractCls2:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomAbstractCls2':INFERENCE
+abstract-method:107:0:107:26:CustomImplementation:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'CustomImplementation':INFERENCE
+abstract-method:107:0:107:26:CustomImplementation:Method '__len__' is abstract in class 'Sized' but is not overridden in child class 'CustomImplementation':INFERENCE
+abstract-method:118:0:118:22:DerivedIterable2:Method '__iter__' is abstract in class 'Iterable' but is not overridden in child class 'DerivedIterable2':INFERENCE
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/implicit/implicit_str_concat.py b/tests/functional/i/implicit/implicit_str_concat.py
index 920b29258..7e28b4cc2 100644
--- a/tests/functional/i/implicit/implicit_str_concat.py
+++ b/tests/functional/i/implicit/implicit_str_concat.py
@@ -1,4 +1,4 @@
-# pylint: disable=invalid-name, missing-docstring, redundant-u-string-prefix, line-too-long
+# pylint: disable=invalid-name, missing-docstring, redundant-u-string-prefix, line-too-long, superfluous-parens
# Basic test with a list
TEST_LIST1 = ['a' 'b'] # [implicit-str-concat]
diff --git a/tests/functional/i/inconsistent/inconsistent_returns.py b/tests/functional/i/inconsistent/inconsistent_returns.py
index 08dde253e..c1183b288 100644
--- a/tests/functional/i/inconsistent/inconsistent_returns.py
+++ b/tests/functional/i/inconsistent/inconsistent_returns.py
@@ -91,13 +91,13 @@ def explicit_returns6(x, y, z):
def explicit_returns7(arg):
if arg < 0:
- arg = 2 * arg
+ arg *= 2
return 'below 0'
elif arg == 0:
print("Null arg")
return '0'
else:
- arg = 3 * arg
+ arg *= 3
return 'above 0'
def bug_1772():
@@ -184,7 +184,7 @@ def explicit_implicit_returns3(arg): # [inconsistent-return-statements]
def returns_missing_in_catched_exceptions(arg): # [inconsistent-return-statements]
try:
- arg = arg**2
+ arg **= arg
raise ValueError('test')
except ValueError:
print('ValueError')
diff --git a/tests/functional/i/invalid/invalid_all_format.py b/tests/functional/i/invalid/invalid_all_format.py
index 1d1c0b18f..10537c6fb 100644
--- a/tests/functional/i/invalid/invalid_all_format.py
+++ b/tests/functional/i/invalid/invalid_all_format.py
@@ -2,6 +2,6 @@
Tuples with one element MUST contain a comma! Otherwise it's a string.
"""
-__all__ = ("CONST") # [invalid-all-format]
+__all__ = ("CONST") # [invalid-all-format, superfluous-parens]
CONST = 42
diff --git a/tests/functional/i/invalid/invalid_all_format.txt b/tests/functional/i/invalid/invalid_all_format.txt
index 2ba8dc17f..2f6ac363b 100644
--- a/tests/functional/i/invalid/invalid_all_format.txt
+++ b/tests/functional/i/invalid/invalid_all_format.txt
@@ -1 +1,2 @@
invalid-all-format:5:11:None:None::Invalid format for __all__, must be tuple or list:UNDEFINED
+superfluous-parens:5:0:None:None::Unnecessary parens after '=' keyword:UNDEFINED
diff --git a/tests/functional/i/invalid/invalid_class_object.txt b/tests/functional/i/invalid/invalid_class_object.txt
index 793a5de69..96e4d42c8 100644
--- a/tests/functional/i/invalid/invalid_class_object.txt
+++ b/tests/functional/i/invalid/invalid_class_object.txt
@@ -1,5 +1,5 @@
-invalid-class-object:20:0:20:11::Invalid __class__ object:UNDEFINED
-invalid-class-object:21:0:21:11::Invalid __class__ object:UNDEFINED
-invalid-class-object:50:8:50:22:Pylint7429Good.class_defining_function_bad:Invalid __class__ object:UNDEFINED
-invalid-class-object:58:15:58:29:Pylint7429Good.class_defining_function_bad_inverted:Invalid __class__ object:UNDEFINED
-invalid-class-object:62:15:62:29:Pylint7429Good.class_defining_function_complex_bad:Invalid __class__ object:UNDEFINED
+invalid-class-object:20:0:20:11::Invalid assignment to '__class__'. Should be a class definition but got a 'Instance':INFERENCE
+invalid-class-object:21:0:21:11::Invalid assignment to '__class__'. Should be a class definition but got a 'Const':INFERENCE
+invalid-class-object:50:8:50:22:Pylint7429Good.class_defining_function_bad:Invalid assignment to '__class__'. Should be a class definition but got a 'Const':INFERENCE
+invalid-class-object:58:15:58:29:Pylint7429Good.class_defining_function_bad_inverted:Invalid assignment to '__class__'. Should be a class definition but got a 'Const':INFERENCE
+invalid-class-object:62:15:62:29:Pylint7429Good.class_defining_function_complex_bad:Invalid assignment to '__class__'. Should be a class definition but got a 'Const':INFERENCE
diff --git a/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt
index 9e8e7ae00..f2ccd8a05 100644
--- a/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt
+++ b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.txt
@@ -1,11 +1,11 @@
-raising-non-exception:38:4:38:30:bad_case0:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-raising-non-exception:42:4:42:25:bad_case1:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-raising-non-exception:48:4:48:30:bad_case2:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-raising-non-exception:52:4:52:23:bad_case3:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-notimplemented-raised:56:4:56:31:bad_case4:NotImplemented raised - should raise NotImplementedError:UNDEFINED
-raising-bad-type:60:4:60:11:bad_case5:Raising int while only classes or instances are allowed:UNDEFINED
-raising-bad-type:64:4:64:14:bad_case6:Raising NoneType while only classes or instances are allowed:UNDEFINED
-raising-non-exception:68:4:68:14:bad_case7:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-raising-non-exception:72:4:72:15:bad_case8:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-raising-non-exception:76:4:76:14:bad_case9:Raising a new style class which doesn't inherit from BaseException:UNDEFINED
-raising-bad-type:110:4:110:18:bad_case10:Raising str while only classes or instances are allowed:UNDEFINED
+raising-non-exception:38:4:38:30:bad_case0:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+raising-non-exception:42:4:42:25:bad_case1:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+raising-non-exception:48:4:48:30:bad_case2:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+raising-non-exception:52:4:52:23:bad_case3:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+notimplemented-raised:56:4:56:31:bad_case4:NotImplemented raised - should raise NotImplementedError:HIGH
+raising-bad-type:60:4:60:11:bad_case5:Raising int while only classes or instances are allowed:INFERENCE
+raising-bad-type:64:4:64:14:bad_case6:Raising NoneType while only classes or instances are allowed:INFERENCE
+raising-non-exception:68:4:68:14:bad_case7:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+raising-non-exception:72:4:72:15:bad_case8:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+raising-non-exception:76:4:76:14:bad_case9:Raising a new style class which doesn't inherit from BaseException:INFERENCE
+raising-bad-type:110:4:110:18:bad_case10:Raising str while only classes or instances are allowed:INFERENCE
diff --git a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py
index 5614674c3..efe6ba25f 100644
--- a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py
+++ b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_ex_returned.py
@@ -1,6 +1,6 @@
"""Check invalid value returned by __getnewargs_ex__ """
-# pylint: disable=too-few-public-methods,missing-docstring,import-error,use-dict-literal,unnecessary-lambda-assignment
+# pylint: disable=too-few-public-methods,missing-docstring,import-error,use-dict-literal,unnecessary-lambda-assignment,use-dict-literal
import six
from missing import Missing
diff --git a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py
index 49fe7b602..06cd81dd0 100644
--- a/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py
+++ b/tests/functional/i/invalid/invalid_getnewargs/invalid_getnewargs_returned.py
@@ -1,6 +1,6 @@
"""Check invalid value returned by __getnewargs__ """
-# pylint: disable=too-few-public-methods,missing-docstring,import-error,unnecessary-lambda-assignment
+# pylint: disable=too-few-public-methods,missing-docstring,import-error,unnecessary-lambda-assignment,use-dict-literal
import six
from missing import Missing
diff --git a/tests/functional/i/invalid/invalid_name/invalid_name-module-disable.py b/tests/functional/i/invalid/invalid_name/invalid_name-module-disable.py
new file mode 100644
index 000000000..f6074eebb
--- /dev/null
+++ b/tests/functional/i/invalid/invalid_name/invalid_name-module-disable.py
@@ -0,0 +1,6 @@
+# pylint: disable=invalid-name
+
+"""Regression test for disabling of invalid-name for module names.
+
+See https://github.com/PyCQA/pylint/issues/3973.
+"""
diff --git a/tests/functional/i/invalid/invalid_sequence_index.py b/tests/functional/i/invalid/invalid_sequence_index.py
index ec12b6e94..11108d4fe 100644
--- a/tests/functional/i/invalid/invalid_sequence_index.py
+++ b/tests/functional/i/invalid/invalid_sequence_index.py
@@ -202,7 +202,7 @@ def function24():
test[0] = 0 # setitem with int, no error
del test[0] # delitem with int, no error
-# Teest ExtSlice usage
+# Test ExtSlice usage
def function25():
"""Extended slice used with a list"""
return TESTLIST[..., 0] # [invalid-sequence-index]
diff --git a/tests/functional/i/invalid/invalid_slice_index.py b/tests/functional/i/invalid/invalid_slice_index.py
index 2e5d2cdb0..253d01ae1 100644
--- a/tests/functional/i/invalid/invalid_slice_index.py
+++ b/tests/functional/i/invalid/invalid_slice_index.py
@@ -1,6 +1,6 @@
"""Errors for invalid slice indices"""
# pylint: disable=too-few-public-methods,missing-docstring,expression-not-assigned,unnecessary-pass
-
+# pylint: disable=pointless-statement
TESTLIST = [1, 2, 3]
@@ -11,7 +11,10 @@ def function1():
def function2():
"""strings used as indices"""
- return TESTLIST['0':'1':] # [invalid-slice-index,invalid-slice-index]
+ TESTLIST['0':'1':] # [invalid-slice-index,invalid-slice-index]
+ ()['0':'1'] # [invalid-slice-index,invalid-slice-index]
+ ""["a":"z"] # [invalid-slice-index,invalid-slice-index]
+ b""["a":"z"] # [invalid-slice-index,invalid-slice-index]
def function3():
"""class without __index__ used as index"""
@@ -22,10 +25,27 @@ def function3():
return TESTLIST[NoIndexTest()::] # [invalid-slice-index]
+def invalid_step():
+ """0 is an invalid value for slice step with most builtin sequences."""
+ TESTLIST[::0] # [invalid-slice-step]
+ [][::0] # [invalid-slice-step]
+ ""[::0] # [invalid-slice-step]
+ b""[::0] # [invalid-slice-step]
+
+ class Custom:
+ def __getitem__(self, indices):
+ ...
+
+ Custom()[::0] # no error -> custom __getitem__ method
+
+def invalid_slice_range():
+ range(5)['0':'1'] # [invalid-slice-index,invalid-slice-index]
+
+
# Valid indices
def function4():
"""integers used as indices"""
- return TESTLIST[0:0:0] # no error
+ return TESTLIST[0:1:1]
def function5():
"""None used as indices"""
diff --git a/tests/functional/i/invalid/invalid_slice_index.txt b/tests/functional/i/invalid/invalid_slice_index.txt
index 97754e840..3e7713ba7 100644
--- a/tests/functional/i/invalid/invalid_slice_index.txt
+++ b/tests/functional/i/invalid/invalid_slice_index.txt
@@ -1,5 +1,17 @@
invalid-slice-index:10:20:10:22:function1:Slice index is not an int, None, or instance with __index__:UNDEFINED
invalid-slice-index:10:23:10:25:function1:Slice index is not an int, None, or instance with __index__:UNDEFINED
-invalid-slice-index:14:20:14:23:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
-invalid-slice-index:14:24:14:27:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
-invalid-slice-index:23:20:23:33:function3:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:14:13:14:16:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:14:17:14:20:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:15:7:15:10:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:15:11:15:14:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:16:7:16:10:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:16:11:16:14:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:17:8:17:11:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:17:12:17:15:function2:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:26:20:26:33:function3:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-step:30:15:30:16:invalid_step:Slice step cannot be 0:HIGH
+invalid-slice-step:31:9:31:10:invalid_step:Slice step cannot be 0:HIGH
+invalid-slice-step:32:9:32:10:invalid_step:Slice step cannot be 0:HIGH
+invalid-slice-step:33:10:33:11:invalid_step:Slice step cannot be 0:HIGH
+invalid-slice-index:42:13:42:16:invalid_slice_range:Slice index is not an int, None, or instance with __index__:UNDEFINED
+invalid-slice-index:42:17:42:20:invalid_slice_range:Slice index is not an int, None, or instance with __index__:UNDEFINED
diff --git a/tests/functional/i/iterable_context.py b/tests/functional/i/iterable_context.py
index bc77ade34..fb035c4df 100644
--- a/tests/functional/i/iterable_context.py
+++ b/tests/functional/i/iterable_context.py
@@ -4,6 +4,7 @@ iterating/mapping context.
"""
# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,import-error,unused-argument,bad-mcs-method-argument,
# pylint: disable=wrong-import-position,no-else-return, unnecessary-comprehension,redundant-u-string-prefix
+# pylint: disable=use-dict-literal
# primitives
numbers = [1, 2, 3]
diff --git a/tests/functional/i/iterable_context.txt b/tests/functional/i/iterable_context.txt
index e0ca8c4fe..ef59b379c 100644
--- a/tests/functional/i/iterable_context.txt
+++ b/tests/functional/i/iterable_context.txt
@@ -1,10 +1,10 @@
-not-an-iterable:57:9:57:22::Non-iterable value powers_of_two is used in an iterating context:UNDEFINED
-not-an-iterable:92:6:92:9::Non-iterable value A() is used in an iterating context:UNDEFINED
-not-an-iterable:94:6:94:7::Non-iterable value B is used in an iterating context:UNDEFINED
-not-an-iterable:95:9:95:12::Non-iterable value A() is used in an iterating context:UNDEFINED
-not-an-iterable:99:9:99:10::Non-iterable value B is used in an iterating context:UNDEFINED
-not-an-iterable:102:9:102:14::Non-iterable value range is used in an iterating context:UNDEFINED
-not-an-iterable:106:9:106:13::Non-iterable value True is used in an iterating context:UNDEFINED
-not-an-iterable:109:9:109:13::Non-iterable value None is used in an iterating context:UNDEFINED
-not-an-iterable:112:9:112:12::Non-iterable value 8.5 is used in an iterating context:UNDEFINED
-not-an-iterable:115:9:115:11::Non-iterable value 10 is used in an iterating context:UNDEFINED
+not-an-iterable:58:9:58:22::Non-iterable value powers_of_two is used in an iterating context:UNDEFINED
+not-an-iterable:93:6:93:9::Non-iterable value A() is used in an iterating context:UNDEFINED
+not-an-iterable:95:6:95:7::Non-iterable value B is used in an iterating context:UNDEFINED
+not-an-iterable:96:9:96:12::Non-iterable value A() is used in an iterating context:UNDEFINED
+not-an-iterable:100:9:100:10::Non-iterable value B is used in an iterating context:UNDEFINED
+not-an-iterable:103:9:103:14::Non-iterable value range is used in an iterating context:UNDEFINED
+not-an-iterable:107:9:107:13::Non-iterable value True is used in an iterating context:UNDEFINED
+not-an-iterable:110:9:110:13::Non-iterable value None is used in an iterating context:UNDEFINED
+not-an-iterable:113:9:113:12::Non-iterable value 8.5 is used in an iterating context:UNDEFINED
+not-an-iterable:116:9:116:11::Non-iterable value 10 is used in an iterating context:UNDEFINED
diff --git a/tests/functional/m/mapping_context.py b/tests/functional/m/mapping_context.py
index 1d8a46afc..8dc6b3b72 100644
--- a/tests/functional/m/mapping_context.py
+++ b/tests/functional/m/mapping_context.py
@@ -1,7 +1,7 @@
"""
Checks that only valid values are used in a mapping context.
"""
-# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,import-error,wrong-import-position
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,import-error,wrong-import-position,use-dict-literal
def test(**kwargs):
diff --git a/tests/functional/m/misplaced_bare_raise.txt b/tests/functional/m/misplaced_bare_raise.txt
index d26893e3f..dbb20c266 100644
--- a/tests/functional/m/misplaced_bare_raise.txt
+++ b/tests/functional/m/misplaced_bare_raise.txt
@@ -1,7 +1,7 @@
-misplaced-bare-raise:6:4:6:9::The raise statement is not inside an except clause:UNDEFINED
-misplaced-bare-raise:36:16:36:21:test1.best:The raise statement is not inside an except clause:UNDEFINED
-misplaced-bare-raise:39:4:39:9:test1:The raise statement is not inside an except clause:UNDEFINED
-misplaced-bare-raise:40:0:40:5::The raise statement is not inside an except clause:UNDEFINED
-misplaced-bare-raise:49:4:49:9::The raise statement is not inside an except clause:UNDEFINED
-misplaced-bare-raise:57:4:57:9:A:The raise statement is not inside an except clause:UNDEFINED
-misplaced-bare-raise:68:4:68:9::The raise statement is not inside an except clause:UNDEFINED
+misplaced-bare-raise:6:4:6:9::The raise statement is not inside an except clause:HIGH
+misplaced-bare-raise:36:16:36:21:test1.best:The raise statement is not inside an except clause:HIGH
+misplaced-bare-raise:39:4:39:9:test1:The raise statement is not inside an except clause:HIGH
+misplaced-bare-raise:40:0:40:5::The raise statement is not inside an except clause:HIGH
+misplaced-bare-raise:49:4:49:9::The raise statement is not inside an except clause:HIGH
+misplaced-bare-raise:57:4:57:9:A:The raise statement is not inside an except clause:HIGH
+misplaced-bare-raise:68:4:68:9::The raise statement is not inside an except clause:HIGH
diff --git a/tests/functional/m/multiple_statements.py b/tests/functional/m/multiple_statements.py
index 5b55eac42..c3252f797 100644
--- a/tests/functional/m/multiple_statements.py
+++ b/tests/functional/m/multiple_statements.py
@@ -27,4 +27,4 @@ finally:
@overload
def concat2(arg1: str) -> str: ...
-def concat2(arg1: str) -> str: ... # [multiple-statements]
+def concat2(arg1: str) -> str: ...
diff --git a/tests/functional/m/multiple_statements.txt b/tests/functional/m/multiple_statements.txt
index 34d80508e..661314268 100644
--- a/tests/functional/m/multiple_statements.txt
+++ b/tests/functional/m/multiple_statements.txt
@@ -3,4 +3,3 @@ multiple-statements:9:9:9:13::More than one statement on a single line:UNDEFINED
multiple-statements:13:26:13:30:MyError:More than one statement on a single line:UNDEFINED
multiple-statements:15:26:15:31:MyError:More than one statement on a single line:UNDEFINED
multiple-statements:17:26:17:31:MyError:More than one statement on a single line:UNDEFINED
-multiple-statements:30:31:30:34:concat2:More than one statement on a single line:UNDEFINED
diff --git a/tests/functional/m/multiple_statements_single_line.py b/tests/functional/m/multiple_statements_single_line.py
index 4a77d992e..93a470702 100644
--- a/tests/functional/m/multiple_statements_single_line.py
+++ b/tests/functional/m/multiple_statements_single_line.py
@@ -27,4 +27,4 @@ finally:
@overload
def concat2(arg1: str) -> str: ...
-def concat2(arg1: str) -> str: ... # [multiple-statements]
+def concat2(arg1: str) -> str: ...
diff --git a/tests/functional/m/multiple_statements_single_line.txt b/tests/functional/m/multiple_statements_single_line.txt
index a28fc96c4..cac2f7eb2 100644
--- a/tests/functional/m/multiple_statements_single_line.txt
+++ b/tests/functional/m/multiple_statements_single_line.txt
@@ -1,3 +1,2 @@
multiple-statements:9:9:9:13::More than one statement on a single line:UNDEFINED
multiple-statements:17:26:17:31:MyError:More than one statement on a single line:UNDEFINED
-multiple-statements:30:31:30:34:concat2:More than one statement on a single line:UNDEFINED
diff --git a/tests/functional/n/named_expr_without_context_py38.py b/tests/functional/n/named_expr_without_context_py38.py
new file mode 100644
index 000000000..ee45b84b3
--- /dev/null
+++ b/tests/functional/n/named_expr_without_context_py38.py
@@ -0,0 +1,6 @@
+# pylint: disable=missing-docstring
+
+if (a := 2):
+ pass
+
+(b := 1) # [named-expr-without-context]
diff --git a/tests/functional/n/named_expr_without_context_py38.rc b/tests/functional/n/named_expr_without_context_py38.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/n/named_expr_without_context_py38.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/n/named_expr_without_context_py38.txt b/tests/functional/n/named_expr_without_context_py38.txt
new file mode 100644
index 000000000..2ab9a2733
--- /dev/null
+++ b/tests/functional/n/named_expr_without_context_py38.txt
@@ -0,0 +1 @@
+named-expr-without-context:6:0:6:8::Named expression used without context:HIGH
diff --git a/tests/functional/n/nested_min_max.py b/tests/functional/n/nested_min_max.py
new file mode 100644
index 000000000..cef63dc2b
--- /dev/null
+++ b/tests/functional/n/nested_min_max.py
@@ -0,0 +1,21 @@
+"""Test detection of redundant nested calls to min/max functions"""
+
+# pylint: disable=redefined-builtin,unnecessary-lambda-assignment
+
+min(1, min(2, 3)) # [nested-min-max]
+max(1, max(2, 3)) # [nested-min-max]
+min(min(1, 2), 3) # [nested-min-max]
+min(min(min(1, 2), 3), 4) # [nested-min-max, nested-min-max]
+min(1, max(2, 3))
+min(1, 2, 3)
+min(min(1, 2), min(3, 4)) # [nested-min-max]
+min(len([]), min(len([1]), len([1, 2]))) # [nested-min-max]
+
+orig_min = min
+min = lambda *args: args[0]
+min(1, min(2, 3))
+orig_min(1, orig_min(2, 3)) # [nested-min-max]
+
+# This is too complicated (for now) as there is no clear better way to write it
+max(max(i for i in range(10)), 0)
+max(max(max(i for i in range(10)), 0), 1)
diff --git a/tests/functional/n/nested_min_max.txt b/tests/functional/n/nested_min_max.txt
new file mode 100644
index 000000000..9bcb663e3
--- /dev/null
+++ b/tests/functional/n/nested_min_max.txt
@@ -0,0 +1,8 @@
+nested-min-max:5:0:5:17::Do not use nested call of 'min'; it's possible to do 'min(1, 2, 3)' instead:INFERENCE
+nested-min-max:6:0:6:17::Do not use nested call of 'max'; it's possible to do 'max(1, 2, 3)' instead:INFERENCE
+nested-min-max:7:0:7:17::Do not use nested call of 'min'; it's possible to do 'min(1, 2, 3)' instead:INFERENCE
+nested-min-max:8:4:8:21::Do not use nested call of 'min'; it's possible to do 'min(1, 2, 3)' instead:INFERENCE
+nested-min-max:8:0:8:25::Do not use nested call of 'min'; it's possible to do 'min(1, 2, 3, 4)' instead:INFERENCE
+nested-min-max:11:0:11:25::Do not use nested call of 'min'; it's possible to do 'min(1, 2, 3, 4)' instead:INFERENCE
+nested-min-max:12:0:12:40::Do not use nested call of 'min'; it's possible to do 'min(len([]), len([1]), len([1, 2]))' instead:INFERENCE
+nested-min-max:17:0:17:27::Do not use nested call of 'orig_min'; it's possible to do 'orig_min(1, 2, 3)' instead:INFERENCE
diff --git a/tests/functional/n/no/no_else_raise.py b/tests/functional/n/no/no_else_raise.py
index 9a54dfc9f..33a1fb561 100644
--- a/tests/functional/n/no/no_else_raise.py
+++ b/tests/functional/n/no/no_else_raise.py
@@ -1,6 +1,6 @@
""" Test that superfluous else raise are detected. """
-# pylint:disable=invalid-name,missing-docstring,unused-variable,raise-missing-from
+# pylint:disable=invalid-name,missing-docstring,unused-variable,raise-missing-from,broad-exception-raised
def foo1(x, y, z):
if x: # [no-else-raise]
diff --git a/tests/functional/n/no/no_member_augassign.py b/tests/functional/n/no/no_member_augassign.py
new file mode 100644
index 000000000..1ffd9a168
--- /dev/null
+++ b/tests/functional/n/no/no_member_augassign.py
@@ -0,0 +1,25 @@
+"""Tests for no-member in relation to AugAssign operations."""
+# pylint: disable=missing-module-docstring, too-few-public-methods, missing-class-docstring, invalid-name
+
+# Test for: https://github.com/PyCQA/pylint/issues/4562
+class A:
+ value: int
+
+obj_a = A()
+obj_a.value += 1 # [no-member]
+
+
+class B:
+ value: int
+
+obj_b = B()
+obj_b.value = 1 + obj_b.value # [no-member]
+
+
+class C:
+ value: int
+
+
+obj_c = C()
+obj_c.value += 1 # [no-member]
+obj_c.value = 1 + obj_c.value # [no-member]
diff --git a/tests/functional/n/no/no_member_augassign.txt b/tests/functional/n/no/no_member_augassign.txt
new file mode 100644
index 000000000..68abf0b93
--- /dev/null
+++ b/tests/functional/n/no/no_member_augassign.txt
@@ -0,0 +1,4 @@
+no-member:9:0:9:11::Instance of 'A' has no 'value' member:INFERENCE
+no-member:16:18:16:29::Instance of 'B' has no 'value' member:INFERENCE
+no-member:24:0:24:11::Instance of 'C' has no 'value' member:INFERENCE
+no-member:25:18:25:29::Instance of 'C' has no 'value' member:INFERENCE
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
index 59585645a..557741753 100644
--- 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
@@ -1,5 +1,5 @@
"""invalid ascii char in a for loop"""
-
+# pylint: disable=consider-using-join
import os
diff --git a/tests/functional/n/not_callable.py b/tests/functional/n/not_callable.py
index f781150a2..c5015e65f 100644
--- a/tests/functional/n/not_callable.py
+++ b/tests/functional/n/not_callable.py
@@ -232,3 +232,14 @@ instance_or_cls = MyClass()
if not isinstance(instance_or_cls, MyClass):
new = MyClass.__new__(instance_or_cls)
new()
+
+
+# Regression test for https://github.com/PyCQA/pylint/issues/5113.
+# Do not emit `not-callable`.
+ATTRIBUTES = {
+ 'DOMAIN': ("domain", str),
+ 'IMAGE': ("image", bytes),
+}
+
+for key, (name, validate) in ATTRIBUTES.items():
+ name = validate(1)
diff --git a/tests/functional/r/raising/raising_bad_type.txt b/tests/functional/r/raising/raising_bad_type.txt
index 28fcfadc6..04ea2d170 100644
--- a/tests/functional/r/raising/raising_bad_type.txt
+++ b/tests/functional/r/raising/raising_bad_type.txt
@@ -1 +1 @@
-raising-bad-type:3:0:3:31::Raising tuple while only classes or instances are allowed:UNDEFINED
+raising-bad-type:3:0:3:31::Raising tuple while only classes or instances are allowed:INFERENCE
diff --git a/tests/functional/r/raising/raising_format_tuple.txt b/tests/functional/r/raising/raising_format_tuple.txt
index 5f9283bb1..a6456bc84 100644
--- a/tests/functional/r/raising/raising_format_tuple.txt
+++ b/tests/functional/r/raising/raising_format_tuple.txt
@@ -1,7 +1,7 @@
-raising-format-tuple:11:4:11:38:bad_percent:Exception arguments suggest string formatting might be intended:UNDEFINED
-raising-format-tuple:19:4:19:53:bad_multiarg:Exception arguments suggest string formatting might be intended:UNDEFINED
-raising-format-tuple:27:4:27:40:bad_braces:Exception arguments suggest string formatting might be intended:UNDEFINED
-raising-format-tuple:35:4:37:52:bad_multistring:Exception arguments suggest string formatting might be intended:UNDEFINED
-raising-format-tuple:41:4:43:53:bad_triplequote:Exception arguments suggest string formatting might be intended:UNDEFINED
-raising-format-tuple:47:4:47:36:bad_unicode:Exception arguments suggest string formatting might be intended:UNDEFINED
-raising-format-tuple:52:4:52:56:raise_something_without_name:Exception arguments suggest string formatting might be intended:UNDEFINED
+raising-format-tuple:11:4:11:38:bad_percent:Exception arguments suggest string formatting might be intended:HIGH
+raising-format-tuple:19:4:19:53:bad_multiarg:Exception arguments suggest string formatting might be intended:HIGH
+raising-format-tuple:27:4:27:40:bad_braces:Exception arguments suggest string formatting might be intended:HIGH
+raising-format-tuple:35:4:37:52:bad_multistring:Exception arguments suggest string formatting might be intended:HIGH
+raising-format-tuple:41:4:43:53:bad_triplequote:Exception arguments suggest string formatting might be intended:HIGH
+raising-format-tuple:47:4:47:36:bad_unicode:Exception arguments suggest string formatting might be intended:HIGH
+raising-format-tuple:52:4:52:56:raise_something_without_name:Exception arguments suggest string formatting might be intended:HIGH
diff --git a/tests/functional/r/raising/raising_non_exception.txt b/tests/functional/r/raising/raising_non_exception.txt
index efa816a5f..5cab16846 100644
--- a/tests/functional/r/raising/raising_non_exception.txt
+++ b/tests/functional/r/raising/raising_non_exception.txt
@@ -1 +1 @@
-raising-non-exception:13:0:13:22::Raising a new style class which doesn't inherit from BaseException:UNDEFINED
+raising-non-exception:13:0:13:22::Raising a new style class which doesn't inherit from BaseException:INFERENCE
diff --git a/tests/functional/r/redefined/redefined_except_handler.txt b/tests/functional/r/redefined/redefined_except_handler.txt
index 1184bdd81..a0ccc6b9b 100644
--- a/tests/functional/r/redefined/redefined_except_handler.txt
+++ b/tests/functional/r/redefined/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:HIGH
+used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:CONTROL_FLOW
redefined-outer-name:71:4:72:12:func:Redefining name 'CustomException' from outer scope (line 62):UNDEFINED
diff --git a/tests/functional/r/regression_02/regression_2964.py b/tests/functional/r/regression_02/regression_2964.py
new file mode 100644
index 000000000..66235fc09
--- /dev/null
+++ b/tests/functional/r/regression_02/regression_2964.py
@@ -0,0 +1,24 @@
+"""
+Regression test for `no-member`.
+See: https://github.com/PyCQA/pylint/issues/2964
+"""
+
+# pylint: disable=missing-class-docstring,too-few-public-methods
+# pylint: disable=unused-private-member,protected-access
+
+
+class Node:
+ def __init__(self, name, path=()):
+ """
+ Initialize self with "name" string and the tuple "path" of its parents.
+ "self" is added to the tuple as its last item.
+ """
+ self.__name = name
+ self.__path = path + (self,)
+
+ def get_full_name(self):
+ """
+ A `no-member` message was emitted:
+ nodes.py:17:24: E1101: Instance of 'tuple' has no '__name' member (no-member)
+ """
+ return ".".join(node.__name for node in self.__path)
diff --git a/tests/functional/r/regression_02/regression_3976.py b/tests/functional/r/regression_02/regression_3976.py
new file mode 100644
index 000000000..3610e9e30
--- /dev/null
+++ b/tests/functional/r/regression_02/regression_3976.py
@@ -0,0 +1,14 @@
+"""
+Regression test for https://github.com/PyCQA/pylint/issues/3976
+
+E1123: Unexpected keyword argument 'include_extras' in function call (unexpected-keyword-arg)
+"""
+
+import typing_extensions
+
+
+def function():
+ """Simple function"""
+
+
+typing_extensions.get_type_hints(function, include_extras=True)
diff --git a/tests/functional/r/regression_02/regression_5048.py b/tests/functional/r/regression_02/regression_5048.py
index 5656759af..08ff55fb2 100644
--- a/tests/functional/r/regression_02/regression_5048.py
+++ b/tests/functional/r/regression_02/regression_5048.py
@@ -1,6 +1,6 @@
"""Crash regression in astroid on Compare node inference
Fixed in https://github.com/PyCQA/astroid/pull/1185"""
-# pylint: disable=missing-docstring
+# pylint: disable=missing-docstring, broad-exception-raised
# Reported at https://github.com/PyCQA/pylint/issues/5048
diff --git a/tests/functional/r/regression_02/regression_too_many_arguments_2335.py b/tests/functional/r/regression_02/regression_too_many_arguments_2335.py
index d2759adfe..55aa87308 100644
--- a/tests/functional/r/regression_02/regression_too_many_arguments_2335.py
+++ b/tests/functional/r/regression_02/regression_too_many_arguments_2335.py
@@ -7,5 +7,5 @@ from abc import ABCMeta
class NodeCheckMetaClass(ABCMeta):
- def __new__(cls, name, bases, namespace, **kwargs):
- return ABCMeta.__new__(cls, name, bases, namespace)
+ def __new__(mcs, name, bases, namespace, **kwargs):
+ return ABCMeta.__new__(mcs, name, bases, namespace)
diff --git a/tests/functional/s/simplifiable/simplifiable_if_statement.py b/tests/functional/s/simplifiable/simplifiable_if_statement.py
index 4d4c8b5d4..59251bd04 100644
--- a/tests/functional/s/simplifiable/simplifiable_if_statement.py
+++ b/tests/functional/s/simplifiable/simplifiable_if_statement.py
@@ -29,6 +29,7 @@ def test_simplifiable_3(arg, arg2):
def test_simplifiable_4(arg):
+ var = False
if arg:
var = True
else:
@@ -89,6 +90,7 @@ def test_not_simplifiable_4(arg):
def test_not_simplifiable_5(arg):
# Different actions in each branch
+ var = 43
if arg == "any":
return True
else:
diff --git a/tests/functional/s/simplifiable/simplifiable_if_statement.txt b/tests/functional/s/simplifiable/simplifiable_if_statement.txt
index d36768ddd..e0a82ef6a 100644
--- a/tests/functional/s/simplifiable/simplifiable_if_statement.txt
+++ b/tests/functional/s/simplifiable/simplifiable_if_statement.txt
@@ -1,4 +1,4 @@
simplifiable-if-statement:8:4:11:20:test_simplifiable_1:The if statement can be replaced with 'return bool(test)':UNDEFINED
simplifiable-if-statement:16:4:19:20:test_simplifiable_2:The if statement can be replaced with 'return bool(test)':UNDEFINED
simplifiable-if-statement:24:4:27:19:test_simplifiable_3:The if statement can be replaced with 'var = bool(test)':UNDEFINED
-simplifiable-if-statement:35:8:38:24:test_simplifiable_4:The if statement can be replaced with 'return bool(test)':UNDEFINED
+simplifiable-if-statement:36:8:39:24:test_simplifiable_4:The if statement can be replaced with 'return bool(test)':UNDEFINED
diff --git a/tests/functional/s/singledispatch_method.txt b/tests/functional/s/singledispatch_method.txt
new file mode 100644
index 000000000..c747fb6a8
--- /dev/null
+++ b/tests/functional/s/singledispatch_method.txt
@@ -0,0 +1,3 @@
+singledispatch-method:26:5:26:19:Board.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
+singledispatch-method:31:5:31:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
+singledispatch-method:37:5:37:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
diff --git a/tests/functional/s/singledispatch_method_py37.py b/tests/functional/s/singledispatch_method_py37.py
new file mode 100644
index 000000000..c9269f7bf
--- /dev/null
+++ b/tests/functional/s/singledispatch_method_py37.py
@@ -0,0 +1,23 @@
+"""Tests for singledispatch-method"""
+# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods
+
+
+from functools import singledispatch
+
+
+class Board:
+ @singledispatch # [singledispatch-method]
+ @classmethod
+ def convert_position(cls, position):
+ pass
+
+ @convert_position.register # [singledispatch-method]
+ @classmethod
+ def _(cls, position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register # [singledispatch-method]
+ @classmethod
+ def _(cls, position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/tests/functional/s/singledispatch_method_py37.rc b/tests/functional/s/singledispatch_method_py37.rc
new file mode 100644
index 000000000..67a28a36a
--- /dev/null
+++ b/tests/functional/s/singledispatch_method_py37.rc
@@ -0,0 +1,2 @@
+[testoptions]
+max_pyver=3.8
diff --git a/tests/functional/s/singledispatch_method_py37.txt b/tests/functional/s/singledispatch_method_py37.txt
new file mode 100644
index 000000000..111bc4722
--- /dev/null
+++ b/tests/functional/s/singledispatch_method_py37.txt
@@ -0,0 +1,3 @@
+singledispatch-method:9:5:9:19:Board.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
+singledispatch-method:14:5:14:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
+singledispatch-method:20:5:20:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
diff --git a/tests/functional/s/singledispatch_method_py38.py b/tests/functional/s/singledispatch_method_py38.py
new file mode 100644
index 000000000..ad8eea1dd
--- /dev/null
+++ b/tests/functional/s/singledispatch_method_py38.py
@@ -0,0 +1,40 @@
+"""Tests for singledispatch-method"""
+# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods
+
+
+from functools import singledispatch, singledispatchmethod
+
+
+class BoardRight:
+ @singledispatchmethod
+ @classmethod
+ def convert_position(cls, position):
+ pass
+
+ @convert_position.register
+ @classmethod
+ def _(cls, position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register
+ def _(self, position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
+
+
+class Board:
+ @singledispatch # [singledispatch-method]
+ @classmethod
+ def convert_position(cls, position):
+ pass
+
+ @convert_position.register # [singledispatch-method]
+ @classmethod
+ def _(cls, position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register # [singledispatch-method]
+ @classmethod
+ def _(cls, position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/tests/functional/s/singledispatch_method_py38.rc b/tests/functional/s/singledispatch_method_py38.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/s/singledispatch_method_py38.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/s/singledispatch_method_py38.txt b/tests/functional/s/singledispatch_method_py38.txt
new file mode 100644
index 000000000..c747fb6a8
--- /dev/null
+++ b/tests/functional/s/singledispatch_method_py38.txt
@@ -0,0 +1,3 @@
+singledispatch-method:26:5:26:19:Board.convert_position:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:HIGH
+singledispatch-method:31:5:31:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
+singledispatch-method:37:5:37:30:Board._:singledispatch decorator should not be used with methods, use singledispatchmethod instead.:INFERENCE
diff --git a/tests/functional/s/singledispatchmethod_function_py38.py b/tests/functional/s/singledispatchmethod_function_py38.py
new file mode 100644
index 000000000..ef44f71c1
--- /dev/null
+++ b/tests/functional/s/singledispatchmethod_function_py38.py
@@ -0,0 +1,41 @@
+"""Tests for singledispatchmethod-function"""
+# pylint: disable=missing-class-docstring, missing-function-docstring,too-few-public-methods
+
+
+from functools import singledispatch, singledispatchmethod
+
+
+class BoardRight:
+ @singledispatch
+ @staticmethod
+ def convert_position(position):
+ pass
+
+ @convert_position.register
+ @staticmethod
+ def _(position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register
+ @staticmethod
+ def _(position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
+
+
+class Board:
+ @singledispatchmethod # [singledispatchmethod-function]
+ @staticmethod
+ def convert_position(position):
+ pass
+
+ @convert_position.register # [singledispatchmethod-function]
+ @staticmethod
+ def _(position: str) -> tuple:
+ position_a, position_b = position.split(",")
+ return (int(position_a), int(position_b))
+
+ @convert_position.register # [singledispatchmethod-function]
+ @staticmethod
+ def _(position: tuple) -> str:
+ return f"{position[0]},{position[1]}"
diff --git a/tests/functional/s/singledispatchmethod_function_py38.rc b/tests/functional/s/singledispatchmethod_function_py38.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/s/singledispatchmethod_function_py38.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/s/singledispatchmethod_function_py38.txt b/tests/functional/s/singledispatchmethod_function_py38.txt
new file mode 100644
index 000000000..4c236b346
--- /dev/null
+++ b/tests/functional/s/singledispatchmethod_function_py38.txt
@@ -0,0 +1,3 @@
+singledispatchmethod-function:27:5:27:25:Board.convert_position:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:HIGH
+singledispatchmethod-function:32:5:32:30:Board._:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE
+singledispatchmethod-function:38:5:38:30:Board._:singledispatchmethod decorator should not be used with functions, use singledispatch instead.:INFERENCE
diff --git a/tests/functional/s/slots_checks.py b/tests/functional/s/slots_checks.py
index e8d55d967..2c22e968e 100644
--- a/tests/functional/s/slots_checks.py
+++ b/tests/functional/s/slots_checks.py
@@ -64,7 +64,7 @@ class SixthBad: # [single-string-used-for-slots]
__slots__ = "a"
class SeventhBad: # [single-string-used-for-slots]
- __slots__ = ('foo')
+ __slots__ = ('foo') # [superfluous-parens]
class EighthBad: # [single-string-used-for-slots]
__slots__ = deque.__name__
diff --git a/tests/functional/s/slots_checks.txt b/tests/functional/s/slots_checks.txt
index 3abccff8f..d63ad2517 100644
--- a/tests/functional/s/slots_checks.txt
+++ b/tests/functional/s/slots_checks.txt
@@ -5,6 +5,7 @@ invalid-slots:57:0:57:15:FourthBad:Invalid __slots__ object:UNDEFINED
invalid-slots-object:61:27:61:29:FifthBad:"Invalid object ""''"" in __slots__, must contain only non empty strings":INFERENCE
single-string-used-for-slots:63:0:63:14:SixthBad:Class __slots__ should be a non-string iterable:UNDEFINED
single-string-used-for-slots:66:0:66:16:SeventhBad:Class __slots__ should be a non-string iterable:UNDEFINED
+superfluous-parens:67:0:None:None::Unnecessary parens after '=' keyword:UNDEFINED
single-string-used-for-slots:69:0:69:15:EighthBad:Class __slots__ should be a non-string iterable:UNDEFINED
invalid-slots-object:73:17:73:20:NinthBad:Invalid object 'str' in __slots__, must contain only non empty strings:INFERENCE
invalid-slots-object:76:17:76:26:TenthBad:Invalid object '1 + 2 + 3' in __slots__, must contain only non empty strings:INFERENCE
diff --git a/tests/functional/s/star/star_needs_assignment_target_py37.txt b/tests/functional/s/star/star_needs_assignment_target_py37.txt
index a4fa2caea..fb5a5faa6 100644
--- a/tests/functional/s/star/star_needs_assignment_target_py37.txt
+++ b/tests/functional/s/star/star_needs_assignment_target_py37.txt
@@ -1 +1 @@
-star-needs-assignment-target:15:37::Can use starred expression only in assignment target
+star-needs-assignment-target:15:36:15:46::Can use starred expression only in assignment target:UNDEFINED
diff --git a/tests/functional/s/stop_iteration_inside_generator.py b/tests/functional/s/stop_iteration_inside_generator.py
index f543f40ee..efde61a77 100644
--- a/tests/functional/s/stop_iteration_inside_generator.py
+++ b/tests/functional/s/stop_iteration_inside_generator.py
@@ -1,10 +1,10 @@
"""
Test that no StopIteration is raised inside a generator
"""
-# pylint: disable=missing-docstring,invalid-name,import-error, try-except-raise, wrong-import-position,not-callable,raise-missing-from
+# pylint: disable=missing-docstring,invalid-name,import-error, try-except-raise, wrong-import-position
+# pylint: disable=not-callable,raise-missing-from,broad-exception-raised
import asyncio
-
class RebornStopIteration(StopIteration):
"""
A class inheriting from StopIteration exception
diff --git a/tests/functional/s/superfluous_parens.py b/tests/functional/s/superfluous_parens.py
index db9349cce..35acfd5d3 100644
--- a/tests/functional/s/superfluous_parens.py
+++ b/tests/functional/s/superfluous_parens.py
@@ -47,13 +47,6 @@ def function_B(var):
def function_C(first, second):
return (first or second) in (0, 1)
-# TODO: Test string combinations, see https://github.com/PyCQA/pylint/issues/4792
-# The lines with "+" should raise the superfluous-parens message
-J = "TestString"
-K = ("Test " + "String")
-L = ("Test " + "String") in I
-assert "" + ("Version " + "String") in I
-
# Test numpy
def function_numpy_A(var_1: int, var_2: int) -> np.ndarray:
result = (((var_1 & var_2)) > 0)
@@ -72,6 +65,19 @@ class ClassA:
if (A == 2) is not (B == 2):
pass
+K = ("Test " + "String") # [superfluous-parens]
M = A is not (A <= H)
M = True is not (M == K)
M = True is not (True is not False) # pylint: disable=comparison-of-constants
+
+Z = "TestString"
+X = ("Test " + "String") # [superfluous-parens]
+Y = ("Test " + "String") in Z # [superfluous-parens]
+assert ("Test " + "String") in "hello" # [superfluous-parens]
+assert ("Version " + "String") in ("Version " + "String") # [superfluous-parens]
+
+hi = ("CONST") # [superfluous-parens]
+hi = ("CONST",)
+
+#TODO: maybe get this line to report [superfluous-parens] without causing other false positives.
+assert "" + ("Version " + "String") in Z
diff --git a/tests/functional/s/superfluous_parens.txt b/tests/functional/s/superfluous_parens.txt
index f830922bc..08b2dd390 100644
--- a/tests/functional/s/superfluous_parens.txt
+++ b/tests/functional/s/superfluous_parens.txt
@@ -4,3 +4,9 @@ superfluous-parens:12:0:None:None::Unnecessary parens after 'for' keyword:UNDEFI
superfluous-parens:14:0:None:None::Unnecessary parens after 'if' keyword:UNDEFINED
superfluous-parens:19:0:None:None::Unnecessary parens after 'del' keyword:UNDEFINED
superfluous-parens:31:0:None:None::Unnecessary parens after 'assert' keyword:UNDEFINED
+superfluous-parens:68:0:None:None::Unnecessary parens after '=' keyword:UNDEFINED
+superfluous-parens:74:0:None:None::Unnecessary parens after '=' keyword:UNDEFINED
+superfluous-parens:75:0:None:None::Unnecessary parens after '=' keyword:UNDEFINED
+superfluous-parens:76:0:None:None::Unnecessary parens after 'assert' keyword:UNDEFINED
+superfluous-parens:77:0:None:None::Unnecessary parens after 'assert' keyword:UNDEFINED
+superfluous-parens:79:0:None:None::Unnecessary parens after '=' keyword:UNDEFINED
diff --git a/tests/functional/s/superfluous_parens_walrus_py38.py b/tests/functional/s/superfluous_parens_walrus_py38.py
index cf155954e..7922e4613 100644
--- a/tests/functional/s/superfluous_parens_walrus_py38.py
+++ b/tests/functional/s/superfluous_parens_walrus_py38.py
@@ -1,5 +1,5 @@
"""Test the superfluous-parens warning with python 3.8 functionality (walrus operator)"""
-# pylint: disable=missing-function-docstring, invalid-name, missing-class-docstring, import-error
+# pylint: disable=missing-function-docstring, invalid-name, missing-class-docstring, import-error, pointless-statement,named-expr-without-context
import numpy
# Test parens in if statements
@@ -49,3 +49,25 @@ class TestYieldClass:
@classmethod
def function_C(cls):
yield (1 + 1) # [superfluous-parens]
+
+
+if (x := "Test " + "String"):
+ print(x)
+
+if (x := ("Test " + "String")): # [superfluous-parens]
+ print(x)
+
+if not (foo := "Test " + "String" in "hello"):
+ print(foo)
+
+if not (foo := ("Test " + "String") in "hello"): # [superfluous-parens]
+ print(foo)
+
+assert (ret := "Test " + "String")
+assert (ret := ("Test " + "String")) # [superfluous-parens]
+
+(walrus := False)
+(walrus := (False)) # [superfluous-parens]
+
+(hi := ("CONST")) # [superfluous-parens]
+(hi := ("CONST",))
diff --git a/tests/functional/s/superfluous_parens_walrus_py38.txt b/tests/functional/s/superfluous_parens_walrus_py38.txt
index 58097f520..da8f1b999 100644
--- a/tests/functional/s/superfluous_parens_walrus_py38.txt
+++ b/tests/functional/s/superfluous_parens_walrus_py38.txt
@@ -3,3 +3,8 @@ superfluous-parens:19:0:None:None::Unnecessary parens after 'if' keyword:UNDEFIN
superfluous-parens:22:0:None:None::Unnecessary parens after 'not' keyword:UNDEFINED
superfluous-parens:25:0:None:None::Unnecessary parens after 'not' keyword:UNDEFINED
superfluous-parens:51:0:None:None::Unnecessary parens after 'yield' keyword:UNDEFINED
+superfluous-parens:57:0:None:None::"Unnecessary parens after ':=' keyword":UNDEFINED
+superfluous-parens:63:0:None:None::"Unnecessary parens after ':=' keyword":UNDEFINED
+superfluous-parens:67:0:None:None::"Unnecessary parens after ':=' keyword":UNDEFINED
+superfluous-parens:70:0:None:None::"Unnecessary parens after ':=' keyword":UNDEFINED
+superfluous-parens:72:0:None:None::"Unnecessary parens after ':=' keyword":UNDEFINED
diff --git a/tests/functional/t/trailing_whitespaces.py b/tests/functional/t/trailing_whitespaces.py
index cb9d642ee..c88b7ea62 100644
--- a/tests/functional/t/trailing_whitespaces.py
+++ b/tests/functional/t/trailing_whitespaces.py
@@ -18,3 +18,24 @@ print('but trailing whitespace on win is not')
""" This module has the Board class.
It's a very nice Board.
"""
+
+# Regression test for https://github.com/PyCQA/pylint/issues/3822
+def example(*args):
+ """Example function."""
+ print(*args)
+
+
+example(
+ "bob", """
+ foobar
+ more text
+""",
+)
+
+example(
+ "bob",
+ """
+ foobar2
+ more text
+""",
+)
diff --git a/tests/functional/u/unbalanced_dict_unpacking.py b/tests/functional/u/unbalanced_dict_unpacking.py
new file mode 100644
index 000000000..2c4d3b103
--- /dev/null
+++ b/tests/functional/u/unbalanced_dict_unpacking.py
@@ -0,0 +1,91 @@
+"""Check possible unbalanced dict unpacking """
+# pylint: disable=missing-function-docstring, invalid-name
+# pylint: disable=unused-variable, redefined-outer-name, line-too-long
+
+def dict_vals():
+ a, b, c, d, e, f, g = {1: 2}.values() # [unbalanced-dict-unpacking]
+ return a, b
+
+def dict_keys():
+ a, b, c, d, e, f, g = {1: 2, "hi": 20}.keys() # [unbalanced-dict-unpacking]
+ return a, b
+
+
+def dict_items():
+ tupe_one, tuple_two = {1: 2, "boo": 3}.items()
+ tupe_one, tuple_two, tuple_three = {1: 2, "boo": 3}.items() # [unbalanced-dict-unpacking]
+ return tuple_three
+
+def all_dict():
+ a, b, c, d, e, f, g = {1: 2, 3: 4} # [unbalanced-dict-unpacking]
+ return a
+
+for a, b, c, d, e, f, g in {1: 2}.items(): # [unbalanced-dict-unpacking]
+ pass
+
+for key, value in {1: 2}: # [unbalanced-dict-unpacking]
+ pass
+
+for key, value in {1: 2}.keys(): # [unbalanced-dict-unpacking, consider-iterating-dictionary]
+ pass
+
+for key, value in {1: 2}.values(): # [unbalanced-dict-unpacking]
+ pass
+
+empty = {}
+
+# this should not raise unbalanced-dict because it is valid code using `items()`
+for key, value in empty.items():
+ print(key)
+ print(value)
+
+for key, val in {1: 2}.items():
+ print(key)
+
+populated = {2: 1}
+for key, val in populated.items():
+ print(key)
+
+key, val = populated.items() # [unbalanced-dict-unpacking]
+
+for key, val in {1: 2, 3: 4, 5: 6}.items():
+ print(key)
+
+key, val = {1: 2, 3: 4, 5: 6}.items() # [unbalanced-dict-unpacking]
+
+a, b, c = {} # [unbalanced-dict-unpacking]
+
+for k in {'key': 'value', 1: 2}.items():
+ print(k)
+
+for k, _ in {'key': 'value'}.items():
+ print(k)
+
+for _, _ in {'key': 'value'}.items():
+ print(_)
+
+for _, val in {'key': 'value'}.values(): # [unbalanced-dict-unpacking]
+ print(val)
+
+for key, *val in {'key': 'value', 1: 2}.items():
+ print(key)
+
+for *key, val in {'key': 'value', 1: 2}.items():
+ print(key)
+
+
+for key, *val in {'key': 'value', 1: 2, 20: 21}.values(): # [unbalanced-dict-unpacking]
+ print(key)
+
+for *key, val in {'key': 'value', 1: 2, 20: 21}.values(): # [unbalanced-dict-unpacking]
+ print(key)
+
+one, *others = {1: 2, 3: 4, 5: 6}.items()
+one, *others, last = {1: 2, 3: 4, 5: 6}.items()
+
+one, *others = {1: 2, 3: 4, 5: 6}.values()
+one, *others, last = {1: 2, 3: 4, 5: 6}.values()
+
+_, *others = {1: 2, 3: 4, 5: 6}.items()
+_, *others = {1: 2, 3: 4, 5: 6}.values()
+_, others = {1: 2, 3: 4, 5: 6}.values() # [unbalanced-dict-unpacking]
diff --git a/tests/functional/u/unbalanced_dict_unpacking.txt b/tests/functional/u/unbalanced_dict_unpacking.txt
new file mode 100644
index 000000000..b31d89b40
--- /dev/null
+++ b/tests/functional/u/unbalanced_dict_unpacking.txt
@@ -0,0 +1,16 @@
+unbalanced-dict-unpacking:6:4:6:41:dict_vals:"Possible unbalanced dict unpacking with {1: 2}.values(): left side has 7 labels, right side has 1 value":INFERENCE
+unbalanced-dict-unpacking:10:4:10:49:dict_keys:"Possible unbalanced dict unpacking with {1: 2, 'hi': 20}.keys(): left side has 7 labels, right side has 2 values":INFERENCE
+unbalanced-dict-unpacking:16:4:16:63:dict_items:"Possible unbalanced dict unpacking with {1: 2, 'boo': 3}.items(): left side has 3 labels, right side has 2 values":INFERENCE
+unbalanced-dict-unpacking:20:4:20:38:all_dict:"Possible unbalanced dict unpacking with {1: 2, 3: 4}: left side has 7 labels, right side has 2 values":INFERENCE
+unbalanced-dict-unpacking:23:0:24:8::"Possible unbalanced dict unpacking with {1: 2}.items(): left side has 7 labels, right side has 1 value":INFERENCE
+unbalanced-dict-unpacking:26:0:27:8::"Possible unbalanced dict unpacking with {1: 2}: left side has 2 labels, right side has 1 value":INFERENCE
+consider-iterating-dictionary:29:18:29:31::Consider iterating the dictionary directly instead of calling .keys():INFERENCE
+unbalanced-dict-unpacking:29:0:30:8::"Possible unbalanced dict unpacking with {1: 2}.keys(): left side has 2 labels, right side has 1 value":INFERENCE
+unbalanced-dict-unpacking:32:0:33:8::"Possible unbalanced dict unpacking with {1: 2}.values(): left side has 2 labels, right side has 1 value":INFERENCE
+unbalanced-dict-unpacking:49:0:49:28::"Possible unbalanced dict unpacking with populated.items(): left side has 2 labels, right side has 1 value":INFERENCE
+unbalanced-dict-unpacking:54:0:54:37::"Possible unbalanced dict unpacking with {1: 2, 3: 4, 5: 6}.items(): left side has 2 labels, right side has 3 values":INFERENCE
+unbalanced-dict-unpacking:56:0:56:12::"Possible unbalanced dict unpacking with {}: left side has 3 labels, right side has 0 values":INFERENCE
+unbalanced-dict-unpacking:67:0:68:14::"Possible unbalanced dict unpacking with {'key': 'value'}.values(): left side has 2 labels, right side has 1 value":INFERENCE
+unbalanced-dict-unpacking:77:0:78:14::"Possible unbalanced dict unpacking with {'key': 'value', 1: 2, 20: 21}.values(): left side has 2 labels, right side has 3 values":INFERENCE
+unbalanced-dict-unpacking:80:0:81:14::"Possible unbalanced dict unpacking with {'key': 'value', 1: 2, 20: 21}.values(): left side has 2 labels, right side has 3 values":INFERENCE
+unbalanced-dict-unpacking:91:0:91:39::"Possible unbalanced dict unpacking with {1: 2, 3: 4, 5: 6}.values(): left side has 2 labels, right side has 3 values":INFERENCE
diff --git a/tests/functional/u/unbalanced_tuple_unpacking.txt b/tests/functional/u/unbalanced_tuple_unpacking.txt
index e32069847..651e09840 100644
--- a/tests/functional/u/unbalanced_tuple_unpacking.txt
+++ b/tests/functional/u/unbalanced_tuple_unpacking.txt
@@ -1,9 +1,9 @@
-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.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
-unbalanced-tuple-unpacking:162:0:162:16::"Possible unbalanced tuple unpacking with sequence (1, 2): left side has 3 label(s), right side has 2 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 labels, right side has 3 values":INFERENCE
+unbalanced-tuple-unpacking:17:4:17:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence '[1, 2, 3]': left side has 2 labels, right side has 3 values":INFERENCE
+unbalanced-tuple-unpacking:23:4:23:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence '(1, 2, 3)': left side has 2 labels, right side has 3 values":INFERENCE
+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 labels, right side has 3 values":INFERENCE
+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 labels, right side has 3 values":INFERENCE
+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 labels, right side has 3 values":INFERENCE
+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 labels, right side has 3 values":INFERENCE
+unbalanced-tuple-unpacking:157:0:157:24::"Possible unbalanced tuple unpacking with sequence defined at line 151: left side has 2 labels, right side has 0 values":INFERENCE
+unbalanced-tuple-unpacking:162:0:162:16::"Possible unbalanced tuple unpacking with sequence '(1, 2)': left side has 3 labels, right side has 2 values":INFERENCE
diff --git a/tests/functional/u/unbalanced_tuple_unpacking_py30.py b/tests/functional/u/unbalanced_tuple_unpacking_py30.py
index dd3e4b6d3..c45cccdd1 100644
--- a/tests/functional/u/unbalanced_tuple_unpacking_py30.py
+++ b/tests/functional/u/unbalanced_tuple_unpacking_py30.py
@@ -1,10 +1,11 @@
""" Test that using starred nodes in unpacking
does not trigger a false positive on Python 3.
"""
-
+# pylint: disable=unused-variable
def test():
""" Test that starred expressions don't give false positives. """
first, second, *last = (1, 2, 3, 4)
+ one, two, three, *four = (1, 2, 3, 4)
*last, = (1, 2)
return (first, second, last)
diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py
index 9ab08d595..9d5cf4111 100644
--- a/tests/functional/u/undefined/undefined_loop_variable.py
+++ b/tests/functional/u/undefined/undefined_loop_variable.py
@@ -1,4 +1,4 @@
-# pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call
+# pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call, broad-exception-raised
import sys
diff --git a/tests/functional/u/undefined/undefined_variable_py30.py b/tests/functional/u/undefined/undefined_variable_py30.py
index 0b5aa0422..ff77aaf8e 100644
--- a/tests/functional/u/undefined/undefined_variable_py30.py
+++ b/tests/functional/u/undefined/undefined_variable_py30.py
@@ -89,9 +89,9 @@ def used_before_assignment(*, arg): return arg + 1
# Test for #4021
# https://github.com/PyCQA/pylint/issues/4021
class MetaClass(type):
- def __new__(cls, *args, parameter=None, **kwargs):
+ def __new__(mcs, *args, parameter=None, **kwargs):
print(parameter)
- return super().__new__(cls, *args, **kwargs)
+ return super().__new__(mcs, *args, **kwargs)
class InheritingClass(metaclass=MetaClass, parameter=variable): # [undefined-variable]
diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py
index 61a70d472..8afb5eaf9 100644
--- a/tests/functional/u/undefined/undefined_variable_py38.py
+++ b/tests/functional/u/undefined/undefined_variable_py38.py
@@ -180,3 +180,9 @@ def expression_in_ternary_operator_inside_container_wrong_position():
# Self-referencing
if (z := z): # [used-before-assignment]
z = z + 1
+
+
+if (defined := False):
+ NEVER_DEFINED = 1
+print(defined)
+print(NEVER_DEFINED) # [used-before-assignment]
diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt
index 8460a989f..eb979fad2 100644
--- a/tests/functional/u/undefined/undefined_variable_py38.txt
+++ b/tests/functional/u/undefined/undefined_variable_py38.txt
@@ -8,3 +8,4 @@ used-before-assignment:140:10:140:16:type_annotation_used_improperly_after_compr
used-before-assignment:147:10:147:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH
used-before-assignment:177:12:177:16:expression_in_ternary_operator_inside_container_wrong_position:Using variable 'val3' before assignment:HIGH
used-before-assignment:181:9:181:10::Using variable 'z' before assignment:HIGH
+used-before-assignment:188:6:188:19::Using variable 'NEVER_DEFINED' before assignment:CONTROL_FLOW
diff --git a/tests/functional/u/unidiomatic_typecheck.py b/tests/functional/u/unidiomatic_typecheck.py
index 1e7642046..2a1957d75 100644
--- a/tests/functional/u/unidiomatic_typecheck.py
+++ b/tests/functional/u/unidiomatic_typecheck.py
@@ -1,5 +1,5 @@
"""Warnings for using type(x) == Y or type(x) is Y instead of isinstance(x, Y)."""
-# pylint: disable=missing-docstring,expression-not-assigned,redefined-builtin,invalid-name,unnecessary-lambda-assignment
+# pylint: disable=missing-docstring,expression-not-assigned,redefined-builtin,invalid-name,unnecessary-lambda-assignment,use-dict-literal
def simple_positives():
type(42) is int # [unidiomatic-typecheck]
diff --git a/tests/functional/u/unnecessary/unnecessary_lambda.py b/tests/functional/u/unnecessary/unnecessary_lambda.py
index c12e30bc7..3e5ece2b1 100644
--- a/tests/functional/u/unnecessary/unnecessary_lambda.py
+++ b/tests/functional/u/unnecessary/unnecessary_lambda.py
@@ -1,4 +1,4 @@
-# pylint: disable=undefined-variable, use-list-literal, unnecessary-lambda-assignment
+# pylint: disable=undefined-variable, use-list-literal, unnecessary-lambda-assignment, use-dict-literal
"""test suspicious lambda expressions
"""
diff --git a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py
index e5cb13514..ec5ee22c2 100644
--- a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py
+++ b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py
@@ -130,3 +130,22 @@ def return_start(start):
for i, k in enumerate(series, return_start(20)):
print(series[idx])
+
+for idx, val in enumerate(iterable=series, start=0):
+ print(series[idx]) # [unnecessary-list-index-lookup]
+
+result = [my_list[idx] for idx, val in enumerate(iterable=my_list)] # [unnecessary-list-index-lookup]
+
+for idx, val in enumerate():
+ print(my_list[idx])
+
+class Command:
+ def _get_extra_attrs(self, extra_columns):
+ self.extra_rows_start = 8 # pylint: disable=attribute-defined-outside-init
+ for index, column in enumerate(extra_columns, start=self.extra_rows_start):
+ pass
+
+Y_START = 2
+nums = list(range(20))
+for y, x in enumerate(nums, start=Y_START + 1):
+ pass
diff --git a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.txt b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.txt
index a38788cc8..da658a20d 100644
--- a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.txt
+++ b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.txt
@@ -6,3 +6,5 @@ unnecessary-list-index-lookup:112:10:112:21::Unnecessary list index lookup, use
unnecessary-list-index-lookup:115:10:115:21::Unnecessary list index lookup, use 'val' instead:HIGH
unnecessary-list-index-lookup:119:10:119:21::Unnecessary list index lookup, use 'val' instead:INFERENCE
unnecessary-list-index-lookup:122:10:122:21::Unnecessary list index lookup, use 'val' instead:INFERENCE
+unnecessary-list-index-lookup:135:10:135:21::Unnecessary list index lookup, use 'val' instead:HIGH
+unnecessary-list-index-lookup:137:10:137:22::Unnecessary list index lookup, use 'val' instead:HIGH
diff --git a/tests/functional/u/unpacking/unpacking_non_sequence.txt b/tests/functional/u/unpacking/unpacking_non_sequence.txt
index 3023bfc6b..473acde6f 100644
--- a/tests/functional/u/unpacking/unpacking_non_sequence.txt
+++ b/tests/functional/u/unpacking/unpacking_non_sequence.txt
@@ -1,7 +1,7 @@
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: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.unpacking:UNDEFINED
unpacking-non-sequence:82:0:82:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking.unpacking:UNDEFINED
unpacking-non-sequence:83:0:83:18::Attempting to unpack a non-sequence:UNDEFINED
diff --git a/tests/functional/u/unreachable.py b/tests/functional/u/unreachable.py
index fa40e88f6..0211a6136 100644
--- a/tests/functional/u/unreachable.py
+++ b/tests/functional/u/unreachable.py
@@ -1,5 +1,9 @@
-# pylint: disable=missing-docstring
+# pylint: disable=missing-docstring, broad-exception-raised, too-few-public-methods, redefined-outer-name
+# pylint: disable=consider-using-sys-exit, protected-access
+import os
+import signal
+import sys
def func1():
return 1
@@ -32,3 +36,46 @@ def func6():
return
yield
print("unreachable") # [unreachable]
+
+def func7():
+ sys.exit(1)
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+def func8():
+ signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
+ try:
+ print(1)
+ except KeyboardInterrupt:
+ pass
+
+class FalseExit:
+ def exit(self, number):
+ print(f"False positive this is not sys.exit({number})")
+
+def func_false_exit():
+ sys = FalseExit()
+ sys.exit(1)
+ var = 2 + 2
+ print(var)
+
+def func9():
+ os._exit()
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+def func10():
+ exit()
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+def func11():
+ quit()
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+incognito_function = sys.exit
+def func12():
+ incognito_function()
+ var = 2 + 2 # [unreachable]
+ print(var)
diff --git a/tests/functional/u/unreachable.txt b/tests/functional/u/unreachable.txt
index 491912d99..82f9797aa 100644
--- a/tests/functional/u/unreachable.txt
+++ b/tests/functional/u/unreachable.txt
@@ -1,5 +1,10 @@
-unreachable:6:4:6:24:func1:Unreachable code:HIGH
-unreachable:11:8:11:28:func2:Unreachable code:HIGH
-unreachable:17:8:17:28:func3:Unreachable code:HIGH
-unreachable:21:4:21:16:func4:Unreachable code:HIGH
-unreachable:34:4:34:24:func6:Unreachable code:HIGH
+unreachable:10:4:10:24:func1:Unreachable code:HIGH
+unreachable:15:8:15:28:func2:Unreachable code:HIGH
+unreachable:21:8:21:28:func3:Unreachable code:HIGH
+unreachable:25:4:25:16:func4:Unreachable code:HIGH
+unreachable:38:4:38:24:func6:Unreachable code:HIGH
+unreachable:42:4:42:15:func7:Unreachable code:INFERENCE
+unreachable:64:4:64:15:func9:Unreachable code:INFERENCE
+unreachable:69:4:69:15:func10:Unreachable code:INFERENCE
+unreachable:74:4:74:15:func11:Unreachable code:INFERENCE
+unreachable:80:4:80:15:func12:Unreachable code:INFERENCE
diff --git a/tests/functional/u/unsubscriptable_value.py b/tests/functional/u/unsubscriptable_value.py
index 2a40d647f..79e17903b 100644
--- a/tests/functional/u/unsubscriptable_value.py
+++ b/tests/functional/u/unsubscriptable_value.py
@@ -4,6 +4,7 @@ Checks that value used in a subscript supports subscription
"""
# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,wrong-import-position, unnecessary-comprehension
# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order, redundant-u-string-prefix
+# pylint: disable=use-dict-literal
# primitives
numbers = [1, 2, 3]
diff --git a/tests/functional/u/unsubscriptable_value.txt b/tests/functional/u/unsubscriptable_value.txt
index 24209a64a..d0833b600 100644
--- a/tests/functional/u/unsubscriptable_value.txt
+++ b/tests/functional/u/unsubscriptable_value.txt
@@ -1,15 +1,15 @@
-unsubscriptable-object:31:0:31:18::Value 'NonSubscriptable()' is unsubscriptable:UNDEFINED
-unsubscriptable-object:32:0:32:16::Value 'NonSubscriptable' is unsubscriptable:UNDEFINED
-unsubscriptable-object:34:0:34:13::Value 'Subscriptable' is unsubscriptable:UNDEFINED
-unsubscriptable-object:43:0:43:15::Value 'powers_of_two()' is unsubscriptable:UNDEFINED
-unsubscriptable-object:44:0:44:13::Value 'powers_of_two' is unsubscriptable:UNDEFINED
-unsubscriptable-object:48:0:48:4::Value 'True' is unsubscriptable:UNDEFINED
-unsubscriptable-object:49:0:49:4::Value 'None' is unsubscriptable:UNDEFINED
-unsubscriptable-object:50:0:50:3::Value '8.5' is unsubscriptable:UNDEFINED
-unsubscriptable-object:51:0:51:2::Value '10' is unsubscriptable:UNDEFINED
-unsubscriptable-object:54:0:54:27::Value '{x**2 for x in range(10)}' is unsubscriptable:UNDEFINED
-unsubscriptable-object:55:0:55:12::Value 'set(numbers)' is unsubscriptable:UNDEFINED
-unsubscriptable-object:56:0:56:18::Value 'frozenset(numbers)' is unsubscriptable:UNDEFINED
-unsubscriptable-object:76:0:76:20::Value 'SubscriptableClass()' is unsubscriptable:UNDEFINED
-unsubscriptable-object:83:0:83:4::Value 'test' is unsubscriptable:UNDEFINED
-unsubscriptable-object:126:11:126:18:test_one:Value 'var_one' is unsubscriptable:UNDEFINED
+unsubscriptable-object:32:0:32:18::Value 'NonSubscriptable()' is unsubscriptable:UNDEFINED
+unsubscriptable-object:33:0:33:16::Value 'NonSubscriptable' is unsubscriptable:UNDEFINED
+unsubscriptable-object:35:0:35:13::Value 'Subscriptable' is unsubscriptable:UNDEFINED
+unsubscriptable-object:44:0:44:15::Value 'powers_of_two()' is unsubscriptable:UNDEFINED
+unsubscriptable-object:45:0:45:13::Value 'powers_of_two' is unsubscriptable:UNDEFINED
+unsubscriptable-object:49:0:49:4::Value 'True' is unsubscriptable:UNDEFINED
+unsubscriptable-object:50:0:50:4::Value 'None' is unsubscriptable:UNDEFINED
+unsubscriptable-object:51:0:51:3::Value '8.5' is unsubscriptable:UNDEFINED
+unsubscriptable-object:52:0:52:2::Value '10' is unsubscriptable:UNDEFINED
+unsubscriptable-object:55:0:55:27::Value '{x**2 for x in range(10)}' is unsubscriptable:UNDEFINED
+unsubscriptable-object:56:0:56:12::Value 'set(numbers)' is unsubscriptable:UNDEFINED
+unsubscriptable-object:57:0:57:18::Value 'frozenset(numbers)' is unsubscriptable:UNDEFINED
+unsubscriptable-object:77:0:77:20::Value 'SubscriptableClass()' is unsubscriptable:UNDEFINED
+unsubscriptable-object:84:0:84:4::Value 'test' is unsubscriptable:UNDEFINED
+unsubscriptable-object:127:11:127:18:test_one:Value 'var_one' is unsubscriptable:UNDEFINED
diff --git a/tests/functional/u/unsupported/unsupported_assignment_operation.py b/tests/functional/u/unsupported/unsupported_assignment_operation.py
index 2cac693dd..93e84c020 100644
--- a/tests/functional/u/unsupported/unsupported_assignment_operation.py
+++ b/tests/functional/u/unsupported/unsupported_assignment_operation.py
@@ -3,7 +3,7 @@ Checks that value used in a subscript support assignments
(i.e. defines __setitem__ method).
"""
# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,wrong-import-position,unnecessary-comprehension
-# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order
+# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order,use-dict-literal
# primitives
numbers = [1, 2, 3]
diff --git a/tests/functional/u/unsupported/unsupported_delete_operation.py b/tests/functional/u/unsupported/unsupported_delete_operation.py
index 56a457324..c33a6eb89 100644
--- a/tests/functional/u/unsupported/unsupported_delete_operation.py
+++ b/tests/functional/u/unsupported/unsupported_delete_operation.py
@@ -3,7 +3,7 @@ Checks that value used in a subscript support deletion
(i.e. defines __delitem__ method).
"""
# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,wrong-import-position,unnecessary-comprehension
-# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order
+# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order,use-dict-literal
# primitives
numbers = [1, 2, 3]
diff --git a/tests/functional/u/unused/unused_import.py b/tests/functional/u/unused/unused_import.py
index 20a900fb5..24300587d 100644
--- a/tests/functional/u/unused/unused_import.py
+++ b/tests/functional/u/unused/unused_import.py
@@ -6,18 +6,24 @@ import os.path as test # [unused-import]
from abc import ABCMeta
from sys import argv as test2 # [unused-import]
from sys import flags # [unused-import]
+
# +1:[unused-import,unused-import]
from collections import deque, OrderedDict, Counter
import re, html.parser # [unused-import]
+
DATA = Counter()
# pylint: disable=self-assigning-variable
from fake import SomeName, SomeOtherName # [unused-import]
+
+
class SomeClass:
- SomeName = SomeName # https://bitbucket.org/logilab/pylint/issue/475
+ SomeName = SomeName # https://bitbucket.org/logilab/pylint/issue/475
SomeOtherName = 1
SomeOtherName = SomeOtherName
+
from never import __all__
+
# pylint: disable=wrong-import-order,ungrouped-imports,reimported
import typing
from typing import TYPE_CHECKING
@@ -32,24 +38,27 @@ if t.TYPE_CHECKING:
import xml
-def get_ordered_dict() -> 'collections.OrderedDict':
+def get_ordered_dict() -> "collections.OrderedDict":
return []
-def get_itertools_obj() -> 'itertools.count':
+def get_itertools_obj() -> "itertools.count":
return []
-def use_html_parser() -> 'html.parser.HTMLParser':
+
+def use_html_parser() -> "html.parser.HTMLParser":
return html.parser.HTMLParser
import os # [unused-import]
import sys
+
class NonRegr:
"""???"""
+
def __init__(self):
- print('initialized')
+ print("initialized")
def sys(self):
"""should not get sys from there..."""
@@ -61,7 +70,8 @@ class NonRegr:
def blop(self):
"""yo"""
- print(self, 'blip')
+ print(self, "blip")
+
if TYPE_CHECKING:
if sys.version_info >= (3, 6, 2):
@@ -71,6 +81,7 @@ if TYPE_CHECKING:
from io import TYPE_CHECKING # pylint: disable=no-name-in-module
import trace as t
import astroid as typing
+
TYPE_CHECKING = "red herring"
if TYPE_CHECKING:
@@ -86,5 +97,16 @@ TYPE_CHECKING = False
if TYPE_CHECKING:
import zoneinfo
+
class WithMetaclass(metaclass=ABCMeta):
pass
+
+
+# Regression test for https://github.com/PyCQA/pylint/issues/3765
+# `unused-import` should not be emitted when a type annotation uses quotation marks
+from typing import List
+
+
+class Bee:
+ def get_all_classes(self) -> "List[Bee]":
+ pass
diff --git a/tests/functional/u/unused/unused_import.txt b/tests/functional/u/unused/unused_import.txt
index 857ba6a0d..f242bcb23 100644
--- a/tests/functional/u/unused/unused_import.txt
+++ b/tests/functional/u/unused/unused_import.txt
@@ -3,12 +3,12 @@ unused-import:4:0:4:14::Unused import xml.sax:UNDEFINED
unused-import:5:0:5:22::Unused os.path imported as test:UNDEFINED
unused-import:7:0:7:29::Unused argv imported from sys as test2:UNDEFINED
unused-import:8:0:8:21::Unused flags imported from sys:UNDEFINED
-unused-import:10:0:10:51::Unused OrderedDict imported from collections:UNDEFINED
-unused-import:10:0:10:51::Unused deque imported from collections:UNDEFINED
-unused-import:11:0:11:22::Unused import re:UNDEFINED
-unused-import:14:0:14:40::Unused SomeOtherName imported from fake:UNDEFINED
-unused-import:46:0:46:9::Unused import os:UNDEFINED
-unused-import:77:4:77:19::Unused import unittest:UNDEFINED
-unused-import:79:4:79:15::Unused import uuid:UNDEFINED
-unused-import:81:4:81:19::Unused import warnings:UNDEFINED
-unused-import:83:4:83:21::Unused import compileall:UNDEFINED
+unused-import:11:0:11:51::Unused OrderedDict imported from collections:UNDEFINED
+unused-import:11:0:11:51::Unused deque imported from collections:UNDEFINED
+unused-import:12:0:12:22::Unused import re:UNDEFINED
+unused-import:16:0:16:40::Unused SomeOtherName imported from fake:UNDEFINED
+unused-import:53:0:53:9::Unused import os:UNDEFINED
+unused-import:88:4:88:19::Unused import unittest:UNDEFINED
+unused-import:90:4:90:15::Unused import uuid:UNDEFINED
+unused-import:92:4:92:19::Unused import warnings:UNDEFINED
+unused-import:94:4:94:21::Unused import compileall:UNDEFINED
diff --git a/tests/functional/u/unused/unused_import_py39.py b/tests/functional/u/unused/unused_import_py39.py
new file mode 100644
index 000000000..2a897b174
--- /dev/null
+++ b/tests/functional/u/unused/unused_import_py39.py
@@ -0,0 +1,10 @@
+"""
+Test that a constant parameter of `typing.Annotated` does not emit `unused-import`.
+`typing.Annotated` was introduced in Python version 3.9
+"""
+
+from pathlib import Path # [unused-import]
+import typing as t
+
+
+example: t.Annotated[str, "Path"] = "/foo/bar"
diff --git a/tests/functional/u/unused/unused_import_py39.rc b/tests/functional/u/unused/unused_import_py39.rc
new file mode 100644
index 000000000..16b75eea7
--- /dev/null
+++ b/tests/functional/u/unused/unused_import_py39.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.9
diff --git a/tests/functional/u/unused/unused_import_py39.txt b/tests/functional/u/unused/unused_import_py39.txt
new file mode 100644
index 000000000..50e5ad5a9
--- /dev/null
+++ b/tests/functional/u/unused/unused_import_py39.txt
@@ -0,0 +1 @@
+unused-import:6:0:6:24::Unused Path imported from pathlib:UNDEFINED
diff --git a/tests/functional/u/unused/unused_variable.py b/tests/functional/u/unused/unused_variable.py
index 92a329f2f..0058516c9 100644
--- a/tests/functional/u/unused/unused_variable.py
+++ b/tests/functional/u/unused/unused_variable.py
@@ -1,4 +1,4 @@
-# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, import-outside-toplevel, fixme, line-too-long
+# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, import-outside-toplevel, fixme, line-too-long, broad-exception-raised
def test_regression_737():
import xml # [unused-import]
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 d316d5acd..2ace15d7e 100644
--- a/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt
+++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt
@@ -1,32 +1,32 @@
-use-implicit-booleaness-not-comparison:14:7:14:21:github_issue_4774:'bad_list == []' can be simplified to 'not bad_list' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:22:3:22:20::'empty_tuple == ()' can be simplified to 'not empty_tuple' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:25:3:25:19::'empty_list == []' can be simplified to 'not empty_list' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:28:3:28:19::'empty_dict == {}' can be simplified to 'not empty_dict' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:31:3:31:20::'empty_tuple == ()' can be simplified to 'not empty_tuple' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:34:3:34:19::'empty_list == []' can be simplified to 'not empty_list' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:37:3:37:19::'empty_dict == {}' can be simplified to 'not empty_dict' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:42:11:42:18:bad_tuple_return:'t == ()' can be simplified to 'not t' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:46:11:46:18:bad_list_return:'b == []' can be simplified to 'not b' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:50:11:50:18:bad_dict_return:'c == {}' can be simplified to 'not c' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:52:7:52:24::'empty_tuple == ()' can be simplified to 'not empty_tuple' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:53:7:53:23::'empty_list == []' can be simplified to 'not empty_list' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:54:7:54:23::'empty_dict != {}' can be simplified to 'empty_dict' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:55:7:55:23::'empty_tuple < ()' can be simplified to 'not empty_tuple' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:56:7:56:23::'empty_list <= []' can be simplified to 'not empty_list' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:57:7:57:23::'empty_tuple > ()' can be simplified to 'not empty_tuple' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:58:7:58:23::'empty_list >= []' can be simplified to 'not empty_list' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:83:3:83:10::'a == []' can be simplified to 'not a' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:95:3:95:10::'e == []' can be simplified to 'not e' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:95:15:95:22::'f == {}' can be simplified to 'not f' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:133:3:133:14::'A.lst == []' can be simplified to 'not A.lst' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:137:3:137:14::'A.lst == []' can be simplified to 'not A.lst' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:141:3:141:20::'A.test(...) == []' can be simplified to 'not A.test(...)' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:149:3:149:24::'test_function(...) == []' can be simplified to 'not test_function(...)' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:156:3:156:20::'numpy_array == []' can be simplified to 'not numpy_array' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:158:3:158:20::'numpy_array != []' can be simplified to 'numpy_array' as an empty sequence is falsey:UNDEFINED
-use-implicit-booleaness-not-comparison:160:3:160:20::'numpy_array >= ()' can be simplified to 'not numpy_array' as an empty sequence is falsey:UNDEFINED
-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
+use-implicit-booleaness-not-comparison:14:7:14:21:github_issue_4774:'bad_list == []' can be simplified to 'not bad_list' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:22:3:22:20::'empty_tuple == ()' can be simplified to 'not empty_tuple' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:25:3:25:19::'empty_list == []' can be simplified to 'not empty_list' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:28:3:28:19::'empty_dict == {}' can be simplified to 'not empty_dict' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:31:3:31:20::'empty_tuple == ()' can be simplified to 'not empty_tuple' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:34:3:34:19::'empty_list == []' can be simplified to 'not empty_list' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:37:3:37:19::'empty_dict == {}' can be simplified to 'not empty_dict' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:42:11:42:18:bad_tuple_return:'t == ()' can be simplified to 'not t' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:46:11:46:18:bad_list_return:'b == []' can be simplified to 'not b' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:50:11:50:18:bad_dict_return:'c == {}' can be simplified to 'not c' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:52:7:52:24::'empty_tuple == ()' can be simplified to 'not empty_tuple' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:53:7:53:23::'empty_list == []' can be simplified to 'not empty_list' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:54:7:54:23::'empty_dict != {}' can be simplified to 'empty_dict' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:55:7:55:23::'empty_tuple < ()' can be simplified to 'not empty_tuple' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:56:7:56:23::'empty_list <= []' can be simplified to 'not empty_list' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:57:7:57:23::'empty_tuple > ()' can be simplified to 'not empty_tuple' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:58:7:58:23::'empty_list >= []' can be simplified to 'not empty_list' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:83:3:83:10::'a == []' can be simplified to 'not a' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:95:3:95:10::'e == []' can be simplified to 'not e' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:95:15:95:22::'f == {}' can be simplified to 'not f' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:133:3:133:14::'A.lst == []' can be simplified to 'not A.lst' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:137:3:137:14::'A.lst == []' can be simplified to 'not A.lst' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:141:3:141:20::'A.test(...) == []' can be simplified to 'not A.test(...)' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:149:3:149:24::'test_function(...) == []' can be simplified to 'not test_function(...)' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:156:3:156:20::'numpy_array == []' can be simplified to 'not numpy_array' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:158:3:158:20::'numpy_array != []' can be simplified to 'numpy_array' as an empty list is falsey:HIGH
+use-implicit-booleaness-not-comparison:160:3:160:20::'numpy_array >= ()' can be simplified to 'not numpy_array' as an empty tuple is falsey:HIGH
+use-implicit-booleaness-not-comparison:185:3:185:13::'data == {}' can be simplified to 'not data' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:187:3:187:13::'data != {}' can be simplified to 'data' as an empty dict is falsey:HIGH
+use-implicit-booleaness-not-comparison:195:3:195:26::'long_test == {}' can be simplified to 'not long_test' as an empty dict is falsey:HIGH
+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 dict is falsey:HIGH
+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 dict is falsey:HIGH
diff --git a/tests/functional/u/use/use_implicit_booleaness_not_len.txt b/tests/functional/u/use/use_implicit_booleaness_not_len.txt
index 11412f5b2..85917de82 100644
--- a/tests/functional/u/use/use_implicit_booleaness_not_len.txt
+++ b/tests/functional/u/use/use_implicit_booleaness_not_len.txt
@@ -1,26 +1,26 @@
-use-implicit-booleaness-not-len:4:3:4:14::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:7:3:7:18::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:11:9:11:34::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:14:11:14:22::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
+use-implicit-booleaness-not-len:4:3:4:14::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:7:3:7:18::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:11:9:11:34::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:14:11:14:22::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
comparison-of-constants:39:3:39:28::"Comparison between constants: '0 < 1' has a constant value":HIGH
-use-implicit-booleaness-not-len:56:5:56:16::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:61:5:61:20::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:64:6:64:17::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:67:6:67:21::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:70:12:70:23::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:73:6:73:21::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:96:11:96:20:github_issue_1331_v2:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:99:11:99:20:github_issue_1331_v3:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:102:17:102:26:github_issue_1331_v4:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:104:9:104:15::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:105:9:105:20::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:124:11:124:34:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:125:11:125:39:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:126:11:126:24:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:127:11:127:35:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:128:11:128:33:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:129:11:129:41:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:130:11:130:43:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
-use-implicit-booleaness-not-len:171:11:171:42:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:UNDEFINED
+use-implicit-booleaness-not-len:56:5:56:16::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:61:5:61:20::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:64:6:64:17::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:67:6:67:21::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:70:12:70:23::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:73:6:73:21::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:96:11:96:20:github_issue_1331_v2:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:99:11:99:20:github_issue_1331_v3:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:102:17:102:26:github_issue_1331_v4:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:104:9:104:15::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:105:9:105:20::Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:124:11:124:34:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:125:11:125:39:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:126:11:126:24:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:127:11:127:35:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:128:11:128:33:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:129:11:129:41:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:HIGH
+use-implicit-booleaness-not-len:130:11:130:43:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
+use-implicit-booleaness-not-len:171:11:171:42:github_issue_1879:Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty:INFERENCE
undefined-variable:183:11:183:24:github_issue_4215:Undefined variable 'undefined_var':UNDEFINED
undefined-variable:185:11:185:25:github_issue_4215:Undefined variable 'undefined_var2':UNDEFINED
diff --git a/tests/functional/u/use/use_literal_dict.py b/tests/functional/u/use/use_literal_dict.py
index 3377b4e63..598b382bd 100644
--- a/tests/functional/u/use/use_literal_dict.py
+++ b/tests/functional/u/use/use_literal_dict.py
@@ -1,7 +1,46 @@
-# pylint: disable=missing-docstring, invalid-name
+# pylint: disable=missing-docstring, invalid-name, disallowed-name, unused-argument, too-few-public-methods
x = dict() # [use-dict-literal]
-x = dict(a="1", b=None, c=3)
+x = dict(a="1", b=None, c=3) # [use-dict-literal]
x = dict(zip(["a", "b", "c"], [1, 2, 3]))
x = {}
x = {"a": 1, "b": 2, "c": 3}
+x = dict(**x) # [use-dict-literal]
+
+def bar(boo: bool = False):
+ return 1
+
+x = dict(foo=bar()) # [use-dict-literal]
+
+baz = {"e": 9, "f": 1}
+
+dict( # [use-dict-literal]
+ **baz,
+ suggestions=list(
+ bar(
+ boo=True,
+ )
+ ),
+)
+
+class SomeClass:
+ prop: dict = {"a": 1}
+
+inst = SomeClass()
+
+dict( # [use-dict-literal]
+ url="/foo",
+ **inst.prop,
+)
+
+dict( # [use-dict-literal]
+ Lorem="ipsum",
+ dolor="sit",
+ amet="consectetur",
+ adipiscing="elit",
+ sed="do",
+ eiusmod="tempor",
+ incididunt="ut",
+ labore="et",
+ dolore="magna",
+)
diff --git a/tests/functional/u/use/use_literal_dict.txt b/tests/functional/u/use/use_literal_dict.txt
index cbcb83f24..145766479 100644
--- a/tests/functional/u/use/use_literal_dict.txt
+++ b/tests/functional/u/use/use_literal_dict.txt
@@ -1 +1,7 @@
-use-dict-literal:3:4:3:10::Consider using {} instead of dict():UNDEFINED
+use-dict-literal:3:4:3:10::Consider using '{}' instead of a call to 'dict'.:INFERENCE
+use-dict-literal:4:4:4:28::"Consider using '{""a"": '1', ""b"": None, ""c"": 3}' instead of a call to 'dict'.":INFERENCE
+use-dict-literal:8:4:8:13::Consider using '{**x}' instead of a call to 'dict'.:INFERENCE
+use-dict-literal:13:4:13:19::"Consider using '{""foo"": bar()}' instead of a call to 'dict'.":INFERENCE
+use-dict-literal:17:0:24:1::"Consider using '{""suggestions"": list(bar(boo=True)), **baz}' instead of a call to 'dict'.":INFERENCE
+use-dict-literal:31:0:34:1::"Consider using '{""url"": '/foo', **inst.prop}' instead of a call to 'dict'.":INFERENCE
+use-dict-literal:36:0:46:1::"Consider using '{""Lorem"": 'ipsum', ""dolor"": 'sit', ""amet"": 'consectetur', ""adipiscing"": 'elit', ... }' instead of a call to 'dict'.":INFERENCE
diff --git a/tests/functional/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py
index 473af2b34..f8ed651b5 100644
--- a/tests/functional/u/used/used_before_assignment.py
+++ b/tests/functional/u/used/used_before_assignment.py
@@ -25,3 +25,84 @@ def redefine_time_import_with_global():
global time # pylint: disable=invalid-name
print(time.time())
import time
+
+
+# Control flow cases
+FALSE = False
+if FALSE:
+ VAR2 = True
+if VAR2: # [used-before-assignment]
+ pass
+
+if FALSE: # pylint: disable=simplifiable-if-statement
+ VAR3 = True
+elif VAR2:
+ VAR3 = True
+else:
+ VAR3 = False
+if VAR3:
+ pass
+
+if FALSE:
+ VAR4 = True
+elif VAR2:
+ pass
+else:
+ VAR4 = False
+if VAR4: # [used-before-assignment]
+ pass
+
+if FALSE:
+ VAR5 = True
+elif VAR2:
+ if FALSE: # pylint: disable=simplifiable-if-statement
+ VAR5 = True
+ else:
+ VAR5 = True
+if VAR5:
+ pass
+
+if FALSE:
+ VAR6 = False
+if VAR6: # [used-before-assignment]
+ pass
+
+
+# Nested try
+if FALSE:
+ try:
+ VAR7 = True
+ except ValueError:
+ pass
+else:
+ VAR7 = False
+if VAR7:
+ pass
+
+if FALSE:
+ try:
+ VAR8 = True
+ except ValueError as ve:
+ print(ve)
+ raise
+else:
+ VAR8 = False
+if VAR8:
+ pass
+
+if FALSE:
+ for i in range(5):
+ VAR9 = i
+ break
+print(VAR9)
+
+if FALSE:
+ with open(__name__, encoding='utf-8') as f:
+ VAR10 = __name__
+print(VAR10) # [used-before-assignment]
+
+for num in [0, 1]:
+ VAR11 = num
+ if VAR11:
+ VAR12 = False
+print(VAR12)
diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt
index c48b3ed7c..70153f39a 100644
--- a/tests/functional/u/used/used_before_assignment.txt
+++ b/tests/functional/u/used/used_before_assignment.txt
@@ -2,3 +2,7 @@ used-before-assignment:5:19:5:22::Using variable 'MSG' before assignment:HIGH
used-before-assignment:7:20:7:24::Using variable 'MSG2' before assignment:HIGH
used-before-assignment:10:4:10:9:outer:Using variable 'inner' before assignment:HIGH
used-before-assignment:20:10:20:14:redefine_time_import:Using variable 'time' before assignment:HIGH
+used-before-assignment:34:3:34:7::Using variable 'VAR2' before assignment:CONTROL_FLOW
+used-before-assignment:52:3:52:7::Using variable 'VAR4' before assignment:CONTROL_FLOW
+used-before-assignment:67:3:67:7::Using variable 'VAR6' before assignment:CONTROL_FLOW
+used-before-assignment:102:6:102:11::Using variable 'VAR10' before assignment:CONTROL_FLOW
diff --git a/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py b/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py
index feae58dbe..2321afed7 100644
--- a/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py
+++ b/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py
@@ -1,4 +1,5 @@
"""Homonym between filtered comprehension and assignment in except block."""
+# pylint: disable=broad-exception-raised
def func():
"""https://github.com/PyCQA/pylint/issues/5586"""
diff --git a/tests/functional/u/used/used_before_assignment_conditional.py b/tests/functional/u/used/used_before_assignment_conditional.py
index b5d16925e..b024d2898 100644
--- a/tests/functional/u/used/used_before_assignment_conditional.py
+++ b/tests/functional/u/used/used_before_assignment_conditional.py
@@ -1,4 +1,5 @@
"""used-before-assignment cases involving IF conditions"""
+
if 1 + 1 == 2:
x = x + 1 # [used-before-assignment]
diff --git a/tests/functional/u/used/used_before_assignment_conditional.txt b/tests/functional/u/used/used_before_assignment_conditional.txt
index 56626f0fe..a65f9f738 100644
--- a/tests/functional/u/used/used_before_assignment_conditional.txt
+++ b/tests/functional/u/used/used_before_assignment_conditional.txt
@@ -1,2 +1,2 @@
-used-before-assignment:3:8:3:9::Using variable 'x' before assignment:HIGH
-used-before-assignment:5:3:5:4::Using variable 'y' before assignment:HIGH
+used-before-assignment:4:8:4:9::Using variable 'x' before assignment:HIGH
+used-before-assignment:6:3:6:4::Using variable 'y' before assignment:HIGH
diff --git a/tests/functional/u/used/used_before_assignment_else_return.py b/tests/functional/u/used/used_before_assignment_else_return.py
index a5dc5c23b..a7e58bb61 100644
--- a/tests/functional/u/used/used_before_assignment_else_return.py
+++ b/tests/functional/u/used/used_before_assignment_else_return.py
@@ -1,5 +1,6 @@
"""If the else block returns, it is generally safe to rely on assignments in the except."""
-
+# pylint: disable=missing-function-docstring, invalid-name
+import sys
def valid():
"""https://github.com/PyCQA/pylint/issues/6790"""
@@ -59,3 +60,15 @@ def invalid_4():
else:
print(error) # [used-before-assignment]
return
+
+def valid_exit():
+ try:
+ pass
+ except SystemExit as e:
+ lint_result = e.code
+ else:
+ sys.exit("Bad")
+ if lint_result != 0:
+ sys.exit("Error is 0.")
+
+ print(lint_result)
diff --git a/tests/functional/u/used/used_before_assignment_else_return.txt b/tests/functional/u/used/used_before_assignment_else_return.txt
index d7d1835f4..5ef28cfd6 100644
--- a/tests/functional/u/used/used_before_assignment_else_return.txt
+++ b/tests/functional/u/used/used_before_assignment_else_return.txt
@@ -1,4 +1,4 @@
-used-before-assignment:25:14:25:19:invalid:Using variable 'error' before assignment:CONTROL_FLOW
-used-before-assignment:38:14:38:19:invalid_2:Using variable 'error' before assignment:CONTROL_FLOW
-used-before-assignment:50:14:50:19:invalid_3:Using variable 'error' before assignment:CONTROL_FLOW
-used-before-assignment:60:14:60:19:invalid_4:Using variable 'error' before assignment:CONTROL_FLOW
+used-before-assignment:26:14:26:19:invalid:Using variable 'error' before assignment:CONTROL_FLOW
+used-before-assignment:39:14:39:19:invalid_2:Using variable 'error' before assignment:CONTROL_FLOW
+used-before-assignment:51:14:51:19:invalid_3:Using variable 'error' before assignment:CONTROL_FLOW
+used-before-assignment:61:14:61:19:invalid_4:Using variable 'error' before assignment:CONTROL_FLOW
diff --git a/tests/functional/u/used/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
index 086ad0554..c83a48473 100644
--- a/tests/functional/u/used/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
@@ -2,7 +2,7 @@
try blocks with return statements.
See: https://github.com/PyCQA/pylint/issues/5500.
"""
-# pylint: disable=inconsistent-return-statements
+# pylint: disable=inconsistent-return-statements,broad-exception-raised
def function():
@@ -77,7 +77,7 @@ def func_ok5(var):
def func_ok6(var):
"""Define 'msg' in one handler nested under if block."""
- err_message = False
+ err_message = "Division by 0"
try:
return 1 / var.some_other_func()
except ZeroDivisionError:
diff --git a/tests/functional/u/used/used_before_assignment_issue626.txt b/tests/functional/u/used/used_before_assignment_issue626.txt
index 1ee575ba3..3d0e57246 100644
--- a/tests/functional/u/used/used_before_assignment_issue626.txt
+++ b/tests/functional/u/used/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:HIGH
+used-before-assignment:8:10:8:11:main1:Using variable 'e' before assignment:CONTROL_FLOW
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:HIGH
+used-before-assignment:44:10:44:11:main4:Using variable 'e' before assignment:CONTROL_FLOW
diff --git a/tests/functional/u/used/used_before_assignment_nonlocal.py b/tests/functional/u/used/used_before_assignment_nonlocal.py
index 270b72d22..18e16177d 100644
--- a/tests/functional/u/used/used_before_assignment_nonlocal.py
+++ b/tests/functional/u/used/used_before_assignment_nonlocal.py
@@ -88,3 +88,21 @@ def type_annotation_never_gets_value_despite_nonlocal():
nonlocal some_num
inner()
print(some_num) # [used-before-assignment]
+
+
+def inner_function_lacks_access_to_outer_args(args):
+ """Check homonym between inner function and outer function names"""
+ def inner():
+ print(args) # [used-before-assignment]
+ args = []
+ inner()
+ print(args)
+
+
+def inner_function_ok(args):
+ """Explicitly redefined homonym defined before is OK."""
+ def inner():
+ args = []
+ print(args)
+ inner()
+ print(args)
diff --git a/tests/functional/u/used/used_before_assignment_nonlocal.txt b/tests/functional/u/used/used_before_assignment_nonlocal.txt
index 3e5045f27..2bdbf2fe1 100644
--- a/tests/functional/u/used/used_before_assignment_nonlocal.txt
+++ b/tests/functional/u/used/used_before_assignment_nonlocal.txt
@@ -5,3 +5,4 @@ used-before-assignment:33:22:33:32:test_fail4:Using variable 'test_fail5' before
used-before-assignment:33:44:33:53:test_fail4:Using variable 'undefined' before assignment:HIGH
used-before-assignment:39:18:39:28:test_fail5:Using variable 'undefined1' before assignment:HIGH
used-before-assignment:90:10:90:18:type_annotation_never_gets_value_despite_nonlocal:Using variable 'some_num' before assignment:HIGH
+used-before-assignment:96:14:96:18:inner_function_lacks_access_to_outer_args.inner:Using variable 'args' before assignment:HIGH
diff --git a/tests/lint/test_pylinter.py b/tests/lint/test_pylinter.py
index 731af5b0a..1d0f43819 100644
--- a/tests/lint/test_pylinter.py
+++ b/tests/lint/test_pylinter.py
@@ -2,12 +2,14 @@
# 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 Any, NoReturn
from unittest import mock
from unittest.mock import patch
import pytest
-from py._path.local import LocalPath # type: ignore[import]
+from _pytest.recwarn import WarningsRecorder
from pytest import CaptureFixture
from pylint.lint.pylinter import PyLinter
@@ -20,39 +22,39 @@ def raise_exception(*args: Any, **kwargs: Any) -> NoReturn:
@patch.object(FileState, "iter_spurious_suppression_messages", raise_exception)
def test_crash_in_file(
- linter: PyLinter, capsys: CaptureFixture, tmpdir: LocalPath
+ linter: PyLinter, capsys: CaptureFixture[str], tmp_path: Path
) -> None:
with pytest.warns(DeprecationWarning):
args = linter.load_command_line_configuration([__file__])
- linter.crash_file_path = str(tmpdir / "pylint-crash-%Y")
+ linter.crash_file_path = str(tmp_path / "pylint-crash-%Y")
linter.check(args)
out, err = capsys.readouterr()
assert not out
assert not err
- files = tmpdir.listdir()
+ files = os.listdir(tmp_path)
assert len(files) == 1
assert "pylint-crash-20" in str(files[0])
assert any(m.symbol == "fatal" for m in linter.reporter.messages)
-def test_check_deprecation(linter: PyLinter, recwarn):
+def test_check_deprecation(linter: PyLinter, recwarn: WarningsRecorder) -> None:
linter.check("myfile.py")
msg = recwarn.pop()
assert "check function will only accept sequence" in str(msg)
def test_crash_during_linting(
- linter: PyLinter, capsys: CaptureFixture[str], tmpdir: LocalPath
+ linter: PyLinter, capsys: CaptureFixture[str], tmp_path: Path
) -> None:
with mock.patch(
"pylint.lint.PyLinter.check_astroid_module", side_effect=RuntimeError
):
- linter.crash_file_path = str(tmpdir / "pylint-crash-%Y")
+ linter.crash_file_path = str(tmp_path / "pylint-crash-%Y")
linter.check([__file__])
out, err = capsys.readouterr()
assert not out
assert not err
- files = tmpdir.listdir()
+ files = os.listdir(tmp_path)
assert len(files) == 1
assert "pylint-crash-20" in str(files[0])
assert any(m.symbol == "astroid-error" for m in linter.reporter.messages)
diff --git a/tests/lint/test_utils.py b/tests/lint/test_utils.py
index 6cc79f18b..872919f72 100644
--- a/tests/lint/test_utils.py
+++ b/tests/lint/test_utils.py
@@ -18,7 +18,7 @@ def test_prepare_crash_report(tmp_path: PosixPath) -> None:
with open(python_file, "w", encoding="utf8") as f:
f.write(python_content)
try:
- raise Exception(exception_content)
+ raise Exception(exception_content) # pylint: disable=broad-exception-raised
except Exception as ex: # pylint: disable=broad-except
template_path = prepare_crash_report(
ex, str(python_file), str(tmp_path / "pylint-crash-%Y.txt")
diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py
index 3336c47bd..88f058b1e 100644
--- a/tests/lint/unittest_expand_modules.py
+++ b/tests/lint/unittest_expand_modules.py
@@ -12,7 +12,7 @@ import pytest
from pylint.checkers import BaseChecker
from pylint.lint.expand_modules import _is_in_ignore_list_re, expand_modules
from pylint.testutils import CheckerTestCase, set_config
-from pylint.typing import MessageDefinitionTuple
+from pylint.typing import MessageDefinitionTuple, ModuleDescriptionDict
def test__is_in_ignore_list_re_match() -> None:
@@ -134,9 +134,12 @@ class TestExpandModules(CheckerTestCase):
],
)
@set_config(ignore_paths="")
- def test_expand_modules(self, files_or_modules, expected):
+ def test_expand_modules(
+ self, files_or_modules: list[str], expected: dict[str, ModuleDescriptionDict]
+ ) -> None:
"""Test expand_modules with the default value of ignore-paths."""
- ignore_list, ignore_list_re = [], []
+ ignore_list: list[str] = []
+ ignore_list_re: list[re.Pattern[str]] = []
modules, errors = expand_modules(
files_or_modules,
ignore_list,
@@ -161,7 +164,7 @@ class TestExpandModules(CheckerTestCase):
)
@set_config(ignore_paths="")
def test_expand_modules_deduplication(
- self, files_or_modules: list[str], expected
+ self, files_or_modules: list[str], expected: dict[str, ModuleDescriptionDict]
) -> None:
"""Test expand_modules deduplication."""
ignore_list: list[str] = []
@@ -189,9 +192,12 @@ class TestExpandModules(CheckerTestCase):
],
)
@set_config(ignore_paths=".*/lint/.*")
- def test_expand_modules_with_ignore(self, files_or_modules, expected):
+ def test_expand_modules_with_ignore(
+ self, files_or_modules: list[str], expected: dict[str, ModuleDescriptionDict]
+ ) -> None:
"""Test expand_modules with a non-default value of ignore-paths."""
- ignore_list, ignore_list_re = [], []
+ ignore_list: list[str] = []
+ ignore_list_re: list[re.Pattern[str]] = []
modules, errors = expand_modules(
files_or_modules,
ignore_list,
diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py
index 057b322d5..25df951df 100644
--- a/tests/lint/unittest_lint.py
+++ b/tests/lint/unittest_lint.py
@@ -12,17 +12,18 @@ import os
import re
import sys
import tempfile
-from collections.abc import Iterable, Iterator
+from collections.abc import Iterator
from contextlib import contextmanager
from importlib import reload
from io import StringIO
from os import chdir, getcwd
from os.path import abspath, dirname, join, sep
from pathlib import Path
-from shutil import copytree, rmtree
+from shutil import copy, rmtree
import platformdirs
import pytest
+from astroid import nodes
from pytest import CaptureFixture
from pylint import checkers, config, exceptions, interfaces, lint, testutils
@@ -74,7 +75,7 @@ def fake_home() -> Iterator[str]:
rmtree(folder, ignore_errors=True)
-def remove(file):
+def remove(file: str) -> None:
try:
os.remove(file)
except OSError:
@@ -108,9 +109,9 @@ def tempdir() -> Iterator[str]:
@pytest.fixture
-def fake_path() -> Iterator[Iterable[str]]:
+def fake_path() -> Iterator[list[str]]:
orig = list(sys.path)
- fake: Iterable[str] = ["1", "2", "3"]
+ fake = ["1", "2", "3"]
sys.path[:] = fake
yield fake
sys.path[:] = orig
@@ -145,7 +146,7 @@ def test_one_arg(fake_path: list[str], case: list[str]) -> None:
["a", "a/c/__init__.py"],
],
)
-def test_two_similar_args(fake_path, case):
+def test_two_similar_args(fake_path: list[str], case: list[str]) -> None:
with tempdir() as chroot:
create_files(["a/b/__init__.py", "a/c/__init__.py"])
expected = [join(chroot, "a")] + fake_path
@@ -164,7 +165,7 @@ def test_two_similar_args(fake_path, case):
["a/b/c", "a", "a/b/c", "a/e", "a"],
],
)
-def test_more_args(fake_path, case):
+def test_more_args(fake_path: list[str], case: list[str]) -> None:
with tempdir() as chroot:
create_files(["a/b/c/__init__.py", "a/d/__init__.py", "a/e/f.py"])
expected = [
@@ -179,12 +180,12 @@ def test_more_args(fake_path, case):
@pytest.fixture(scope="module")
-def disable():
+def disable() -> list[str]:
return ["I"]
@pytest.fixture(scope="module")
-def reporter():
+def reporter() -> type[testutils.GenericTestReporter]:
return testutils.GenericTestReporter
@@ -208,7 +209,7 @@ def test_pylint_visit_method_taken_in_account(linter: PyLinter) -> None:
msgs = {"W9999": ("", "custom", "")}
@only_required_for_messages("custom")
- def visit_class(self, _):
+ def visit_class(self, _: nodes.ClassDef) -> None:
pass
linter.register_checker(CustomChecker(linter))
@@ -533,7 +534,9 @@ def test_load_plugin_path_manipulation_case_6() -> None:
the config file has run. This is not supported, and was previously a silent
failure. This test ensures a ``bad-plugin-value`` message is emitted.
"""
- dummy_plugin_path = abspath(join(REGRTEST_DATA_DIR, "dummy_plugin"))
+ dummy_plugin_path = abspath(
+ join(REGRTEST_DATA_DIR, "dummy_plugin", "dummy_plugin.py")
+ )
with fake_home() as home_path:
# construct a basic rc file that just modifies the path
pylintrc_file = join(home_path, "pylintrc")
@@ -546,7 +549,7 @@ def test_load_plugin_path_manipulation_case_6() -> None:
]
)
- copytree(dummy_plugin_path, join(home_path, "copy_dummy"))
+ copy(dummy_plugin_path, join(home_path, "copy_dummy.py"))
# To confirm we won't load this module _without_ the init hook running.
assert home_path not in sys.path
@@ -565,7 +568,7 @@ def test_load_plugin_path_manipulation_case_6() -> None:
assert run._rcfile == pylintrc_file
assert home_path in sys.path
# The module should not be loaded
- assert not any(ch.name == "copy_dummy" for ch in run.linter.get_checkers())
+ assert not any(ch.name == "dummy_plugin" for ch in run.linter.get_checkers())
# There should be a bad-plugin-message for this module
assert len(run.linter.reporter.messages) == 1
@@ -602,7 +605,9 @@ def test_load_plugin_path_manipulation_case_3() -> None:
the config file has run. This is not supported, and was previously a silent
failure. This test ensures a ``bad-plugin-value`` message is emitted.
"""
- dummy_plugin_path = abspath(join(REGRTEST_DATA_DIR, "dummy_plugin"))
+ dummy_plugin_path = abspath(
+ join(REGRTEST_DATA_DIR, "dummy_plugin", "dummy_plugin.py")
+ )
with fake_home() as home_path:
# construct a basic rc file that just modifies the path
pylintrc_file = join(home_path, "pylintrc")
@@ -614,7 +619,7 @@ def test_load_plugin_path_manipulation_case_3() -> None:
]
)
- copytree(dummy_plugin_path, join(home_path, "copy_dummy"))
+ copy(dummy_plugin_path, join(home_path, "copy_dummy.py"))
# To confirm we won't load this module _without_ the init hook running.
assert home_path not in sys.path
@@ -633,7 +638,7 @@ def test_load_plugin_path_manipulation_case_3() -> None:
assert run._rcfile == pylintrc_file
assert home_path in sys.path
# The module should not be loaded
- assert not any(ch.name == "copy_dummy" for ch in run.linter.get_checkers())
+ assert not any(ch.name == "dummy_plugin" for ch in run.linter.get_checkers())
# There should be a bad-plugin-message for this module
assert len(run.linter.reporter.messages) == 1
@@ -661,49 +666,137 @@ def test_load_plugin_path_manipulation_case_3() -> None:
sys.path.remove(home_path)
-def test_load_plugin_command_line_before_init_hook() -> None:
- """Check that the order of 'load-plugins' and 'init-hook' doesn't affect execution."""
- regrtest_data_dir_abs = abspath(REGRTEST_DATA_DIR)
+@pytest.mark.usefixtures("pop_pylintrc")
+def test_load_plugin_pylintrc_order_independent() -> None:
+ """Test that the init-hook is called independent of the order in a config file.
- run = Run(
- [
- "--load-plugins",
- "dummy_plugin",
- "--init-hook",
- f'import sys; sys.path.append("{regrtest_data_dir_abs}")',
- join(REGRTEST_DATA_DIR, "empty.py"),
- ],
- exit=False,
- )
- assert (
- len([ch.name for ch in run.linter.get_checkers() if ch.name == "dummy_plugin"])
- == 2
+ We want to ensure that any path manipulation in init hook
+ that means a plugin can load (as per GitHub Issue #7264 Cases 4+7)
+ runs before the load call, regardless of the order of lines in the
+ pylintrc file.
+ """
+ dummy_plugin_path = abspath(
+ join(REGRTEST_DATA_DIR, "dummy_plugin", "dummy_plugin.py")
)
- # Necessary as the executed init-hook modifies sys.path
- sys.path.remove(regrtest_data_dir_abs)
+ with fake_home() as home_path:
+ copy(dummy_plugin_path, join(home_path, "copy_dummy.py"))
+ # construct a basic rc file that just modifies the path
+ pylintrc_file_before = join(home_path, "pylintrc_before")
+ with open(pylintrc_file_before, "w", encoding="utf8") as out:
+ out.writelines(
+ [
+ "[MASTER]\n",
+ f"init-hook=\"import sys; sys.path.append(r'{home_path}')\"\n",
+ "load-plugins=copy_dummy\n",
+ ]
+ )
+ pylintrc_file_after = join(home_path, "pylintrc_after")
+ with open(pylintrc_file_after, "w", encoding="utf8") as out:
+ out.writelines(
+ [
+ "[MASTER]\n",
+ "load-plugins=copy_dummy\n"
+ f"init-hook=\"import sys; sys.path.append(r'{home_path}')\"\n",
+ ]
+ )
+ for rcfile in (pylintrc_file_before, pylintrc_file_after):
+ # To confirm we won't load this module _without_ the init hook running.
+ assert home_path not in sys.path
+ run = Run(
+ [
+ "--rcfile",
+ rcfile,
+ join(REGRTEST_DATA_DIR, "empty.py"),
+ ],
+ exit=False,
+ )
+ assert (
+ len(
+ [
+ ch.name
+ for ch in run.linter.get_checkers()
+ if ch.name == "dummy_plugin"
+ ]
+ )
+ == 2
+ )
+ assert run._rcfile == rcfile
+ assert home_path in sys.path
+ # Necessary as the executed init-hook modifies sys.path
+ sys.path.remove(home_path)
-def test_load_plugin_command_line_with_init_hook_command_line() -> None:
- regrtest_data_dir_abs = abspath(REGRTEST_DATA_DIR)
- run = Run(
- [
- "--init-hook",
- f'import sys; sys.path.append("{regrtest_data_dir_abs}")',
- "--load-plugins",
- "dummy_plugin",
- join(REGRTEST_DATA_DIR, "empty.py"),
- ],
- exit=False,
+def test_load_plugin_command_line_before_init_hook() -> None:
+ """Check that the order of 'load-plugins' and 'init-hook' doesn't affect execution."""
+ dummy_plugin_path = abspath(
+ join(REGRTEST_DATA_DIR, "dummy_plugin", "dummy_plugin.py")
)
- assert (
- len([ch.name for ch in run.linter.get_checkers() if ch.name == "dummy_plugin"])
- == 2
+
+ with fake_home() as home_path:
+ copy(dummy_plugin_path, join(home_path, "copy_dummy.py"))
+ # construct a basic rc file that just modifies the path
+ assert home_path not in sys.path
+ run = Run(
+ [
+ "--load-plugins",
+ "copy_dummy",
+ "--init-hook",
+ f'import sys; sys.path.append(r"{home_path}")',
+ join(REGRTEST_DATA_DIR, "empty.py"),
+ ],
+ exit=False,
+ )
+ assert home_path in sys.path
+ assert (
+ len(
+ [
+ ch.name
+ for ch in run.linter.get_checkers()
+ if ch.name == "dummy_plugin"
+ ]
+ )
+ == 2
+ )
+
+ # Necessary as the executed init-hook modifies sys.path
+ sys.path.remove(home_path)
+
+
+def test_load_plugin_command_line_with_init_hook_command_line() -> None:
+ dummy_plugin_path = abspath(
+ join(REGRTEST_DATA_DIR, "dummy_plugin", "dummy_plugin.py")
)
- # Necessary as the executed init-hook modifies sys.path
- sys.path.remove(regrtest_data_dir_abs)
+ with fake_home() as home_path:
+ copy(dummy_plugin_path, join(home_path, "copy_dummy.py"))
+ # construct a basic rc file that just modifies the path
+ assert home_path not in sys.path
+ run = Run(
+ [
+ "--init-hook",
+ f'import sys; sys.path.append(r"{home_path}")',
+ "--load-plugins",
+ "copy_dummy",
+ join(REGRTEST_DATA_DIR, "empty.py"),
+ ],
+ exit=False,
+ )
+ assert (
+ len(
+ [
+ ch.name
+ for ch in run.linter.get_checkers()
+ if ch.name == "dummy_plugin"
+ ]
+ )
+ == 2
+ )
+ assert home_path in sys.path
+
+ # Necessary as the executed init-hook modifies sys.path
+ sys.path.remove(home_path)
def test_load_plugin_config_file() -> None:
@@ -797,7 +890,7 @@ def test_full_documentation(linter: PyLinter) -> None:
def test_list_msgs_enabled(
- initialized_linter: PyLinter, capsys: CaptureFixture
+ initialized_linter: PyLinter, capsys: CaptureFixture[str]
) -> None:
linter = initialized_linter
linter.enable("W0101", scope="package")
@@ -851,7 +944,7 @@ def test_pylint_home_from_environ() -> None:
del os.environ["PYLINTHOME"]
-def test_warn_about_old_home(capsys: CaptureFixture) -> None:
+def test_warn_about_old_home(capsys: CaptureFixture[str]) -> None:
"""Test that we correctly warn about old_home."""
# Create old home
old_home = Path(USER_HOME) / OLD_DEFAULT_PYLINT_HOME
@@ -1064,7 +1157,7 @@ def test_by_module_statement_value(initialized_linter: PyLinter) -> None:
("--ignore-paths", ".*ignored.*/failing.*"),
],
)
-def test_recursive_ignore(ignore_parameter, ignore_parameter_value) -> None:
+def test_recursive_ignore(ignore_parameter: str, ignore_parameter_value: str) -> None:
run = Run(
[
"--recursive",
diff --git a/tests/message/unittest_message_definition.py b/tests/message/unittest_message_definition.py
index d42a249e3..aebd1bc6b 100644
--- a/tests/message/unittest_message_definition.py
+++ b/tests/message/unittest_message_definition.py
@@ -2,6 +2,8 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+from __future__ import annotations
+
import sys
from unittest import mock
@@ -21,7 +23,7 @@ from pylint.message import MessageDefinition
("W12345", "Invalid message id 'W12345'"),
],
)
-def test_create_invalid_message_type(msgid, expected):
+def test_create_invalid_message_type(msgid: str, expected: str) -> None:
checker_mock = mock.Mock(name="Checker")
checker_mock.name = "checker"
@@ -51,16 +53,20 @@ class FalseChecker(BaseChecker):
class TestMessagesDefinition:
@staticmethod
- def assert_with_fail_msg(msg: MessageDefinition, expected: bool = True) -> None:
+ def assert_with_fail_msg(
+ msg: MessageDefinition,
+ expected: bool = True,
+ py_version: tuple[int, ...] | sys._version_info = sys.version_info,
+ ) -> None:
fail_msg = (
f"With minversion='{msg.minversion}' and maxversion='{msg.maxversion}',"
- f" and the python interpreter being {sys.version_info} "
+ f" and the py-version option being {py_version} "
"the message should{}be emitable"
)
if expected:
- assert msg.may_be_emitted(), fail_msg.format(" ")
+ assert msg.may_be_emitted(py_version), fail_msg.format(" ")
else:
- assert not msg.may_be_emitted(), fail_msg.format(" not ")
+ assert not msg.may_be_emitted(py_version), fail_msg.format(" not ")
@staticmethod
def get_message_definition() -> MessageDefinition:
@@ -73,7 +79,7 @@ class TestMessagesDefinition:
WarningScope.NODE,
)
- def test_may_be_emitted(self) -> None:
+ def test_may_be_emitted_default(self) -> None:
major = sys.version_info.major
minor = sys.version_info.minor
msg = self.get_message_definition()
@@ -88,6 +94,21 @@ class TestMessagesDefinition:
msg.maxversion = (major, minor - 1)
self.assert_with_fail_msg(msg, expected=False)
+ def test_may_be_emitted_py_version(self) -> None:
+ msg = self.get_message_definition()
+ self.assert_with_fail_msg(msg, expected=True, py_version=(3, 2))
+
+ msg.maxversion = (3, 5)
+ self.assert_with_fail_msg(msg, expected=True, py_version=(3, 2))
+ self.assert_with_fail_msg(msg, expected=False, py_version=(3, 5))
+ self.assert_with_fail_msg(msg, expected=False, py_version=(3, 6))
+
+ msg.maxversion = None
+ msg.minversion = (3, 9)
+ self.assert_with_fail_msg(msg, expected=True, py_version=(3, 9))
+ self.assert_with_fail_msg(msg, expected=True, py_version=(3, 10))
+ self.assert_with_fail_msg(msg, expected=False, py_version=(3, 8))
+
def test_repr(self) -> None:
msg = self.get_message_definition()
repr_str = str([msg, msg])
diff --git a/tests/message/unittest_message_definition_store.py b/tests/message/unittest_message_definition_store.py
index 6a7914334..d36b1b42a 100644
--- a/tests/message/unittest_message_definition_store.py
+++ b/tests/message/unittest_message_definition_store.py
@@ -2,6 +2,8 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+from __future__ import annotations
+
from contextlib import redirect_stdout
from io import StringIO
@@ -13,6 +15,7 @@ from pylint.exceptions import InvalidMessageError, UnknownMessageError
from pylint.lint.pylinter import PyLinter
from pylint.message import MessageDefinition
from pylint.message.message_definition_store import MessageDefinitionStore
+from pylint.typing import MessageDefinitionTuple
@pytest.mark.parametrize(
@@ -120,7 +123,11 @@ from pylint.message.message_definition_store import MessageDefinitionStore
),
],
)
-def test_register_error(empty_store, messages, expected):
+def test_register_error(
+ empty_store: MessageDefinitionStore,
+ messages: dict[str, MessageDefinitionTuple],
+ expected: str,
+) -> None:
class Checker(BaseChecker):
def __init__(self) -> None:
super().__init__(PyLinter())
diff --git a/tests/message/unittest_message_id_store.py b/tests/message/unittest_message_id_store.py
index e543fef55..9dcf774e5 100644
--- a/tests/message/unittest_message_id_store.py
+++ b/tests/message/unittest_message_id_store.py
@@ -127,7 +127,7 @@ def test_exclusivity_of_msgids() -> None:
"07": ("exceptions", "broad_try_clause", "overlap-except"),
"12": ("design", "logging"),
"17": ("async", "refactoring"),
- "20": ("compare-to-zero", "empty-comment"),
+ "20": ("compare-to-zero", "empty-comment", "magic-value"),
}
for msgid, definition in runner.linter.msgs_store._messages_definitions.items():
diff --git a/tests/primer/packages_to_lint_batch_one.json b/tests/primer/packages_to_lint_batch_one.json
index 9b5eb7985..6520e2bd1 100644
--- a/tests/primer/packages_to_lint_batch_one.json
+++ b/tests/primer/packages_to_lint_batch_one.json
@@ -3,11 +3,5 @@
"branch": "master",
"directories": ["keras"],
"url": "https://github.com/keras-team/keras.git"
- },
- "music21": {
- "branch": "master",
- "directories": ["music21"],
- "pylintrc_relpath": ".pylintrc",
- "url": "https://github.com/cuthbertLab/music21"
}
}
diff --git a/tests/primer/packages_to_prime.json b/tests/primer/packages_to_prime.json
index 0fb877dcc..a1fd74b4d 100644
--- a/tests/primer/packages_to_prime.json
+++ b/tests/primer/packages_to_prime.json
@@ -20,6 +20,13 @@
"directories": ["src/flask"],
"url": "https://github.com/pallets/flask"
},
+ "music21": {
+ "branch": "master",
+ "directories": ["music21"],
+ "pylintrc_relpath": ".pylintrc",
+ "minimum_python": "3.10",
+ "url": "https://github.com/cuthbertLab/music21"
+ },
"pandas": {
"branch": "main",
"directories": ["pandas"],
@@ -45,5 +52,11 @@
"branch": "master",
"directories": ["src/sentry"],
"url": "https://github.com/getsentry/sentry"
+ },
+ "coverage": {
+ "branch": "master",
+ "directories": ["coverage"],
+ "url": "https://github.com/nedbat/coveragepy",
+ "pylintrc_relpath": "pylintrc"
}
}
diff --git a/tests/primer/test_primer_stdlib.py b/tests/primer/test_primer_stdlib.py
index 6cae6fd36..c2d879764 100644
--- a/tests/primer/test_primer_stdlib.py
+++ b/tests/primer/test_primer_stdlib.py
@@ -2,10 +2,14 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+from __future__ import annotations
+
import contextlib
import io
import os
import sys
+import warnings
+from collections.abc import Iterator
import pytest
from pytest import CaptureFixture
@@ -22,7 +26,7 @@ def is_package(filename: str, location: str) -> bool:
@contextlib.contextmanager
-def _patch_stdout(out):
+def _patch_stdout(out: io.StringIO) -> Iterator[None]:
sys.stdout = out
try:
yield
@@ -57,11 +61,13 @@ def test_primer_stdlib_no_crash(
# Duplicate code takes too long and is relatively safe
# We don't want to lint the test directory which are repetitive
disables = ["--disable=duplicate-code", "--ignore=test"]
- Run([test_module_name] + enables + disables)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", category=UserWarning)
+ Run([test_module_name] + enables + disables)
except SystemExit as ex:
out, err = capsys.readouterr()
assert not err, err
assert not out
msg = f"Encountered {{}} during primer stlib test for {test_module_name}"
assert ex.code != 32, msg.format("a crash")
- assert ex.code % 2 == 0, msg.format("a message of category 'fatal'")
+ assert ex.code % 2 == 0, msg.format("a message of category 'fatal'") # type: ignore[operator]
diff --git a/tests/profile/test_profile_against_externals.py b/tests/profile/test_profile_against_externals.py
index 579a5bc9c..7a429fad8 100644
--- a/tests/profile/test_profile_against_externals.py
+++ b/tests/profile/test_profile_against_externals.py
@@ -6,8 +6,11 @@
# pylint: disable=missing-function-docstring
+from __future__ import annotations
+
import os
import pprint
+from pathlib import Path
import pytest
@@ -15,10 +18,10 @@ from pylint.testutils import GenericTestReporter as Reporter
from pylint.testutils._run import _Run as Run
-def _get_py_files(scanpath):
+def _get_py_files(scanpath: str) -> list[str]:
assert os.path.exists(scanpath), f"Dir not found {scanpath}"
- filepaths = []
+ filepaths: list[str] = []
for dirpath, dirnames, filenames in os.walk(scanpath):
dirnames[:] = [dirname for dirname in dirnames if dirname != "__pycache__"]
filepaths.extend(
@@ -38,7 +41,7 @@ def _get_py_files(scanpath):
@pytest.mark.parametrize(
"name,git_repo", [("numpy", "https://github.com/numpy/numpy.git")]
)
-def test_run(tmp_path, name, git_repo):
+def test_run(tmp_path: Path, name: str, git_repo: str) -> None:
"""Runs pylint against external sources."""
checkoutdir = tmp_path / name
checkoutdir.mkdir()
diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py
index d8b6ea9f4..a37e4bde1 100644
--- a/tests/pyreverse/conftest.py
+++ b/tests/pyreverse/conftest.py
@@ -12,6 +12,7 @@ from astroid.nodes.scoped_nodes import Module
from pylint.lint import fix_import_path
from pylint.pyreverse.inspector import Project, project_from_files
from pylint.testutils.pyreverse import PyreverseConfig
+from pylint.typing import GetProjectCallable
@pytest.fixture()
@@ -66,11 +67,11 @@ def html_config() -> PyreverseConfig:
@pytest.fixture(scope="session")
-def get_project() -> Callable:
+def get_project() -> GetProjectCallable:
def _get_project(module: str, name: str | None = "No Name") -> Project:
"""Return an astroid project representation."""
- def _astroid_wrapper(func: Callable, modname: str) -> Module:
+ def _astroid_wrapper(func: Callable[[str], Module], modname: str) -> Module:
return func(modname)
with fix_import_path([module]):
diff --git a/tests/pyreverse/data/classes_No_Name.dot b/tests/pyreverse/data/classes_No_Name.dot
index 1f3f705e7..a598ab6d9 100644
--- a/tests/pyreverse/data/classes_No_Name.dot
+++ b/tests/pyreverse/data/classes_No_Name.dot
@@ -1,17 +1,17 @@
digraph "classes_No_Name" {
rankdir=BT
charset="utf-8"
-"data.clientmodule_test.Ancestor" [color="black", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="solid"];
-"data.suppliermodule_test.CustomException" [color="black", fontcolor="red", label="{CustomException|\l|}", shape="record", style="solid"];
-"data.suppliermodule_test.DoNothing" [color="black", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="solid"];
-"data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label="{DoNothing2|\l|}", shape="record", style="solid"];
-"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)\lincrement_value(): None\ltransform_value(value: int): int\l}", shape="record", style="solid"];
+"data.clientmodule_test.Ancestor" [color="black", fontcolor="black", label=<{Ancestor|attr : str<br ALIGN="LEFT"/>cls_member<br ALIGN="LEFT"/>|get_value()<br ALIGN="LEFT"/>set_value(value)<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
+"data.suppliermodule_test.CustomException" [color="black", fontcolor="red", label=<{CustomException|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
+"data.suppliermodule_test.DoNothing" [color="black", fontcolor="black", label=<{DoNothing|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
+"data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
+"data.suppliermodule_test.DoSomething" [color="black", fontcolor="black", label=<{DoSomething|my_int : Optional[int]<br ALIGN="LEFT"/>my_int_2 : Optional[int]<br ALIGN="LEFT"/>my_string : str<br ALIGN="LEFT"/>|do_it(new_int: int): int<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
+"data.suppliermodule_test.Interface" [color="black", fontcolor="black", label=<{Interface|<br ALIGN="LEFT"/>|<I>get_value</I>()<br ALIGN="LEFT"/><I>set_value</I>(value)<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
+"data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label=<{PropertyPatterns|prop1<br ALIGN="LEFT"/>prop2<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
+"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label=<{Specialization|TYPE : str<br ALIGN="LEFT"/>relation<br ALIGN="LEFT"/>relation2<br ALIGN="LEFT"/>top : str<br ALIGN="LEFT"/>|from_value(value: int)<br ALIGN="LEFT"/>increment_value(): None<br ALIGN="LEFT"/>transform_value(value: int): int<br ALIGN="LEFT"/>}>, 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"];
"data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Specialization" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"];
-"data.suppliermodule_test.DoNothing2" -> "data.clientmodule_test.Specialization" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation2", style="solid"];
+"data.suppliermodule_test.DoNothing2" -> "data.clientmodule_test.Specialization" [arrowhead="odiamond", arrowtail="none", fontcolor="green", label="relation2", style="solid"];
}
diff --git a/tests/pyreverse/data/classes_No_Name.html b/tests/pyreverse/data/classes_No_Name.html
index 956758223..602f2e3b7 100644
--- a/tests/pyreverse/data/classes_No_Name.html
+++ b/tests/pyreverse/data/classes_No_Name.html
@@ -23,8 +23,8 @@
do_it(new_int: int) int
}
class Interface {
- get_value()
- set_value(value)
+ get_value()*
+ set_value(value)*
}
class PropertyPatterns {
prop1
@@ -43,7 +43,7 @@
Ancestor ..|> Interface
DoNothing --* Ancestor : cls_member
DoNothing --* Specialization : relation
- DoNothing2 --* Specialization : relation2
+ DoNothing2 --o Specialization : relation2
</div>
</body>
diff --git a/tests/pyreverse/data/classes_No_Name.mmd b/tests/pyreverse/data/classes_No_Name.mmd
index 4daa91c24..1db88b2ae 100644
--- a/tests/pyreverse/data/classes_No_Name.mmd
+++ b/tests/pyreverse/data/classes_No_Name.mmd
@@ -18,8 +18,8 @@ classDiagram
do_it(new_int: int) int
}
class Interface {
- get_value()
- set_value(value)
+ get_value()*
+ set_value(value)*
}
class PropertyPatterns {
prop1
@@ -38,4 +38,4 @@ classDiagram
Ancestor ..|> Interface
DoNothing --* Ancestor : cls_member
DoNothing --* Specialization : relation
- DoNothing2 --* Specialization : relation2
+ DoNothing2 --o Specialization : relation2
diff --git a/tests/pyreverse/data/classes_No_Name.puml b/tests/pyreverse/data/classes_No_Name.puml
index 37767b321..837e6865c 100644
--- a/tests/pyreverse/data/classes_No_Name.puml
+++ b/tests/pyreverse/data/classes_No_Name.puml
@@ -19,8 +19,8 @@ class "DoSomething" as data.suppliermodule_test.DoSomething {
do_it(new_int: int) -> int
}
class "Interface" as data.suppliermodule_test.Interface {
- get_value()
- set_value(value)
+ {abstract}get_value()
+ {abstract}set_value(value)
}
class "PropertyPatterns" as data.property_pattern.PropertyPatterns {
prop1
@@ -39,5 +39,5 @@ data.clientmodule_test.Specialization --|> data.clientmodule_test.Ancestor
data.clientmodule_test.Ancestor ..|> data.suppliermodule_test.Interface
data.suppliermodule_test.DoNothing --* data.clientmodule_test.Ancestor : cls_member
data.suppliermodule_test.DoNothing --* data.clientmodule_test.Specialization : relation
-data.suppliermodule_test.DoNothing2 --* data.clientmodule_test.Specialization : relation2
+data.suppliermodule_test.DoNothing2 --o data.clientmodule_test.Specialization : relation2
@enduml
diff --git a/tests/pyreverse/data/classes_colorized.dot b/tests/pyreverse/data/classes_colorized.dot
index 72f30658d..4ff12a819 100644
--- a/tests/pyreverse/data/classes_colorized.dot
+++ b/tests/pyreverse/data/classes_colorized.dot
@@ -1,17 +1,17 @@
digraph "classes_colorized" {
rankdir=BT
charset="utf-8"
-"data.clientmodule_test.Ancestor" [color="aliceblue", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="filled"];
-"data.suppliermodule_test.CustomException" [color="aliceblue", fontcolor="red", label="{CustomException|\l|}", shape="record", style="filled"];
-"data.suppliermodule_test.DoNothing" [color="aliceblue", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="filled"];
-"data.suppliermodule_test.DoNothing2" [color="aliceblue", fontcolor="black", label="{DoNothing2|\l|}", shape="record", style="filled"];
-"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)\lincrement_value(): None\ltransform_value(value: int): int\l}", shape="record", style="filled"];
+"data.clientmodule_test.Ancestor" [color="aliceblue", fontcolor="black", label=<{Ancestor|attr : str<br ALIGN="LEFT"/>cls_member<br ALIGN="LEFT"/>|get_value()<br ALIGN="LEFT"/>set_value(value)<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
+"data.suppliermodule_test.CustomException" [color="aliceblue", fontcolor="red", label=<{CustomException|<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
+"data.suppliermodule_test.DoNothing" [color="aliceblue", fontcolor="black", label=<{DoNothing|<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
+"data.suppliermodule_test.DoNothing2" [color="aliceblue", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
+"data.suppliermodule_test.DoSomething" [color="aliceblue", fontcolor="black", label=<{DoSomething|my_int : Optional[int]<br ALIGN="LEFT"/>my_int_2 : Optional[int]<br ALIGN="LEFT"/>my_string : str<br ALIGN="LEFT"/>|do_it(new_int: int): int<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
+"data.suppliermodule_test.Interface" [color="aliceblue", fontcolor="black", label=<{Interface|<br ALIGN="LEFT"/>|<I>get_value</I>()<br ALIGN="LEFT"/><I>set_value</I>(value)<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
+"data.property_pattern.PropertyPatterns" [color="aliceblue", fontcolor="black", label=<{PropertyPatterns|prop1<br ALIGN="LEFT"/>prop2<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
+"data.clientmodule_test.Specialization" [color="aliceblue", fontcolor="black", label=<{Specialization|TYPE : str<br ALIGN="LEFT"/>relation<br ALIGN="LEFT"/>relation2<br ALIGN="LEFT"/>top : str<br ALIGN="LEFT"/>|from_value(value: int)<br ALIGN="LEFT"/>increment_value(): None<br ALIGN="LEFT"/>transform_value(value: int): int<br ALIGN="LEFT"/>}>, 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"];
"data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Specialization" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"];
-"data.suppliermodule_test.DoNothing2" -> "data.clientmodule_test.Specialization" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation2", style="solid"];
+"data.suppliermodule_test.DoNothing2" -> "data.clientmodule_test.Specialization" [arrowhead="odiamond", arrowtail="none", fontcolor="green", label="relation2", style="solid"];
}
diff --git a/tests/pyreverse/data/classes_colorized.puml b/tests/pyreverse/data/classes_colorized.puml
index 1226f7b4e..7398ee60f 100644
--- a/tests/pyreverse/data/classes_colorized.puml
+++ b/tests/pyreverse/data/classes_colorized.puml
@@ -19,8 +19,8 @@ class "DoSomething" as data.suppliermodule_test.DoSomething #aliceblue {
do_it(new_int: int) -> int
}
class "Interface" as data.suppliermodule_test.Interface #aliceblue {
- get_value()
- set_value(value)
+ {abstract}get_value()
+ {abstract}set_value(value)
}
class "PropertyPatterns" as data.property_pattern.PropertyPatterns #aliceblue {
prop1
@@ -39,5 +39,5 @@ data.clientmodule_test.Specialization --|> data.clientmodule_test.Ancestor
data.clientmodule_test.Ancestor ..|> data.suppliermodule_test.Interface
data.suppliermodule_test.DoNothing --* data.clientmodule_test.Ancestor : cls_member
data.suppliermodule_test.DoNothing --* data.clientmodule_test.Specialization : relation
-data.suppliermodule_test.DoNothing2 --* data.clientmodule_test.Specialization : relation2
+data.suppliermodule_test.DoNothing2 --o data.clientmodule_test.Specialization : relation2
@enduml
diff --git a/tests/pyreverse/data/packages_No_Name.dot b/tests/pyreverse/data/packages_No_Name.dot
index 461c8f9b4..5421c328c 100644
--- a/tests/pyreverse/data/packages_No_Name.dot
+++ b/tests/pyreverse/data/packages_No_Name.dot
@@ -1,9 +1,9 @@
digraph "packages_No_Name" {
rankdir=BT
charset="utf-8"
-"data" [color="black", label="data", shape="box", style="solid"];
-"data.clientmodule_test" [color="black", label="data.clientmodule_test", shape="box", style="solid"];
-"data.property_pattern" [color="black", label="data.property_pattern", shape="box", style="solid"];
-"data.suppliermodule_test" [color="black", label="data.suppliermodule_test", shape="box", style="solid"];
+"data" [color="black", label=<data>, shape="box", style="solid"];
+"data.clientmodule_test" [color="black", label=<data.clientmodule_test>, shape="box", style="solid"];
+"data.property_pattern" [color="black", label=<data.property_pattern>, shape="box", style="solid"];
+"data.suppliermodule_test" [color="black", label=<data.suppliermodule_test>, shape="box", style="solid"];
"data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"];
}
diff --git a/tests/pyreverse/data/packages_colorized.dot b/tests/pyreverse/data/packages_colorized.dot
index 1a95d4c97..10005f26c 100644
--- a/tests/pyreverse/data/packages_colorized.dot
+++ b/tests/pyreverse/data/packages_colorized.dot
@@ -1,9 +1,9 @@
digraph "packages_colorized" {
rankdir=BT
charset="utf-8"
-"data" [color="aliceblue", label="data", shape="box", style="filled"];
-"data.clientmodule_test" [color="aliceblue", label="data.clientmodule_test", shape="box", style="filled"];
-"data.property_pattern" [color="aliceblue", label="data.property_pattern", shape="box", style="filled"];
-"data.suppliermodule_test" [color="aliceblue", label="data.suppliermodule_test", shape="box", style="filled"];
+"data" [color="aliceblue", label=<data>, shape="box", style="filled"];
+"data.clientmodule_test" [color="aliceblue", label=<data.clientmodule_test>, shape="box", style="filled"];
+"data.property_pattern" [color="aliceblue", label=<data.property_pattern>, shape="box", style="filled"];
+"data.suppliermodule_test" [color="aliceblue", label=<data.suppliermodule_test>, shape="box", style="filled"];
"data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"];
}
diff --git a/tests/pyreverse/functional/class_diagrams/annotations/attributes_annotation.dot b/tests/pyreverse/functional/class_diagrams/annotations/attributes_annotation.dot
index e9b23699b..94c242ccf 100644
--- a/tests/pyreverse/functional/class_diagrams/annotations/attributes_annotation.dot
+++ b/tests/pyreverse/functional/class_diagrams/annotations/attributes_annotation.dot
@@ -1,6 +1,6 @@
digraph "classes" {
rankdir=BT
charset="utf-8"
-"attributes_annotation.Dummy" [color="black", fontcolor="black", label="{Dummy|\l|}", shape="record", style="solid"];
-"attributes_annotation.Dummy2" [color="black", fontcolor="black", label="{Dummy2|alternative_union_syntax : str \| int\lclass_attr : list[Dummy]\loptional : Optional[Dummy]\lparam : str\lunion : Union[int, str]\l|}", shape="record", style="solid"];
+"attributes_annotation.Dummy" [color="black", fontcolor="black", label=<{Dummy|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
+"attributes_annotation.Dummy2" [color="black", fontcolor="black", label=<{Dummy2|alternative_union_syntax : str \| int<br ALIGN="LEFT"/>class_attr : list[Dummy]<br ALIGN="LEFT"/>optional : Optional[Dummy]<br ALIGN="LEFT"/>param : str<br ALIGN="LEFT"/>union : Union[int, str]<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
}
diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py
index 35dcd0e3a..da16eea33 100644
--- a/tests/pyreverse/test_diadefs.py
+++ b/tests/pyreverse/test_diadefs.py
@@ -9,7 +9,7 @@
from __future__ import annotations
import sys
-from collections.abc import Callable
+from collections.abc import Iterator
from pathlib import Path
import pytest
@@ -25,6 +25,7 @@ from pylint.pyreverse.diagrams import DiagramEntity, Relationship
from pylint.pyreverse.inspector import Linker, Project
from pylint.testutils.pyreverse import PyreverseConfig
from pylint.testutils.utils import _test_cwd
+from pylint.typing import GetProjectCallable
HERE = Path(__file__)
TESTS = HERE.parent.parent
@@ -53,7 +54,7 @@ def HANDLER(default_config: PyreverseConfig) -> DiadefsHandler:
@pytest.fixture(scope="module")
-def PROJECT(get_project):
+def PROJECT(get_project: GetProjectCallable) -> Iterator[Project]:
with _test_cwd(TESTS):
yield get_project("data")
@@ -98,14 +99,13 @@ def test_default_values() -> None:
class TestDefaultDiadefGenerator:
_should_rels = [
+ ("aggregation", "DoNothing2", "Specialization"),
("association", "DoNothing", "Ancestor"),
("association", "DoNothing", "Specialization"),
- ("association", "DoNothing2", "Specialization"),
("implements", "Ancestor", "Interface"),
("specialization", "Specialization", "Ancestor"),
]
- @pytest.mark.xfail
def test_extract_relations(self, HANDLER: DiadefsHandler, PROJECT: Project) -> None:
"""Test extract_relations between classes."""
with pytest.warns(DeprecationWarning):
@@ -114,9 +114,8 @@ class TestDefaultDiadefGenerator:
relations = _process_relations(cd.relationships)
assert relations == self._should_rels
- @pytest.mark.xfail
def test_functional_relation_extraction(
- self, default_config: PyreverseConfig, get_project: Callable
+ self, default_config: PyreverseConfig, get_project: GetProjectCallable
) -> None:
"""Functional test of relations extraction;
different classes possibly in different modules
@@ -160,7 +159,9 @@ def test_known_values1(HANDLER: DiadefsHandler, PROJECT: Project) -> None:
]
-def test_known_values2(HANDLER: DiadefsHandler, get_project: Callable) -> None:
+def test_known_values2(
+ HANDLER: DiadefsHandler, get_project: GetProjectCallable
+) -> None:
project = get_project("data.clientmodule_test")
dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project)
assert len(dd) == 1
@@ -205,7 +206,7 @@ def test_known_values4(HANDLER: DiadefsHandler, PROJECT: Project) -> None:
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requires dataclasses")
def test_regression_dataclasses_inference(
- HANDLER: DiadefsHandler, get_project: Callable
+ HANDLER: DiadefsHandler, get_project: GetProjectCallable
) -> None:
project_path = Path("regrtest_data") / "dataclasses_pyreverse"
path = get_project(str(project_path))
diff --git a/tests/pyreverse/test_diagrams.py b/tests/pyreverse/test_diagrams.py
index b4a59a571..863bcecc9 100644
--- a/tests/pyreverse/test_diagrams.py
+++ b/tests/pyreverse/test_diagrams.py
@@ -6,15 +6,14 @@
from __future__ import annotations
-from collections.abc import Callable
-
from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler
from pylint.pyreverse.inspector import Linker
from pylint.testutils.pyreverse import PyreverseConfig
+from pylint.typing import GetProjectCallable
def test_property_handling(
- default_config: PyreverseConfig, get_project: Callable
+ default_config: PyreverseConfig, get_project: GetProjectCallable
) -> None:
project = get_project("data.property_pattern")
class_diagram = DefaultDiadefGenerator(
diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py
index 15f9d305a..00cad918f 100644
--- a/tests/pyreverse/test_inspector.py
+++ b/tests/pyreverse/test_inspector.py
@@ -9,7 +9,8 @@
from __future__ import annotations
import os
-from collections.abc import Callable, Generator
+import warnings
+from collections.abc import Generator
from pathlib import Path
import astroid
@@ -19,17 +20,20 @@ from astroid import nodes
from pylint.pyreverse import inspector
from pylint.pyreverse.inspector import Project
from pylint.testutils.utils import _test_cwd
+from pylint.typing import GetProjectCallable
HERE = Path(__file__)
TESTS = HERE.parent.parent
@pytest.fixture
-def project(get_project: Callable) -> Generator[Project, None, None]:
+def project(get_project: GetProjectCallable) -> Generator[Project, None, None]:
with _test_cwd(TESTS):
project = get_project("data", "data")
linker = inspector.Linker(project)
- linker.visit(project)
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
+ linker.visit(project)
yield project
diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py
index 55c6ea1ec..4cc0573d6 100644
--- a/tests/pyreverse/test_main.py
+++ b/tests/pyreverse/test_main.py
@@ -14,6 +14,7 @@ from unittest import mock
import pytest
from _pytest.capture import CaptureFixture
+from _pytest.fixtures import SubRequest
from pylint.lint import fix_import_path
from pylint.pyreverse import main
@@ -23,13 +24,13 @@ PROJECT_ROOT_DIR = os.path.abspath(os.path.join(TEST_DATA_DIR, ".."))
@pytest.fixture(name="mock_subprocess")
-def mock_utils_subprocess():
+def mock_utils_subprocess() -> Iterator[mock.MagicMock]:
with mock.patch("pylint.pyreverse.utils.subprocess") as mock_subprocess:
yield mock_subprocess
@pytest.fixture
-def mock_graphviz(mock_subprocess):
+def mock_graphviz(mock_subprocess: mock.MagicMock) -> Iterator[None]:
mock_subprocess.run.return_value = mock.Mock(
stderr=(
'Format: "XYZ" not recognized. Use one of: '
@@ -45,7 +46,7 @@ def mock_graphviz(mock_subprocess):
@pytest.fixture(params=[PROJECT_ROOT_DIR, TEST_DATA_DIR])
-def setup_path(request) -> Iterator:
+def setup_path(request: SubRequest) -> Iterator[None]:
current_sys_path = list(sys.path)
sys.path[:] = []
current_dir = os.getcwd()
@@ -68,7 +69,9 @@ def test_project_root_in_sys_path() -> None:
@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: CaptureFixture) -> None:
+def test_graphviz_supported_image_format(
+ mock_writer: mock.MagicMock, capsys: CaptureFixture[str]
+) -> None:
"""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
@@ -88,7 +91,7 @@ def test_graphviz_supported_image_format(mock_writer, capsys: CaptureFixture) ->
@mock.patch("pylint.pyreverse.main.writer")
@pytest.mark.usefixtures("mock_graphviz")
def test_graphviz_cant_determine_supported_formats(
- mock_writer, mock_subprocess, capsys: CaptureFixture
+ mock_writer: mock.MagicMock, mock_subprocess: mock.MagicMock, capsys: CaptureFixture
) -> None:
"""Test that Graphviz is used if the image format is supported."""
mock_subprocess.run.return_value.stderr = "..."
@@ -148,7 +151,7 @@ def test_graphviz_unsupported_image_format(capsys: CaptureFixture) -> None:
@mock.patch("pylint.pyreverse.main.sys.exit", new=mock.MagicMock())
def test_command_line_arguments_defaults(arg: str, expected_default: Any) -> None:
"""Test that the default arguments of all options are correct."""
- run = main.Run([TEST_DATA_DIR])
+ run = main.Run([TEST_DATA_DIR]) # type: ignore[var-annotated]
assert getattr(run.config, arg) == expected_default
@@ -175,7 +178,7 @@ def test_class_command(
Make sure that we append multiple --class arguments to one option destination.
"""
- runner = main.Run(
+ runner = main.Run( # type: ignore[var-annotated]
[
"--class",
"data.clientmodule_test.Ancestor",
diff --git a/tests/pyreverse/test_printer.py b/tests/pyreverse/test_printer.py
index d6785940f..4248e8bae 100644
--- a/tests/pyreverse/test_printer.py
+++ b/tests/pyreverse/test_printer.py
@@ -39,7 +39,7 @@ def test_explicit_layout(
"layout, printer_class",
[(Layout.BOTTOM_TO_TOP, PlantUmlPrinter), (Layout.RIGHT_TO_LEFT, PlantUmlPrinter)],
)
-def test_unsupported_layout(layout: Layout, printer_class: type[Printer]):
+def test_unsupported_layout(layout: Layout, printer_class: type[Printer]) -> None:
with pytest.raises(ValueError):
printer_class(title="unittest", layout=layout)
diff --git a/tests/pyreverse/test_printer_factory.py b/tests/pyreverse/test_printer_factory.py
index 97ee1179c..76406f0a8 100644
--- a/tests/pyreverse/test_printer_factory.py
+++ b/tests/pyreverse/test_printer_factory.py
@@ -4,11 +4,14 @@
"""Unit tests for pylint.pyreverse.printer_factory."""
+from __future__ import annotations
+
import pytest
from pylint.pyreverse import printer_factory
from pylint.pyreverse.dot_printer import DotPrinter
from pylint.pyreverse.plantuml_printer import PlantUmlPrinter
+from pylint.pyreverse.printer import Printer
from pylint.pyreverse.vcg_printer import VCGPrinter
@@ -22,5 +25,7 @@ from pylint.pyreverse.vcg_printer import VCGPrinter
("png", DotPrinter),
],
)
-def test_get_printer_for_filetype(filetype, expected_printer_class):
+def test_get_printer_for_filetype(
+ filetype: str, expected_printer_class: type[Printer]
+) -> None:
assert printer_factory.get_printer_for_filetype(filetype) == expected_printer_class
diff --git a/tests/pyreverse/test_pyreverse_functional.py b/tests/pyreverse/test_pyreverse_functional.py
index 2cc880d70..15fd1978b 100644
--- a/tests/pyreverse/test_pyreverse_functional.py
+++ b/tests/pyreverse/test_pyreverse_functional.py
@@ -5,7 +5,6 @@
from pathlib import Path
import pytest
-from py._path.local import LocalPath # type: ignore[import]
from pylint.pyreverse.main import Run
from pylint.testutils.pyreverse import (
@@ -23,17 +22,15 @@ CLASS_DIAGRAM_TEST_IDS = [testfile.source.stem for testfile in CLASS_DIAGRAM_TES
CLASS_DIAGRAM_TESTS,
ids=CLASS_DIAGRAM_TEST_IDS,
)
-def test_class_diagrams(
- testfile: FunctionalPyreverseTestfile, tmpdir: LocalPath
-) -> None:
+def test_class_diagrams(testfile: FunctionalPyreverseTestfile, tmp_path: Path) -> None:
input_file = testfile.source
for output_format in testfile.options["output_formats"]:
with pytest.raises(SystemExit) as sys_exit:
- args = ["-o", f"{output_format}", "-d", str(tmpdir)]
+ args = ["-o", f"{output_format}", "-d", str(tmp_path)]
args.extend(testfile.options["command_line_args"])
args += [str(input_file)]
Run(args)
assert sys_exit.value.code == 0
assert testfile.source.with_suffix(f".{output_format}").read_text(
encoding="utf8"
- ) == Path(tmpdir / f"classes.{output_format}").read_text(encoding="utf8")
+ ) == (tmp_path / f"classes.{output_format}").read_text(encoding="utf8")
diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py
index 70d95346f..d64bf4fa7 100644
--- a/tests/pyreverse/test_utils.py
+++ b/tests/pyreverse/test_utils.py
@@ -4,6 +4,8 @@
"""Tests for pylint.pyreverse.utils."""
+from __future__ import annotations
+
from typing import Any
from unittest.mock import patch
@@ -31,7 +33,7 @@ from pylint.pyreverse.utils import (
),
],
)
-def test_get_visibility(names, expected):
+def test_get_visibility(names: list[str], expected: str) -> None:
for name in names:
got = get_visibility(name)
assert got == expected, f"got {got} instead of {expected} for value {name}"
@@ -46,10 +48,12 @@ def test_get_visibility(names, expected):
("a: Optional[str] = None", "Optional[str]"),
],
)
-def test_get_annotation_annassign(assign, label):
+def test_get_annotation_annassign(assign: str, label: str) -> None:
"""AnnAssign."""
- node = astroid.extract_node(assign)
- got = get_annotation(node.value).name
+ node: nodes.AnnAssign = astroid.extract_node(assign)
+ annotation = get_annotation(node.value)
+ assert annotation is not None
+ got = annotation.name
assert isinstance(node, nodes.AnnAssign)
assert got == label, f"got {got} instead of {label} for value {node}"
@@ -65,7 +69,7 @@ def test_get_annotation_annassign(assign, label):
("def __init__(self, x: Optional[str] = 'str'): self.x = x", "Optional[str]"),
],
)
-def test_get_annotation_assignattr(init_method, label):
+def test_get_annotation_assignattr(init_method: str, label: str) -> None:
"""AssignAttr."""
assign = rf"""
class A:
@@ -75,7 +79,9 @@ def test_get_annotation_assignattr(init_method, label):
instance_attrs = node.instance_attrs
for assign_attrs in instance_attrs.values():
for assign_attr in assign_attrs:
- got = get_annotation(assign_attr).name
+ annotation = get_annotation(assign_attr)
+ assert annotation is not None
+ got = annotation.name
assert isinstance(assign_attr, nodes.AssignAttr)
assert got == label, f"got {got} instead of {label} for value {node}"
@@ -98,7 +104,7 @@ def test_get_annotation_label_of_return_type(
@patch("pylint.pyreverse.utils.get_annotation")
-@patch("astroid.node_classes.NodeNG.infer", side_effect=astroid.InferenceError)
+@patch("astroid.nodes.NodeNG.infer", side_effect=astroid.InferenceError)
def test_infer_node_1(mock_infer: Any, mock_get_annotation: Any) -> None:
"""Return set() when astroid.InferenceError is raised and an annotation has
not been returned
@@ -111,7 +117,7 @@ def test_infer_node_1(mock_infer: Any, mock_get_annotation: Any) -> None:
@patch("pylint.pyreverse.utils.get_annotation")
-@patch("astroid.node_classes.NodeNG.infer")
+@patch("astroid.nodes.NodeNG.infer")
def test_infer_node_2(mock_infer: Any, mock_get_annotation: Any) -> None:
"""Return set(node.infer()) when InferenceError is not raised and an
annotation has not been returned
diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py
index a03105092..805f8fab5 100644
--- a/tests/pyreverse/test_writer.py
+++ b/tests/pyreverse/test_writer.py
@@ -8,7 +8,7 @@ from __future__ import annotations
import codecs
import os
-from collections.abc import Callable, Iterator
+from collections.abc import Iterator
from difflib import unified_diff
from unittest.mock import Mock
@@ -18,6 +18,7 @@ from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler
from pylint.pyreverse.inspector import Linker, Project
from pylint.pyreverse.writer import DiagramWriter
from pylint.testutils.pyreverse import PyreverseConfig
+from pylint.typing import GetProjectCallable
_DEFAULTS = {
"all_ancestors": None,
@@ -49,7 +50,7 @@ HTML_FILES = ["packages_No_Name.html", "classes_No_Name.html"]
class Config:
"""Config object for tests."""
- def __init__(self):
+ def __init__(self) -> None:
for attr, value in _DEFAULTS.items():
setattr(self, attr, value)
@@ -69,7 +70,9 @@ def _file_lines(path: str) -> list[str]:
@pytest.fixture()
-def setup_dot(default_config: PyreverseConfig, get_project: Callable) -> Iterator:
+def setup_dot(
+ default_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(default_config)
project = get_project(TEST_DATA_DIR)
yield from _setup(project, default_config, writer)
@@ -77,22 +80,26 @@ def setup_dot(default_config: PyreverseConfig, get_project: Callable) -> Iterato
@pytest.fixture()
def setup_colorized_dot(
- colorized_dot_config: PyreverseConfig, get_project: Callable
-) -> Iterator:
+ colorized_dot_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(colorized_dot_config)
project = get_project(TEST_DATA_DIR, name="colorized")
yield from _setup(project, colorized_dot_config, writer)
@pytest.fixture()
-def setup_vcg(vcg_config: PyreverseConfig, get_project: Callable) -> Iterator:
+def setup_vcg(
+ vcg_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(vcg_config)
project = get_project(TEST_DATA_DIR)
yield from _setup(project, vcg_config, writer)
@pytest.fixture()
-def setup_puml(puml_config: PyreverseConfig, get_project: Callable) -> Iterator:
+def setup_puml(
+ puml_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(puml_config)
project = get_project(TEST_DATA_DIR)
yield from _setup(project, puml_config, writer)
@@ -100,15 +107,17 @@ def setup_puml(puml_config: PyreverseConfig, get_project: Callable) -> Iterator:
@pytest.fixture()
def setup_colorized_puml(
- colorized_puml_config: PyreverseConfig, get_project: Callable
-) -> Iterator:
+ colorized_puml_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(colorized_puml_config)
project = get_project(TEST_DATA_DIR, name="colorized")
yield from _setup(project, colorized_puml_config, writer)
@pytest.fixture()
-def setup_mmd(mmd_config: PyreverseConfig, get_project: Callable) -> Iterator:
+def setup_mmd(
+ mmd_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(mmd_config)
project = get_project(TEST_DATA_DIR)
@@ -116,7 +125,9 @@ def setup_mmd(mmd_config: PyreverseConfig, get_project: Callable) -> Iterator:
@pytest.fixture()
-def setup_html(html_config: PyreverseConfig, get_project: Callable) -> Iterator:
+def setup_html(
+ html_config: PyreverseConfig, get_project: GetProjectCallable
+) -> Iterator[None]:
writer = DiagramWriter(html_config)
project = get_project(TEST_DATA_DIR)
@@ -125,7 +136,7 @@ def setup_html(html_config: PyreverseConfig, get_project: Callable) -> Iterator:
def _setup(
project: Project, config: PyreverseConfig, writer: DiagramWriter
-) -> Iterator:
+) -> Iterator[None]:
linker = Linker(project)
handler = DiadefsHandler(config)
dd = DefaultDiadefGenerator(linker, handler).visit(project)
diff --git a/tests/regrtest_data/imported_module_in_typehint/module_a.py b/tests/regrtest_data/imported_module_in_typehint/module_a.py
new file mode 100644
index 000000000..d9754eca4
--- /dev/null
+++ b/tests/regrtest_data/imported_module_in_typehint/module_a.py
@@ -0,0 +1,5 @@
+import uuid
+from typing import Optional
+
+
+ID = None # type: Optional[uuid.UUID]
diff --git a/tests/regrtest_data/imported_module_in_typehint/module_b.py b/tests/regrtest_data/imported_module_in_typehint/module_b.py
new file mode 100644
index 000000000..4ab5fc595
--- /dev/null
+++ b/tests/regrtest_data/imported_module_in_typehint/module_b.py
@@ -0,0 +1 @@
+import uuid
diff --git a/tests/reporters/unittest_json_reporter.py b/tests/reporters/unittest_json_reporter.py
index 90a67fceb..9104016ea 100644
--- a/tests/reporters/unittest_json_reporter.py
+++ b/tests/reporters/unittest_json_reporter.py
@@ -102,7 +102,7 @@ def get_linter_result(score: bool, message: dict[str, Any]) -> list[dict[str, An
reporter.display_reports(EvaluationSection(expected_score_message))
reporter.display_messages(None)
report_result = json.loads(output.getvalue())
- return report_result
+ return report_result # type: ignore[no-any-return]
@pytest.mark.parametrize(
@@ -131,7 +131,7 @@ def get_linter_result(score: bool, message: dict[str, Any]) -> list[dict[str, An
)
],
)
-def test_serialize_deserialize(message):
+def test_serialize_deserialize(message: Message) -> None:
# TODO: 3.0: Add confidence handling, add path and abs path handling or a new JSONReporter
json_message = JSONReporter.serialize(message)
assert message == JSONReporter.deserialize(json_message)
diff --git a/tests/reporters/unittest_reporting.py b/tests/reporters/unittest_reporting.py
index 37f3e5fd9..7b8139119 100644
--- a/tests/reporters/unittest_reporting.py
+++ b/tests/reporters/unittest_reporting.py
@@ -11,9 +11,11 @@ import warnings
from contextlib import redirect_stdout
from io import StringIO
from json import dumps
-from typing import TYPE_CHECKING
+from pathlib import Path
+from typing import TYPE_CHECKING, TextIO
import pytest
+from _pytest.recwarn import WarningsRecorder
from pylint import checkers
from pylint.interfaces import HIGH
@@ -28,16 +30,16 @@ if TYPE_CHECKING:
@pytest.fixture(scope="module")
-def reporter():
+def reporter() -> type[TextReporter]:
return TextReporter
@pytest.fixture(scope="module")
-def disable():
+def disable() -> list[str]:
return ["I"]
-def test_template_option(linter):
+def test_template_option(linter: PyLinter) -> None:
output = StringIO()
linter.reporter.out = output
linter.config.msg_template = "{msg_id}:{line:03d}"
@@ -48,7 +50,7 @@ def test_template_option(linter):
assert output.getvalue() == "************* Module 0123\nC0301:001\nC0301:002\n"
-def test_template_option_default(linter) -> None:
+def test_template_option_default(linter: PyLinter) -> None:
"""Test the default msg-template setting."""
output = StringIO()
linter.reporter.out = output
@@ -62,7 +64,7 @@ def test_template_option_default(linter) -> None:
assert out_lines[2] == "my_module:2:0: C0301: Line too long (3/4) (line-too-long)"
-def test_template_option_end_line(linter) -> None:
+def test_template_option_end_line(linter: PyLinter) -> None:
"""Test the msg-template option with end_line and end_column."""
output = StringIO()
linter.reporter.out = output
@@ -81,7 +83,7 @@ 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_existing(linter) -> None:
+def test_template_option_non_existing(linter: PyLinter) -> 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
@@ -113,7 +115,7 @@ def test_template_option_non_existing(linter) -> None:
assert out_lines[2] == "my_mod:2::()"
-def test_deprecation_set_output(recwarn):
+def test_deprecation_set_output(recwarn: WarningsRecorder) -> None:
"""TODO remove in 3.0."""
reporter = BaseReporter()
# noinspection PyDeprecation
@@ -123,7 +125,7 @@ def test_deprecation_set_output(recwarn):
assert reporter.out == sys.stdout
-def test_parseable_output_deprecated():
+def test_parseable_output_deprecated() -> None:
with warnings.catch_warnings(record=True) as cm:
warnings.simplefilter("always")
ParseableTextReporter()
@@ -132,9 +134,10 @@ def test_parseable_output_deprecated():
assert isinstance(cm[0].message, DeprecationWarning)
-def test_parseable_output_regression():
+def test_parseable_output_regression() -> None:
output = StringIO()
with warnings.catch_warnings(record=True):
+ warnings.simplefilter("ignore", category=DeprecationWarning)
linter = PyLinter(reporter=ParseableTextReporter())
checkers.initialize(linter)
@@ -155,18 +158,18 @@ class NopReporter(BaseReporter):
name = "nop-reporter"
extension = ""
- def __init__(self, output=None):
+ def __init__(self, output: TextIO | None = None) -> None:
super().__init__(output)
print("A NopReporter was initialized.", file=self.out)
- def writeln(self, string=""):
+ def writeln(self, string: str = "") -> None:
pass
def _display(self, layout: Section) -> None:
pass
-def test_multi_format_output(tmp_path):
+def test_multi_format_output(tmp_path: Path) -> None:
text = StringIO(newline=None)
json = tmp_path / "somefile.json"
@@ -191,12 +194,15 @@ def test_multi_format_output(tmp_path):
linter.reporter.out = text
linter.open()
- linter.check_single_file_item(FileItem("somemodule", source_file, "somemodule"))
+ linter.check_single_file_item(
+ FileItem("somemodule", str(source_file), "somemodule")
+ )
linter.add_message("line-too-long", line=1, args=(1, 2))
linter.generate_reports()
linter.reporter.writeln("direct output")
# Ensure the output files are flushed and closed
+ assert isinstance(linter.reporter, MultiReporter)
linter.reporter.close_output_files()
del linter.reporter
diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py
index a7fb5c158..15b59304c 100644
--- a/tests/test_check_parallel.py
+++ b/tests/test_check_parallel.py
@@ -110,7 +110,7 @@ class ParallelTestChecker(BaseRawFileChecker):
for _ in self.data[1::2]: # Work on pairs of files, see class docstring.
self.add_message("R9999", args=("From process_module, two files seen.",))
- def get_map_data(self):
+ def get_map_data(self) -> list[str]:
return self.data
def reduce_map_data(self, linter: PyLinter, data: list[list[str]]) -> None:
@@ -161,10 +161,10 @@ class ThirdParallelTestChecker(ParallelTestChecker):
class TestCheckParallelFramework:
"""Tests the check_parallel() function's framework."""
- def setup_class(self):
+ def setup_class(self) -> None:
self._prev_global_linter = pylint.lint.parallel._worker_linter
- def teardown_class(self):
+ def teardown_class(self) -> None:
pylint.lint.parallel._worker_linter = self._prev_global_linter
def test_worker_initialize(self) -> None:
@@ -190,7 +190,7 @@ class TestCheckParallelFramework:
def test_worker_check_single_file_uninitialised(self) -> None:
pylint.lint.parallel._worker_linter = None
with pytest.raises( # Objects that do not match the linter interface will fail
- Exception, match="Worker linter not yet initialised"
+ RuntimeError, match="Worker linter not yet initialised"
):
worker_check_single_file(_gen_file_data())
@@ -239,7 +239,9 @@ class TestCheckParallelFramework:
linter.load_plugin_modules(["pylint.extensions.overlapping_exceptions"])
try:
dill.dumps(linter)
- assert False, "Plugins loaded were pickle-safe! This test needs altering"
+ raise AssertionError(
+ "Plugins loaded were pickle-safe! This test needs altering"
+ )
except (KeyError, TypeError, PickleError, NotImplementedError):
pass
@@ -247,8 +249,10 @@ class TestCheckParallelFramework:
linter.load_plugin_configuration()
try:
dill.dumps(linter)
- except KeyError:
- assert False, "Cannot pickle linter when using non-pickleable plugin"
+ except KeyError as exc:
+ raise AssertionError(
+ "Cannot pickle linter when using non-pickleable plugin"
+ ) from exc
def test_worker_check_sequential_checker(self) -> None:
"""Same as test_worker_check_single_file_no_checkers with SequentialTestChecker."""
@@ -430,7 +434,9 @@ class TestCheckParallel:
(10, 2, 3),
],
)
- def test_compare_workers_to_single_proc(self, num_files, num_jobs, num_checkers):
+ def test_compare_workers_to_single_proc(
+ self, num_files: int, num_jobs: int, num_checkers: int
+ ) -> None:
"""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,
@@ -527,7 +533,7 @@ class TestCheckParallel:
(10, 2, 3),
],
)
- def test_map_reduce(self, num_files, num_jobs, num_checkers):
+ def test_map_reduce(self, num_files: int, num_jobs: int, num_checkers: int) -> None:
"""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 e1b090395..7e9116e99 100644
--- a/tests/test_epylint.py
+++ b/tests/test_epylint.py
@@ -25,12 +25,12 @@ def example_path(tmp_path: PosixPath) -> PosixPath:
def test_epylint_good_command(example_path: PosixPath) -> None:
- out, err = lint.py_run(
- # pylint: disable-next=consider-using-f-string
- "%s -E --disable=E1111 --msg-template '{category} {module} {obj} {line} {column} {msg}'"
- % example_path,
- return_std=True,
- )
+ with pytest.warns(DeprecationWarning):
+ out, _ = lint.py_run(
+ f"{example_path} -E --disable=E1111 --msg-template "
+ "'{category} {module} {obj} {line} {column} {msg}'",
+ return_std=True,
+ )
msg = out.read()
assert (
msg
@@ -39,16 +39,16 @@ def test_epylint_good_command(example_path: PosixPath) -> None:
error my_app IvrAudioApp.run 4 8 Instance of 'IvrAudioApp' has no 'hassan' member
"""
)
- assert err.read() == ""
def test_epylint_strange_command(example_path: PosixPath) -> None:
- out, err = lint.py_run(
- # pylint: disable-next=consider-using-f-string
- "%s -E --disable=E1111 --msg-template={category} {module} {obj} {line} {column} {msg}"
- % example_path,
- return_std=True,
- )
+ with pytest.warns(DeprecationWarning):
+ out, _ = lint.py_run(
+ # pylint: disable-next=consider-using-f-string
+ "%s -E --disable=E1111 --msg-template={category} {module} {obj} {line} {column} {msg}"
+ % example_path,
+ return_std=True,
+ )
assert (
out.read()
== """\
@@ -66,4 +66,3 @@ def test_epylint_strange_command(example_path: PosixPath) -> None:
error
"""
)
- assert err.read() == ""
diff --git a/tests/test_func.py b/tests/test_func.py
index 23f5ff102..493489aee 100644
--- a/tests/test_func.py
+++ b/tests/test_func.py
@@ -25,11 +25,13 @@ FILTER_RGX = None
INFO_TEST_RGX = re.compile(r"^func_i\d\d\d\d$")
-def exception_str(self, ex) -> str: # pylint: disable=unused-argument
+def exception_str(
+ self: Exception, ex: Exception # pylint: disable=unused-argument
+) -> str:
"""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)}"
+ return f"in {ex.file}\n:: {', '.join(ex.args)}" # type: ignore[attr-defined] # Defined in the caller
class LintTestUsingModule:
@@ -92,7 +94,7 @@ class LintTestUsingModule:
class LintTestUpdate(LintTestUsingModule):
- def _check_result(self, got):
+ def _check_result(self, got: str) -> None:
if not self._has_output():
return
try:
@@ -100,18 +102,20 @@ class LintTestUpdate(LintTestUsingModule):
except OSError:
expected = ""
if got != expected:
- with open(self.output, "w", encoding="utf-8") as f:
+ with open(self.output or "", "w", encoding="utf-8") as f:
f.write(got)
-def gen_tests(filter_rgx):
+def gen_tests(
+ filter_rgx: str | re.Pattern[str] | None,
+) -> list[tuple[str, str, list[tuple[str, str]]]]:
if filter_rgx:
is_to_run = re.compile(filter_rgx).search
else:
is_to_run = (
- lambda x: 1 # pylint: disable=unnecessary-lambda-assignment
+ lambda x: 1 # type: ignore[assignment,misc] # pylint: disable=unnecessary-lambda-assignment
) # noqa: E731 We're going to throw all this anyway
- tests = []
+ tests: list[tuple[str, str, list[tuple[str, str]]]] = []
for module_file, messages_file in _get_tests_info(INPUT_DIR, MSG_DIR, "func_", ""):
if not is_to_run(module_file) or module_file.endswith((".pyc", "$py.class")):
continue
@@ -135,7 +139,10 @@ TEST_WITH_EXPECTED_DEPRECATION = ["func_excess_escapes.py"]
ids=[o[0] for o in gen_tests(FILTER_RGX)],
)
def test_functionality(
- module_file, messages_file, dependencies, recwarn: pytest.WarningsRecorder
+ module_file: str,
+ messages_file: str,
+ dependencies: list[tuple[str, str]],
+ recwarn: pytest.WarningsRecorder,
) -> None:
__test_functionality(module_file, messages_file, dependencies)
if recwarn.list:
diff --git a/tests/test_functional.py b/tests/test_functional.py
index 74b541bcf..77cdbc58f 100644
--- a/tests/test_functional.py
+++ b/tests/test_functional.py
@@ -11,7 +11,6 @@ from pathlib import Path
import pytest
from _pytest.config import Config
-from _pytest.recwarn import WarningsRecorder
from pylint import testutils
from pylint.testutils import UPDATE_FILE, UPDATE_OPTION
@@ -33,34 +32,28 @@ TESTS = [
]
TESTS_NAMES = [t.base for t in TESTS]
TEST_WITH_EXPECTED_DEPRECATION = [
+ "anomalous_backslash_escape",
+ "anomalous_unicode_escape",
+ "excess_escapes",
"future_unicode_literals",
- "anomalous_unicode_escape_py3",
]
@pytest.mark.parametrize("test_file", TESTS, ids=TESTS_NAMES)
-def test_functional(
- test_file: FunctionalTestFile, recwarn: WarningsRecorder, pytestconfig: Config
-) -> None:
+def test_functional(test_file: FunctionalTestFile, pytestconfig: Config) -> None:
__tracebackhide__ = True # pylint: disable=unused-variable
+ lint_test: LintModuleOutputUpdate | testutils.LintModuleTest
if UPDATE_FILE.exists():
- lint_test: (
- LintModuleOutputUpdate | testutils.LintModuleTest
- ) = LintModuleOutputUpdate(test_file, pytestconfig)
+ lint_test = LintModuleOutputUpdate(test_file, pytestconfig)
else:
lint_test = testutils.LintModuleTest(test_file, pytestconfig)
lint_test.setUp()
- lint_test.runTest()
- if recwarn.list:
- if (
- test_file.base in TEST_WITH_EXPECTED_DEPRECATION
- and sys.version_info.minor > 5
- ):
- assert any(
- "invalid escape sequence" in str(i.message)
- for i in recwarn.list
- if issubclass(i.category, DeprecationWarning)
- )
+
+ if test_file.base in TEST_WITH_EXPECTED_DEPRECATION:
+ with pytest.warns(DeprecationWarning, match="invalid escape sequence"):
+ lint_test.runTest()
+ else:
+ lint_test.runTest()
if __name__ == "__main__":
diff --git a/tests/test_import_graph.py b/tests/test_import_graph.py
index a05ebbd71..2ad51f889 100644
--- a/tests/test_import_graph.py
+++ b/tests/test_import_graph.py
@@ -20,7 +20,7 @@ from pylint.lint import PyLinter
@pytest.fixture
-def dest(request: SubRequest) -> Iterator[Iterator | Iterator[str]]:
+def dest(request: SubRequest) -> Iterator[str]:
dest = request.param
yield dest
try:
@@ -74,7 +74,7 @@ def linter() -> PyLinter:
@pytest.fixture
-def remove_files() -> Iterator:
+def remove_files() -> Iterator[None]:
yield
for fname in ("import.dot", "ext_import.dot", "int_import.dot"):
try:
diff --git a/tests/test_numversion.py b/tests/test_numversion.py
index 1bfb451da..2c34c1aa3 100644
--- a/tests/test_numversion.py
+++ b/tests/test_numversion.py
@@ -2,6 +2,8 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
+from __future__ import annotations
+
import pytest
from pylint.__pkginfo__ import get_numversion_from_version
@@ -23,5 +25,5 @@ from pylint.__pkginfo__ import get_numversion_from_version
["2.8.3.dev3+g28c093c2.d20210428", (2, 8, 3)],
],
)
-def test_numversion(version, expected_numversion):
+def test_numversion(version: str, expected_numversion: tuple[int, int, int]) -> None:
assert get_numversion_from_version(version) == expected_numversion
diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py
index c83f3e627..06a16c3a5 100644
--- a/tests/test_pylint_runners.py
+++ b/tests/test_pylint_runners.py
@@ -10,47 +10,75 @@ import os
import pathlib
import shlex
import sys
-from collections.abc import Callable
+from collections.abc import Sequence
from io import BufferedReader
-from typing import Any
+from typing import Any, NoReturn
from unittest.mock import MagicMock, mock_open, patch
import pytest
-from py._path.local import LocalPath # type: ignore[import]
from pylint import run_epylint, run_pylint, run_pyreverse, run_symilar
from pylint.lint import Run
from pylint.testutils import GenericTestReporter as Reporter
+from pylint.testutils.utils import _test_cwd
+if sys.version_info >= (3, 8):
+ from typing import Protocol
+else:
+ from typing_extensions import Protocol
-@pytest.mark.parametrize(
- "runner", [run_epylint, run_pylint, run_pyreverse, run_symilar]
-)
-def test_runner(runner: Callable, tmpdir: LocalPath) -> None:
+
+class _RunCallable(Protocol): # pylint: disable=too-few-public-methods
+ def __call__(self, argv: Sequence[str] | None = None) -> NoReturn | None:
+ ...
+
+
+@pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar])
+def test_runner(runner: _RunCallable, tmp_path: pathlib.Path) -> None:
filepath = os.path.abspath(__file__)
testargs = ["", filepath]
- with tmpdir.as_cwd():
+ with _test_cwd(tmp_path):
with patch.object(sys, "argv", testargs):
with pytest.raises(SystemExit) as err:
runner()
assert err.value.code == 0
-@pytest.mark.parametrize(
- "runner", [run_epylint, run_pylint, run_pyreverse, run_symilar]
-)
-def test_runner_with_arguments(runner: Callable, tmpdir: LocalPath) -> None:
+def test_epylint(tmp_path: pathlib.Path) -> None:
+ """TODO: 3.0 delete with epylint."""
+ filepath = os.path.abspath(__file__)
+ with _test_cwd(tmp_path):
+ with patch.object(sys, "argv", ["", filepath]):
+ with pytest.raises(SystemExit) as err:
+ with pytest.warns(DeprecationWarning):
+ run_epylint()
+ assert err.value.code == 0
+
+
+@pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar])
+def test_runner_with_arguments(runner: _RunCallable, tmp_path: pathlib.Path) -> None:
"""Check the runners with arguments as parameter instead of sys.argv."""
filepath = os.path.abspath(__file__)
testargs = [filepath]
- with tmpdir.as_cwd():
+ with _test_cwd(tmp_path):
with pytest.raises(SystemExit) as err:
runner(testargs)
assert err.value.code == 0
+def test_epylint_with_arguments(tmp_path: pathlib.Path) -> None:
+ """TODO: 3.0 delete with epylint."""
+ filepath = os.path.abspath(__file__)
+ testargs = [filepath]
+ with _test_cwd(tmp_path):
+ with pytest.raises(SystemExit) as err:
+ with pytest.warns(DeprecationWarning):
+ run_epylint(testargs)
+ assert err.value.code == 0
+
+
def test_pylint_argument_deduplication(
- tmpdir: LocalPath, tests_directory: pathlib.Path
+ tmp_path: pathlib.Path, tests_directory: pathlib.Path
) -> None:
"""Check that the Pylint runner does not over-report on duplicate
arguments.
@@ -62,7 +90,7 @@ def test_pylint_argument_deduplication(
testargs = shlex.split("--report n --score n --max-branches 13")
testargs.extend([filepath] * 4)
exit_stack = contextlib.ExitStack()
- exit_stack.enter_context(tmpdir.as_cwd())
+ exit_stack.enter_context(_test_cwd(tmp_path))
exit_stack.enter_context(patch.object(sys, "argv", testargs))
err = exit_stack.enter_context(pytest.raises(SystemExit))
with exit_stack:
@@ -71,7 +99,7 @@ def test_pylint_argument_deduplication(
def test_pylint_run_jobs_equal_zero_dont_crash_with_cpu_fraction(
- tmpdir: LocalPath,
+ tmp_path: pathlib.Path,
) -> None:
"""Check that the pylint runner does not crash if `pylint.lint.run._query_cpu`
determines only a fraction of a CPU core to be available.
@@ -80,21 +108,21 @@ def test_pylint_run_jobs_equal_zero_dont_crash_with_cpu_fraction(
def _mock_open(*args: Any, **kwargs: Any) -> BufferedReader:
if args[0] == "/sys/fs/cgroup/cpu/cpu.cfs_quota_us":
- return mock_open(read_data=b"-1")(*args, **kwargs)
+ return mock_open(read_data=b"-1")(*args, **kwargs) # type: ignore[no-any-return]
if args[0] == "/sys/fs/cgroup/cpu/cpu.shares":
- return mock_open(read_data=b"2")(*args, **kwargs)
- return builtin_open(*args, **kwargs)
+ return mock_open(read_data=b"2")(*args, **kwargs) # type: ignore[no-any-return]
+ return builtin_open(*args, **kwargs) # type: ignore[no-any-return]
pathlib_path = pathlib.Path
- def _mock_path(*args: str, **kwargs) -> pathlib.Path:
+ def _mock_path(*args: str, **kwargs: Any) -> pathlib.Path:
if args[0] == "/sys/fs/cgroup/cpu/cpu.shares":
return MagicMock(is_file=lambda: True)
return pathlib_path(*args, **kwargs)
filepath = os.path.abspath(__file__)
testargs = [filepath, "--jobs=0"]
- with tmpdir.as_cwd():
+ with _test_cwd(tmp_path):
with pytest.raises(SystemExit) as err:
with patch("builtins.open", _mock_open):
with patch("pylint.lint.run.Path", _mock_path):
diff --git a/tests/test_regr.py b/tests/test_regr.py
index 80492ae78..eb8ad6c5d 100644
--- a/tests/test_regr.py
+++ b/tests/test_regr.py
@@ -27,12 +27,12 @@ sys.path.insert(1, REGR_DATA)
@pytest.fixture(scope="module")
-def reporter():
+def reporter() -> type[testutils.GenericTestReporter]:
return testutils.GenericTestReporter
@pytest.fixture(scope="module")
-def disable():
+def disable() -> list[str]:
return ["I"]
@@ -48,7 +48,7 @@ def finalize_linter(linter: PyLinter) -> Iterator[PyLinter]:
linter.reporter.finalize()
-def Equals(expected):
+def Equals(expected: str) -> Callable[[str], bool]:
return lambda got: got == expected
@@ -67,7 +67,7 @@ def Equals(expected):
],
)
def test_package(
- finalize_linter: PyLinter, file_names: list[str], check: Callable
+ finalize_linter: PyLinter, file_names: list[str], check: Callable[[str], bool]
) -> None:
finalize_linter.check(file_names)
finalize_linter.reporter = cast( # Due to fixture
@@ -101,7 +101,7 @@ def test_descriptor_crash(fname: str, finalize_linter: PyLinter) -> None:
@pytest.fixture
-def modify_path() -> Iterator:
+def modify_path() -> Iterator[None]:
cwd = os.getcwd()
sys.path.insert(0, "")
yield
diff --git a/tests/test_self.py b/tests/test_self.py
index 010e60682..587b8bb58 100644
--- a/tests/test_self.py
+++ b/tests/test_self.py
@@ -27,14 +27,13 @@ from unittest import mock
from unittest.mock import patch
import pytest
-from py._path.local import LocalPath # type: ignore[import]
from pylint import extensions, modify_sys_path
from pylint.constants import MAIN_CHECKER_NAME, MSG_TYPES_STATUS
from pylint.lint.pylinter import PyLinter
from pylint.message import Message
-from pylint.reporters import JSONReporter
-from pylint.reporters.text import BaseReporter, ColorizedTextReporter, TextReporter
+from pylint.reporters import BaseReporter, JSONReporter
+from pylint.reporters.text import ColorizedTextReporter, TextReporter
from pylint.testutils._run import _add_rcfile_default_pylintrc
from pylint.testutils._run import _Run as Run
from pylint.testutils.utils import (
@@ -62,7 +61,7 @@ UNNECESSARY_LAMBDA = join(
@contextlib.contextmanager
-def _configure_lc_ctype(lc_ctype: str) -> Iterator:
+def _configure_lc_ctype(lc_ctype: str) -> Iterator[None]:
lc_ctype_env = "LC_CTYPE"
original_lctype = os.environ.get(lc_ctype_env)
os.environ[lc_ctype_env] = lc_ctype
@@ -98,8 +97,8 @@ class MultiReporter(BaseReporter):
def out(self) -> TextIO: # type: ignore[override]
return self._reporters[0].out
- @property # type: ignore[override]
- def linter(self) -> PyLinter: # type: ignore[override]
+ @property
+ def linter(self) -> PyLinter:
return self._linter
@linter.setter
@@ -140,7 +139,7 @@ class TestRunTC:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
Run(args, reporter=reporter)
- return cm.value.code
+ return int(cm.value.code)
@staticmethod
def _clean_paths(output: str) -> str:
@@ -162,7 +161,7 @@ class TestRunTC:
assert unexpected_output.strip() not in actual_output.strip()
def _test_output_file(
- self, args: list[str], filename: LocalPath, expected_output: str
+ self, args: list[str], filename: Path, expected_output: str
) -> None:
"""Run Pylint with the ``output`` option set (must be included in
the ``args`` passed to this method!) and check the file content afterwards.
@@ -297,6 +296,39 @@ class TestRunTC:
actual_output = actual_output[actual_output.find("\n") :]
assert self._clean_paths(expected_output.strip()) == actual_output.strip()
+ def test_type_annotation_names(self) -> None:
+ """Test resetting the `_type_annotation_names` list to `[]` when leaving a module.
+
+ An import inside `module_a`, which is used as a type annotation in `module_a`, should not prevent
+ emitting the `unused-import` message when the same import occurs in `module_b` & is unused.
+ See: https://github.com/PyCQA/pylint/issues/4150
+ """
+ module1 = join(
+ HERE, "regrtest_data", "imported_module_in_typehint", "module_a.py"
+ )
+
+ module2 = join(
+ HERE, "regrtest_data", "imported_module_in_typehint", "module_b.py"
+ )
+ expected_output = textwrap.dedent(
+ f"""
+ ************* Module module_b
+ {module2}:1:0: W0611: Unused import uuid (unused-import)
+ """
+ )
+ args = [
+ module1,
+ module2,
+ "--disable=all",
+ "--enable=unused-import",
+ "-rn",
+ "-sn",
+ ]
+ out = StringIO()
+ self._run_pylint(args, out=out)
+ actual_output = self._clean_paths(out.getvalue().strip())
+ assert self._clean_paths(expected_output.strip()) in actual_output.strip()
+
def test_import_itself_not_accounted_for_relative_imports(self) -> None:
expected = "Your code has been rated at 10.00/10"
package = join(HERE, "regrtest_data", "dummy")
@@ -477,7 +509,7 @@ class TestRunTC:
self._test_output([module, "-E"], expected_output=expected_output)
@pytest.mark.skipif(sys.platform == "win32", reason="only occurs on *nix")
- def test_parseable_file_path(self):
+ def test_parseable_file_path(self) -> None:
file_name = "test_target.py"
fake_path = HERE + os.getcwd()
module = join(fake_path, file_name)
@@ -503,7 +535,7 @@ class TestRunTC:
("mymodule.py", "mymodule", "mymodule.py"),
],
)
- def test_stdin(self, input_path, module, expected_path):
+ def test_stdin(self, input_path: str, module: str, expected_path: str) -> None:
expected_output = f"""************* Module {module}
{expected_path}:1:0: W0611: Unused import os (unused-import)
@@ -522,8 +554,8 @@ class TestRunTC:
self._runtest(["--from-stdin"], code=32)
@pytest.mark.parametrize("write_bpy_to_disk", [False, True])
- def test_relative_imports(self, write_bpy_to_disk, tmpdir):
- a = tmpdir.join("a")
+ def test_relative_imports(self, write_bpy_to_disk: bool, tmp_path: Path) -> None:
+ a = tmp_path / "a"
b_code = textwrap.dedent(
"""
@@ -543,12 +575,12 @@ class TestRunTC:
)
a.mkdir()
- a.join("__init__.py").write("")
+ (a / "__init__.py").write_text("")
if write_bpy_to_disk:
- a.join("b.py").write(b_code)
- a.join("c.py").write(c_code)
+ (a / "b.py").write_text(b_code)
+ (a / "c.py").write_text(c_code)
- with tmpdir.as_cwd():
+ with _test_cwd(tmp_path):
# why don't we start pylint in a sub-process?
expected = (
"************* Module a.b\n"
@@ -696,14 +728,14 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
(-9, "missing-function-docstring", "fail_under_minus10.py", 22),
(-5, "missing-function-docstring", "fail_under_minus10.py", 22),
# --fail-under should guide whether error code as missing-function-docstring is not hit
- (-10, "broad-except", "fail_under_plus7_5.py", 0),
- (6, "broad-except", "fail_under_plus7_5.py", 0),
- (7.5, "broad-except", "fail_under_plus7_5.py", 0),
- (7.6, "broad-except", "fail_under_plus7_5.py", 16),
- (-11, "broad-except", "fail_under_minus10.py", 0),
- (-10, "broad-except", "fail_under_minus10.py", 0),
- (-9, "broad-except", "fail_under_minus10.py", 22),
- (-5, "broad-except", "fail_under_minus10.py", 22),
+ (-10, "broad-exception-caught", "fail_under_plus7_5.py", 0),
+ (6, "broad-exception-caught", "fail_under_plus7_5.py", 0),
+ (7.5, "broad-exception-caught", "fail_under_plus7_5.py", 0),
+ (7.6, "broad-exception-caught", "fail_under_plus7_5.py", 16),
+ (-11, "broad-exception-caught", "fail_under_minus10.py", 0),
+ (-10, "broad-exception-caught", "fail_under_minus10.py", 0),
+ (-9, "broad-exception-caught", "fail_under_minus10.py", 22),
+ (-5, "broad-exception-caught", "fail_under_minus10.py", 22),
# Enable by message id
(-10, "C0116", "fail_under_plus7_5.py", 16),
# Enable by category
@@ -713,7 +745,7 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
(-10, "C0115", "fail_under_plus7_5.py", 0),
],
)
- def test_fail_on(self, fu_score, fo_msgs, fname, out):
+ def test_fail_on(self, fu_score: int, fo_msgs: str, fname: str, out: int) -> None:
self._runtest(
[
"--fail-under",
@@ -741,7 +773,7 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
(["--fail-on=C0116", "--disable=C0116"], 16),
],
)
- def test_fail_on_edge_case(self, opts, out):
+ def test_fail_on_edge_case(self, opts: list[str], out: int) -> None:
self._runtest(
opts + [join(HERE, "regrtest_data", "fail_under_plus7_5.py")],
code=out,
@@ -847,34 +879,34 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
],
)
def test_do_not_import_files_from_local_directory(
- self, tmpdir: LocalPath, args: list[str]
+ self, tmp_path: Path, args: list[str]
) -> None:
for path in ("astroid.py", "hmac.py"):
- file_path = tmpdir / path
- file_path.write("'Docstring'\nimport completely_unknown\n")
+ file_path = tmp_path / path
+ file_path.write_text("'Docstring'\nimport completely_unknown\n")
pylint_call = [sys.executable, "-m", "pylint"] + args + [path]
- with tmpdir.as_cwd():
- subprocess.check_output(pylint_call, cwd=str(tmpdir))
+ with _test_cwd(tmp_path):
+ subprocess.check_output(pylint_call, cwd=str(tmp_path))
new_python_path = os.environ.get("PYTHONPATH", "").strip(":")
- with tmpdir.as_cwd(), _test_environ_pythonpath(f"{new_python_path}:"):
+ with _test_cwd(tmp_path), _test_environ_pythonpath(f"{new_python_path}:"):
# Appending a colon to PYTHONPATH should not break path stripping
# https://github.com/PyCQA/pylint/issues/3636
- subprocess.check_output(pylint_call, cwd=str(tmpdir))
+ subprocess.check_output(pylint_call, cwd=str(tmp_path))
@staticmethod
def test_import_plugin_from_local_directory_if_pythonpath_cwd(
- tmpdir: LocalPath,
+ tmp_path: Path,
) -> None:
- p_plugin = tmpdir / "plugin.py"
- p_plugin.write("# Some plugin content")
+ p_plugin = tmp_path / "plugin.py"
+ p_plugin.write_text("# Some plugin content")
if sys.platform == "win32":
python_path = "."
else:
python_path = f"{os.environ.get('PYTHONPATH', '').strip(':')}:."
- with tmpdir.as_cwd(), _test_environ_pythonpath(python_path):
+ with _test_cwd(tmp_path), _test_environ_pythonpath(python_path):
args = [sys.executable, "-m", "pylint", "--load-plugins", "plugin"]
process = subprocess.run(
- args, cwd=str(tmpdir), stderr=subprocess.PIPE, check=False
+ args, cwd=str(tmp_path), stderr=subprocess.PIPE, check=False
)
assert (
"AttributeError: module 'plugin' has no attribute 'register'"
@@ -882,18 +914,18 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
)
def test_allow_import_of_files_found_in_modules_during_parallel_check(
- self, tmpdir: LocalPath
+ self, tmp_path: Path
) -> None:
- test_directory = tmpdir / "test_directory"
+ test_directory = tmp_path / "test_directory"
test_directory.mkdir()
spam_module = test_directory / "spam.py"
- spam_module.write("'Empty'")
+ spam_module.write_text("'Empty'")
init_module = test_directory / "__init__.py"
- init_module.write("'Empty'")
+ init_module.write_text("'Empty'")
# For multiple jobs we could not find the `spam.py` file.
- with tmpdir.as_cwd():
+ with _test_cwd(tmp_path):
args = [
"-j2",
"--disable=missing-docstring, missing-final-newline",
@@ -902,7 +934,7 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
self._runtest(args, code=0)
# A single job should be fine as well
- with tmpdir.as_cwd():
+ with _test_cwd(tmp_path):
args = [
"-j1",
"--disable=missing-docstring, missing-final-newline",
@@ -911,11 +943,11 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
self._runtest(args, code=0)
@staticmethod
- def test_can_list_directories_without_dunder_init(tmpdir: LocalPath) -> None:
- test_directory = tmpdir / "test_directory"
+ def test_can_list_directories_without_dunder_init(tmp_path: Path) -> None:
+ test_directory = tmp_path / "test_directory"
test_directory.mkdir()
spam_module = test_directory / "spam.py"
- spam_module.write("'Empty'")
+ spam_module.write_text("'Empty'")
subprocess.check_output(
[
@@ -925,7 +957,7 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
"--disable=missing-docstring, missing-final-newline",
"test_directory",
],
- cwd=str(tmpdir),
+ cwd=str(tmp_path),
stderr=subprocess.PIPE,
)
@@ -943,9 +975,9 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
)
self._test_output([path, "-j2"], expected_output="")
- def test_output_file_valid_path(self, tmpdir: LocalPath) -> None:
+ def test_output_file_valid_path(self, tmp_path: Path) -> None:
path = join(HERE, "regrtest_data", "unused_variable.py")
- output_file = tmpdir / "output.txt"
+ output_file = tmp_path / "output.txt"
expected = "Your code has been rated at 7.50/10"
self._test_output_file(
[path, f"--output={output_file}"],
@@ -972,7 +1004,7 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
(["--fail-on=useless-suppression", "--enable=C"], 22),
],
)
- def test_fail_on_exit_code(self, args, expected):
+ def test_fail_on_exit_code(self, args: list[str], expected: int) -> None:
path = join(HERE, "regrtest_data", "fail_on.py")
# We set fail-under to be something very low so that even with the warnings
# and errors that are generated they don't affect the exit code.
@@ -998,7 +1030,7 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
(["--fail-on=useless-suppression", "--enable=C"], 1),
],
)
- def test_fail_on_info_only_exit_code(self, args, expected):
+ def test_fail_on_info_only_exit_code(self, args: list[str], expected: int) -> None:
path = join(HERE, "regrtest_data", "fail_on_info_only.py")
self._runtest([path] + args, code=expected)
@@ -1025,10 +1057,10 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
],
)
def test_output_file_can_be_combined_with_output_format_option(
- self, tmpdir, output_format, expected_output
- ):
+ self, tmp_path: Path, output_format: str, expected_output: str
+ ) -> None:
path = join(HERE, "regrtest_data", "unused_variable.py")
- output_file = tmpdir / "output.txt"
+ output_file = tmp_path / "output.txt"
self._test_output_file(
[path, f"--output={output_file}", f"--output-format={output_format}"],
output_file,
@@ -1036,10 +1068,10 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
)
def test_output_file_can_be_combined_with_custom_reporter(
- self, tmpdir: LocalPath
+ self, tmp_path: Path
) -> None:
path = join(HERE, "regrtest_data", "unused_variable.py")
- output_file = tmpdir / "output.txt"
+ output_file = tmp_path / "output.txt"
# It does not really have to be a truly custom reporter.
# It is only important that it is being passed explicitly to ``Run``.
myreporter = TextReporter()
@@ -1050,9 +1082,9 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er
)
assert output_file.exists()
- def test_output_file_specified_in_rcfile(self, tmpdir: LocalPath) -> None:
- output_file = tmpdir / "output.txt"
- rcfile = tmpdir / "pylintrc"
+ def test_output_file_specified_in_rcfile(self, tmp_path: Path) -> None:
+ output_file = tmp_path / "output.txt"
+ rcfile = tmp_path / "pylintrc"
rcfile_contents = textwrap.dedent(
f"""
[MAIN]
diff --git a/tests/test_similar.py b/tests/test_similar.py
index d59602ff6..5558b70e7 100644
--- a/tests/test_similar.py
+++ b/tests/test_similar.py
@@ -36,7 +36,7 @@ class TestSimilarCodeChecker:
@staticmethod
def _run_pylint(args: list[str], out: TextIO) -> int:
"""Runs pylint with a patched output."""
- args = args + [
+ args += [
"--persistent=no",
"--enable=astroid-error",
# Enable functionality that will build another ast
@@ -48,7 +48,7 @@ class TestSimilarCodeChecker:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
Run(args)
- return cm.value.code
+ return int(cm.value.code)
@staticmethod
def _clean_paths(output: str) -> str:
diff --git a/tests/testutils/_primer/test_package_to_lint.py b/tests/testutils/_primer/test_package_to_lint.py
index 2ee9f3dec..220e2c0b2 100644
--- a/tests/testutils/_primer/test_package_to_lint.py
+++ b/tests/testutils/_primer/test_package_to_lint.py
@@ -42,8 +42,8 @@ def test_package_to_lint_default_value() -> None:
branch="main",
directories=["src/flask"], # Must work on Windows (src\\flask)
)
- assert package_to_lint.pylintrc is None
+ assert package_to_lint.pylintrc == ""
expected_path_to_lint = (
PRIMER_DIRECTORY_PATH / "pallets" / "flask" / "src" / "flask"
)
- assert package_to_lint.pylint_args == [str(expected_path_to_lint)]
+ assert package_to_lint.pylint_args == [str(expected_path_to_lint), "--rcfile="]
diff --git a/tests/testutils/test_functional_testutils.py b/tests/testutils/test_functional_testutils.py
index 9090661be..68dad697d 100644
--- a/tests/testutils/test_functional_testutils.py
+++ b/tests/testutils/test_functional_testutils.py
@@ -45,7 +45,7 @@ def test_get_functional_test_files_from_directory() -> None:
get_functional_test_files_from_directory(DATA_DIRECTORY)
-def test_minimal_messages_config_enabled(pytest_config) -> None:
+def test_minimal_messages_config_enabled(pytest_config: MagicMock) -> None:
"""Test that all messages not targeted in the functional test are disabled
when running with --minimal-messages-config.
"""
@@ -68,7 +68,7 @@ def test_minimal_messages_config_enabled(pytest_config) -> None:
assert not mod_test._linter.is_message_enabled("unused-import")
-def test_minimal_messages_config_excluded_file(pytest_config) -> None:
+def test_minimal_messages_config_excluded_file(pytest_config: MagicMock) -> None:
"""Test that functional test files can be excluded from the run with
--minimal-messages-config if they set the exclude_from_minimal_messages_config
option in their rcfile.
diff --git a/tests/testutils/test_output_line.py b/tests/testutils/test_output_line.py
index 2a21ce1fd..5b2bf1a1b 100644
--- a/tests/testutils/test_output_line.py
+++ b/tests/testutils/test_output_line.py
@@ -6,7 +6,7 @@
from __future__ import annotations
-from collections.abc import Callable
+import sys
import pytest
@@ -16,9 +16,19 @@ from pylint.message import Message
from pylint.testutils.output_line import OutputLine
from pylint.typing import MessageLocationTuple
+if sys.version_info >= (3, 8):
+ from typing import Protocol
+else:
+ from typing_extensions import Protocol
+
+
+class _MessageCallable(Protocol):
+ def __call__(self, confidence: Confidence = HIGH) -> Message:
+ ...
+
@pytest.fixture()
-def message() -> Callable:
+def message() -> _MessageCallable:
def inner(confidence: Confidence = HIGH) -> Message:
return Message(
symbol="missing-docstring",
@@ -55,7 +65,7 @@ def test_output_line() -> None:
assert output_line.confidence == "HIGH"
-def test_output_line_from_message(message: Callable) -> None:
+def test_output_line_from_message(message: _MessageCallable) -> None:
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg."""
expected_column = 2 if PY38_PLUS else 0
@@ -91,7 +101,7 @@ def test_output_line_from_message(message: Callable) -> None:
@pytest.mark.parametrize("confidence", [HIGH, INFERENCE])
-def test_output_line_to_csv(confidence: Confidence, message: Callable) -> None:
+def test_output_line_to_csv(confidence: Confidence, message: _MessageCallable) -> None:
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg
and then converted to csv.
"""
diff --git a/tests/testutils/test_testutils_utils.py b/tests/testutils/test_testutils_utils.py
index 79f4e2a81..b521e25c4 100644
--- a/tests/testutils/test_testutils_utils.py
+++ b/tests/testutils/test_testutils_utils.py
@@ -6,6 +6,8 @@ import os
import sys
from pathlib import Path
+import pytest
+
from pylint.testutils.utils import _test_cwd, _test_environ_pythonpath, _test_sys_path
@@ -50,22 +52,28 @@ def test__test_cwd(tmp_path: Path) -> None:
assert os.getcwd() == cwd
-def test__test_environ_pythonpath_no_arg() -> None:
- python_path = os.environ.get("PYTHONPATH")
- with _test_environ_pythonpath():
- assert os.environ.get("PYTHONPATH") == python_path
- new_pythonpath = "./whatever/:"
- os.environ["PYTHONPATH"] = new_pythonpath
- assert os.environ.get("PYTHONPATH") == new_pythonpath
- assert os.environ.get("PYTHONPATH") == python_path
+@pytest.mark.parametrize("old_pythonpath", ["./oldpath/:", None])
+def test__test_environ_pythonpath_no_arg(old_pythonpath: str) -> None:
+ real_pythonpath = os.environ.get("PYTHONPATH")
+ with _test_environ_pythonpath(old_pythonpath):
+ with _test_environ_pythonpath():
+ assert os.environ.get("PYTHONPATH") is None
+ new_pythonpath = "./whatever/:"
+ os.environ["PYTHONPATH"] = new_pythonpath
+ assert os.environ.get("PYTHONPATH") == new_pythonpath
+ assert os.environ.get("PYTHONPATH") == old_pythonpath
+ assert os.environ.get("PYTHONPATH") == real_pythonpath
-def test__test_environ_pythonpath() -> None:
- python_path = os.environ.get("PYTHONPATH")
- new_pythonpath = "./whatever/:"
- with _test_environ_pythonpath(new_pythonpath):
- assert os.environ.get("PYTHONPATH") == new_pythonpath
- newer_pythonpath = "./something_else/:"
- os.environ["PYTHONPATH"] = newer_pythonpath
- assert os.environ.get("PYTHONPATH") == newer_pythonpath
- assert os.environ.get("PYTHONPATH") == python_path
+@pytest.mark.parametrize("old_pythonpath", ["./oldpath/:", None])
+def test__test_environ_pythonpath(old_pythonpath: str) -> None:
+ real_pythonpath = os.environ.get("PYTHONPATH")
+ with _test_environ_pythonpath(old_pythonpath):
+ new_pythonpath = "./whatever/:"
+ with _test_environ_pythonpath(new_pythonpath):
+ assert os.environ.get("PYTHONPATH") == new_pythonpath
+ newer_pythonpath = "./something_else/:"
+ os.environ["PYTHONPATH"] = newer_pythonpath
+ assert os.environ.get("PYTHONPATH") == newer_pythonpath
+ assert os.environ.get("PYTHONPATH") == old_pythonpath
+ assert os.environ.get("PYTHONPATH") == real_pythonpath
diff --git a/tests/utils/unittest_ast_walker.py b/tests/utils/unittest_ast_walker.py
index 43614c0ed..5a2dc6609 100644
--- a/tests/utils/unittest_ast_walker.py
+++ b/tests/utils/unittest_ast_walker.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import warnings
import astroid
+from astroid import nodes
from pylint.checkers.base_checker import BaseChecker
from pylint.checkers.utils import only_required_for_messages
@@ -27,19 +28,23 @@ class TestASTWalker:
self.called: set[str] = set()
@only_required_for_messages("first-message")
- def visit_module(self, module): # pylint: disable=unused-argument
+ def visit_module(
+ self, module: nodes.Module # pylint: disable=unused-argument
+ ) -> None:
self.called.add("module")
@only_required_for_messages("second-message")
- def visit_call(self, module):
+ def visit_call(self, module: nodes.Call) -> None:
raise NotImplementedError
@only_required_for_messages("second-message", "third-message")
- def visit_assignname(self, module): # pylint: disable=unused-argument
+ def visit_assignname(
+ self, module: nodes.AssignName # pylint: disable=unused-argument
+ ) -> None:
self.called.add("assignname")
@only_required_for_messages("second-message")
- def leave_assignname(self, module):
+ def leave_assignname(self, module: nodes.AssignName) -> None:
raise NotImplementedError
def test_only_required_for_messages(self) -> None:
@@ -59,7 +64,9 @@ class TestASTWalker:
self.called = False
@only_required_for_messages("first-message")
- def visit_assname(self, node): # pylint: disable=unused-argument
+ def visit_assname(
+ self, node: nodes.AssignName # pylint: disable=unused-argument
+ ) -> None:
self.called = True
linter = self.MockLinter({"first-message": True})
diff --git a/towncrier.toml b/towncrier.toml
index b25b28298..51ea3d078 100644
--- a/towncrier.toml
+++ b/towncrier.toml
@@ -7,9 +7,25 @@ issue_format = "`#{issue} <https://github.com/PyCQA/pylint/issues/{issue}>`_"
wrap = true
# Definition of fragment types.
-# TODO: with the next towncrier release (21.9.1) it will be possible to define
-# custom types as toml tables:
-# https://github.com/twisted/towncrier#further-options
+# We want the changelog to show in the same order as the fragment types
+# are defined here. Therefore we have to use the array-style fragment definition.
+# The table-style definition, although more concise, would be sorted alphabetically.
+# https://github.com/twisted/towncrier/issues/437
+[[tool.towncrier.type]]
+directory = "breaking"
+name = "Breaking Changes"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "user_action"
+name = "Changes requiring user actions"
+showcontent = true
+
+[[tool.towncrier.type]]
+directory = "feature"
+name = "New Features"
+showcontent = true
+
[[tool.towncrier.type]]
directory = "new_check"
name = "New Checks"
diff --git a/tox.ini b/tox.ini
index 4a8f67dbe..0c78b8798 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
minversion = 3.0
-envlist = formatting, py37, py38, py39, py310, pypy, benchmark
+envlist = formatting, py37, py38, py39, py310, py311, pypy, benchmark
skip_missing_interpreters = true
requires = pip >=21.3.1
isolated_build = true