diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index 92ae83f9fd..664f6f1d2f 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -81,11 +81,11 @@ body: label: Pylint version description: >- Please copy and paste the result of `pylint --version` or specify the range of - version affected. + versions affected. placeholder: | - pylint 2.9.6 - astroid 2.6.5 - Python 3.8.10 (default, Jun 2 2021, 10:49:15) [GCC 9.4.0] + pylint 3.3.0 + astroid 3.3.0 + Python 3.12.0 (v3.12.0:0fb18b02c8, Oct 2 2023, 09:45:56) render: shell validations: required: true diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index e10646a7c9..856ab0d423 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -21,14 +21,14 @@ jobs: timeout-minutes: 10 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 with: # `towncrier check` runs `git diff --name-only origin/main...`, which # needs a non-shallow clone. fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -41,7 +41,7 @@ jobs: $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 4b305cd2c6..8668ab6ab5 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -11,9 +11,9 @@ on: - "maintenance/**" env: - CACHE_VERSION: 1 + CACHE_VERSION: 2 KEY_PREFIX: base-venv - DEFAULT_PYTHON: "3.11" + DEFAULT_PYTHON: "3.12" PRE_COMMIT_CACHE: ~/.cache/pre-commit concurrency: @@ -33,10 +33,10 @@ jobs: pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -49,7 +49,7 @@ jobs: $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- @@ -71,7 +71,7 @@ jobs: hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT - name: Restore pre-commit environment id: cache-precommit - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: ${{ env.PRE_COMMIT_CACHE }} key: >- @@ -89,16 +89,16 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv fail-on-cache-miss: true @@ -107,7 +107,7 @@ jobs: needs.prepare-base.outputs.python-key }} - name: Restore pre-commit environment id: cache-precommit - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -130,16 +130,16 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv fail-on-cache-miss: true @@ -158,16 +158,16 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv fail-on-cache-miss: true @@ -183,7 +183,9 @@ jobs: . venv/bin/activate cd doc pre-commit run --hook-stage push sphinx-generated-doc --all-files || { + echo "git diff:" ; \ git diff ; \ - echo "Make sure that there are no modifications locally when launching 'make html'" ; \ + echo "End of 'git diff'" ; \ + echo "Make sure that 'make html' succeed without any modifications locally." ; \ exit 1; \ } diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 68d8d84988..61bfd28de8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/primer-test.yaml b/.github/workflows/primer-test.yaml index a6fa2a8753..980f556407 100644 --- a/.github/workflows/primer-test.yaml +++ b/.github/workflows/primer-test.yaml @@ -30,15 +30,15 @@ jobs: timeout-minutes: 5 strategy: matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -51,7 +51,7 @@ jobs: $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- @@ -72,19 +72,19 @@ jobs: needs: prepare-tests-linux strategy: matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv fail-on-cache-miss: true diff --git a/.github/workflows/primer_comment.yaml b/.github/workflows/primer_comment.yaml index 77bd262665..e2b6232635 100644 --- a/.github/workflows/primer_comment.yaml +++ b/.github/workflows/primer_comment.yaml @@ -30,10 +30,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -41,7 +41,7 @@ jobs: # Restore cached Python environment - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: diff --git a/.github/workflows/primer_run_main.yaml b/.github/workflows/primer_run_main.yaml index 9334d96f39..7027b1407b 100644 --- a/.github/workflows/primer_run_main.yaml +++ b/.github/workflows/primer_run_main.yaml @@ -29,23 +29,23 @@ jobs: timeout-minutes: 45 strategy: matrix: - python-version: ["3.8", "3.12"] + python-version: ["3.9", "3.12"] batches: [4] batchIdx: [0, 1, 2, 3] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true # Create a re-usable virtual environment - - name: Create Python virtual environment cache + - name: Restore Python virtual environment cache id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache/restore@v4.2.0 with: path: venv key: @@ -60,6 +60,17 @@ jobs: . venv/bin/activate python -m pip install -U pip setuptools wheel pip install -U -r requirements_test.txt + # Save cached Python environment (explicit because cancel-in-progress: true) + - name: Save Python virtual environment to cache + if: steps.cache-venv.outputs.cache-hit != 'true' + uses: actions/cache/save@v4.2.0 + with: + path: venv + key: + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} # Cache primer packages - name: Get commit string @@ -71,7 +82,7 @@ jobs: echo "commitstring=$output" >> $GITHUB_OUTPUT - name: Restore projects cache id: cache-projects - uses: actions/cache@v4.0.2 + uses: actions/cache/restore@v4.2.0 with: path: tests/.pylint_primer_tests/ key: >- @@ -82,8 +93,16 @@ jobs: run: | . venv/bin/activate python tests/primer/__main__.py prepare --clone + - name: Save projects cache + if: steps.cache-projects.outputs.cache-hit != 'true' + uses: actions/cache/save@v4.2.0 + with: + path: tests/.pylint_primer_tests/ + key: >- + ${{ runner.os }}-${{ matrix.python-version }}-${{ + steps.commitstring.outputs.commitstring }}-primer - name: Upload commit string - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.4.0 if: matrix.batchIdx == 0 with: name: primer_commitstring_${{ matrix.python-version }} @@ -104,7 +123,7 @@ jobs: then echo "::warning ::$WARNINGS" fi - name: Upload output - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.4.0 with: name: primer_output_main_${{ matrix.python-version }}_batch${{ matrix.batchIdx }} diff --git a/.github/workflows/primer_run_pr.yaml b/.github/workflows/primer_run_pr.yaml index 14c3e8892c..9b813c45c4 100644 --- a/.github/workflows/primer_run_pr.yaml +++ b/.github/workflows/primer_run_pr.yaml @@ -38,17 +38,17 @@ jobs: timeout-minutes: 45 strategy: matrix: - python-version: ["3.8", "3.12"] + python-version: ["3.9", "3.12"] batches: [4] batchIdx: [0, 1, 2, 3] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -56,7 +56,7 @@ jobs: # Restore cached Python environment - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache/restore@v4.2.0 with: path: venv key: @@ -72,6 +72,17 @@ jobs: . venv/bin/activate python -m pip install -U pip setuptools wheel pip install -U -r requirements_test.txt + # Save cached Python environment (explicit because cancel-in-progress: true) + - name: Save Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + uses: actions/cache/save@v4.2.0 + with: + path: venv + key: + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} # Cache primer packages - name: Download last 'main' run info @@ -140,7 +151,7 @@ jobs: echo "commitstring=$output" >> $GITHUB_OUTPUT - name: Restore projects cache id: cache-projects - uses: actions/cache@v4.0.2 + uses: actions/cache/restore@v4.2.0 with: path: tests/.pylint_primer_tests/ key: >- @@ -151,6 +162,14 @@ jobs: run: | . venv/bin/activate python tests/primer/__main__.py prepare --clone + - name: Save projects cache + if: steps.cache-projects.outputs.cache-hit != 'true' + uses: actions/cache/save@v4.2.0 + with: + path: tests/.pylint_primer_tests/ + key: >- + ${{ runner.os }}-${{ matrix.python-version }}-${{ + steps.commitstring.outputs.commitstring }}-primer - name: Check cache run: | . venv/bin/activate @@ -178,7 +197,7 @@ jobs: then echo "::warning ::$WARNINGS" fi - name: Upload output of PR - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.4.0 with: name: primer_output_pr_${{ matrix.python-version }}_batch${{ matrix.batchIdx }} @@ -186,7 +205,7 @@ jobs: tests/.pylint_primer_tests/output_${{ matrix.python-version }}_pr_batch${{ matrix.batchIdx }}.txt - name: Upload output of 'main' - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.4.0 with: name: primer_output_main_${{ matrix.python-version }}_batch${{ matrix.batchIdx }} @@ -198,8 +217,8 @@ jobs: echo ${{ github.event.pull_request.number }} | tee pr_number.txt - name: Upload PR number if: - startsWith(steps.python.outputs.python-version, '3.8') && matrix.batchIdx == 0 - uses: actions/upload-artifact@v4.3.3 + startsWith(steps.python.outputs.python-version, '3.9') && matrix.batchIdx == 0 + uses: actions/upload-artifact@v4.4.0 with: name: pr_number path: pr_number.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c36cb0cf2..7aa4c51747 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,10 +20,10 @@ jobs: url: https://pypi.org/project/pylint/ steps: - name: Check out code from Github - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 86edc29880..5559f2b89c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -31,15 +31,15 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] outputs: python-key: ${{ steps.generate-python-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -52,7 +52,7 @@ jobs: $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- @@ -76,9 +76,10 @@ jobs: pip list | grep 'astroid\|pylint' python -m pytest -vv --minimal-messages-config tests/test_functional.py - name: Upload coverage artifact - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.4.0 with: name: coverage-${{ matrix.python-version }} + include-hidden-files: true path: .coverage coverage: @@ -88,16 +89,16 @@ jobs: needs: tests-linux steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python 3.12 id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: "3.12" check-latest: true - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv fail-on-cache-miss: true @@ -105,7 +106,7 @@ jobs: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ needs.tests-linux.outputs.python-key }} - name: Download all coverage artifacts - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v4.1.8 - name: Combine coverage results run: | . venv/bin/activate @@ -128,16 +129,16 @@ jobs: python-version: ["3.12"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv fail-on-cache-miss: true @@ -160,11 +161,12 @@ jobs: run: >- echo "datetime="$(date "+%Y%m%d_%H%M") >> $GITHUB_OUTPUT - name: Upload benchmark artifact - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.4.0 with: name: benchmark-${{ runner.os }}-${{ matrix.python-version }}_${{ steps.artifact-name-suffix.outputs.datetime }} + include-hidden-files: true path: .benchmarks/ tests-windows: @@ -175,17 +177,17 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13-dev"] steps: - name: Set temp directory run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV # Workaround to set correct temp directory on Windows # https://github.com/actions/virtual-environments/issues/712 - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -197,7 +199,7 @@ jobs: }}" >> $env:GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- @@ -225,13 +227,13 @@ jobs: fail-fast: false matrix: # We only run on the oldest supported version on Mac - python-version: [3.8] + python-version: [3.9] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -243,7 +245,7 @@ jobs: }}" >> $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- @@ -269,13 +271,13 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy-3.8", "pypy-3.9"] + python-version: ["pypy-3.9", "pypy-3.10"] steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.7 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -287,7 +289,7 @@ jobs: }}" >> $GITHUB_OUTPUT - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.2.0 with: path: venv key: >- diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 104267c522..ceee345834 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,6 +7,9 @@ repos: hooks: - id: trailing-whitespace exclude: tests(/\w*)*/functional/t/trailing_whitespaces.py|tests/pyreverse/data/.*.html|doc/data/messages/t/trailing-whitespace/bad.py + # - id: file-contents-sorter # commented out because it does not preserve comments order + # args: ["--ignore-case", "--unique"] + # files: "custom_dict.txt" - id: end-of-file-fixer exclude: | (?x)^( @@ -17,15 +20,14 @@ repos: doc/data/messages/m/missing-final-newline/bad/crlf.py )$ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.4" + rev: "v0.6.5" hooks: - id: ruff args: ["--fix"] - exclude: &fixtures tests(/\w*)*/functional/|tests/input|doc/data/messages|tests(/\w*)*data/ + exclude: doc/data/messages - id: ruff name: ruff-doc files: doc/data/messages - args: ["--config", "doc/data/ruff.toml"] - repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit rev: 0.1.2 hooks: @@ -39,11 +41,11 @@ repos: - id: isort exclude: doc/data/messages/ - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black args: [--safe, --quiet] - exclude: *fixtures + exclude: &fixtures tests(/\w*)*/functional/|tests/input|doc/data/messages|tests(/\w*)*data/ - id: black name: black-doc args: [--safe, --quiet] @@ -77,6 +79,9 @@ repos: entry: pylint language: system types: [python] + # Not that problematic to run in parallel see Pre-commit + # integration in the doc for details + # require_serial: true args: ["-rn", "-sn", "--rcfile=pylintrc", "--fail-on=I"] exclude: tests(/\w*)*/functional/|tests/input|tests(/\w*)*data/|doc/ # We define an additional manual step to allow running pylint with a spelling @@ -113,14 +118,14 @@ repos: files: ^(doc/whatsnew/fragments) exclude: doc/whatsnew/fragments/_.*.rst - repo: https://github.com/rstcheck/rstcheck - rev: "v6.2.0" + rev: "v6.2.4" hooks: - id: rstcheck args: ["--report-level=warning"] files: ^(doc/(.*/)*.*\.rst) - additional_dependencies: [Sphinx==5.0.1] + additional_dependencies: [Sphinx==7.4.3] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.11.2 hooks: - id: mypy name: mypy @@ -135,11 +140,11 @@ repos: "platformdirs==2.2.0", "py==1.11", "tomlkit>=0.10.1", - "types-pkg_resources==0.1.3", + "types-setuptools==75.6.0.20241126", ] exclude: tests(/\w*)*/functional/|tests/input|tests(/.*)+/conftest.py|doc/data/messages|tests(/\w*)*data/ - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.3.3 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] @@ -166,8 +171,15 @@ repos: setup.cfg )$ - repo: https://github.com/PyCQA/bandit - rev: 1.7.8 + rev: 1.7.9 hooks: - id: bandit args: ["-r", "-lll"] exclude: *fixtures + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: ["--toml=pyproject.toml"] + additional_dependencies: + - tomli diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 24959e454d..4e238428ed 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -158,6 +158,7 @@ contributors: * Added new useless-return checker, * Added new try-except-raise checker - theirix +- correctmost <134317971+correctmost@users.noreply.github.com> - Téo Bouvard - Stavros Ntentos <133706+stdedos@users.noreply.github.com> - Nicolas Boulenguez @@ -210,6 +211,7 @@ contributors: - wtracy - jessebrennan - chohner +- aatle <168398276+aatle@users.noreply.github.com> - Tiago Honorato <61059243+tiagohonorato@users.noreply.github.com> - Steven M. Vascellaro - Robin Tweedie <70587124+robin-wayve@users.noreply.github.com> @@ -264,6 +266,7 @@ contributors: - Samuel FORESTIER - Rémi Cardona - Ryan Ozawa +- Roger Sheu <78449574+rogersheu@users.noreply.github.com> - Raphael Gaschignard - Ram Rachum (cool-RR) - Radostin Stoyanov @@ -287,9 +290,11 @@ contributors: - Justin Li - John Kirkham - Jens H. Nielsen +- Jake Lishman - Ioana Tagirta : fix bad thread instantiation check - Ikraduya Edian : Added new checks 'consider-using-generator' and 'use-a-generator'. - Hugues Bruant +- Hashem Nasarat - Harut - Grygorii Iermolenko - Grizzly Nyo @@ -317,9 +322,11 @@ contributors: - Ben Green - Batuhan Taskaya - Alexander Kapshuna +- Akhil Kamat - Adam Parkin - 谭九鼎 <109224573@qq.com> - Łukasz Sznuk +- zasca - y2kbugger - vinnyrose - ttenhoeve-aa @@ -382,10 +389,12 @@ contributors: - Trevor Bekolay * Added --list-msgs-enabled command - Tomer Chachamu : simplifiable-if-expression +- Tomasz Michalski - Tomasz Magulski - Tom - Tim Hatch - Tim Gates +- Tianyu Chen <124018391+UTsweetyfish@users.noreply.github.com> - Théo Battrel - Thomas Benhamou - Theodore Ni <3806110+tjni@users.noreply.github.com> @@ -412,6 +421,7 @@ contributors: - Ryan McGuire - Ry4an Brase - Ruro +- Roshan Shetty - Roman Ivanov - Robert Schweizer - Reverb Chu @@ -438,6 +448,7 @@ contributors: - Oisín Moran - Obscuron - Noam Yorav-Raphael +- Noah-Agnel <138210920+Noah-Agnel@users.noreply.github.com> - Nir Soffer - Niko Wenselowski - Nikita Sobolev @@ -515,7 +526,6 @@ contributors: - James Broadhead - Jakub Kulík - Jakob Normark -- Jake Lishman - Jacques Kvam - Jace Browning : updated default report format with clickable paths - JT Olds @@ -523,7 +533,6 @@ contributors: - Hayden Richards <62866982+SupImDos@users.noreply.github.com> * Fixed "no-self-use" for async methods * Fixed "docparams" extension for async functions and methods -- Hashem Nasarat - Harshil <37377066+harshil21@users.noreply.github.com> - Harry - Grégoire <96051754+gregoire-mullvad@users.noreply.github.com> @@ -537,6 +546,7 @@ contributors: - Eric Froemling - Emmanuel Chaudron - Elizabeth Bott <52465744+elizabethbott@users.noreply.github.com> +- Ekin Dursun - Eisuke Kawashima - Edward K. Ream - Edgemaster @@ -547,6 +557,7 @@ contributors: - Dmytro Kyrychuk - Dionisio E Alonso - DetachHead <57028336+DetachHead@users.noreply.github.com> +- Dennis Keck <26092524+fellhorn@users.noreply.github.com> - Denis Laxalde - David Lawson - David Cain @@ -582,12 +593,14 @@ contributors: - Benjamin Graham - Benedikt Morbach - Ben Greiner +- Barak Shoshany - Banjamin Freeman - Avram Lubkin - Athos Ribeiro : Fixed dict-keys-not-iterating false positive for inverse containment checks - Arun Persaud - Arthur Lutz - Antonio Ossa +- Antonio Gámiz Delgado <73933988+antoniogamizbadger@users.noreply.github.com> - Anthony VEREZ - Anthony Tan - Anthony Foglia (Google): Added simple string slots check. @@ -617,6 +630,7 @@ contributors: - Adrian Chirieac - Aditya Gupta (adityagupta1089) * Added ignore_signatures to duplicate checker +- Adam Tuft <73994535+adamtuft@users.noreply.github.com> - Adam Dangoor - 243f6a88 85a308d3 <33170174+243f6a8885a308d313198a2e037@users.noreply.github.com> diff --git a/README.rst b/README.rst index 2910eba680..0595d895d8 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ :target: https://codecov.io/gh/pylint-dev/pylint .. image:: https://img.shields.io/pypi/v/pylint.svg - :alt: Pypi Package version + :alt: PyPI Package version :target: https://pypi.python.org/pypi/pylint .. image:: https://readthedocs.org/projects/pylint/badge/?version=latest @@ -45,7 +45,7 @@ What is Pylint? --------------- Pylint is a `static code analyser`_ for Python 2 or 3. The latest version supports Python -3.8.0 and above. +3.9.0 and above. .. _`static code analyser`: https://en.wikipedia.org/wiki/Static_code_analysis @@ -81,7 +81,7 @@ It can also be integrated in most editors or IDEs. More information can be found What differentiates Pylint? --------------------------- -Pylint is not trusting your typing and is inferring the actual value of nodes (for a +Pylint is not trusting your typing and is inferring the actual values of nodes (for a start because there was no typing when pylint started off) using its internal code representation (astroid). If your code is ``import logging as argparse``, Pylint can check and know that ``argparse.error(...)`` is in fact a logging call and not an @@ -123,7 +123,7 @@ ecosystem of existing plugins for popular frameworks and third-party libraries. .. _`plugins`: https://pylint.readthedocs.io/en/latest/development_guide/how_tos/plugins.html#plugins .. _`pylint-pydantic`: https://pypi.org/project/pylint-pydantic .. _`pylint-django`: https://github.com/pylint-dev/pylint-django -.. _`pylint-sonarjson`: https://github.com/omegacen/pylint-sonarjson +.. _`pylint-sonarjson`: https://github.com/cnescatlab/pylint-sonarjson-catlab Advised linters alongside pylint -------------------------------- @@ -135,7 +135,7 @@ mypy_, pyright_ / pylance or pyre_ (typing checks), bandit_ (security oriented c isort_ (auto-formatting), autoflake_ (automated removal of unused imports or variables), pyupgrade_ (automated upgrade to newer python syntax) and pydocstringformatter_ (automated pep257). -.. _ruff: https://github.com/charliermarsh/ruff +.. _ruff: https://github.com/astral-sh/ruff .. _flake8: https://github.com/PyCQA/flake8 .. _bandit: https://github.com/PyCQA/bandit .. _mypy: https://github.com/python/mypy diff --git a/.pyenchant_pylint_custom_dict.txt b/custom_dict.txt similarity index 98% rename from .pyenchant_pylint_custom_dict.txt rename to custom_dict.txt index 78d861aea3..576ef703e0 100644 --- a/.pyenchant_pylint_custom_dict.txt +++ b/custom_dict.txt @@ -13,10 +13,11 @@ argumentparser argumentsparser argv ascii +asend assignattr assignname -ast AST +ast astroid async asynccontextmanager @@ -67,8 +68,8 @@ contextlib contextmanager contravariance contravariant -cpython CPython +cpython csv CVE cwd @@ -126,8 +127,8 @@ formfeed fromlineno fullname func -functiøn functiondef +functiøn functools genexpr getattr @@ -160,10 +161,11 @@ isfile isinstance isort iter -itered iterable iterables +itered iteritems +iTerm jn jpg json @@ -206,10 +208,10 @@ monkeypatch mro # Used so much that we need the abbreviation msg +msg-template msgid msgids msgs -msg-template mult multiline multiset @@ -249,8 +251,9 @@ paren parens passthru pathlib -positionals +patternerror png +positionals pragma pragma's pragmas @@ -263,9 +266,9 @@ pyenchant pyfile pyi pylint +pylint's pylintdict pylintrc -pylint's pyproject pypy pyreverse @@ -274,6 +277,7 @@ qname rawcheckers rc rcfile +re-usable readlines recognise recurse @@ -344,9 +348,9 @@ tomlkit toplevel towncrier tp +truthey truthness truthy -truthey tryexcept txt typecheck @@ -361,8 +365,8 @@ unary unflattens unhandled unicode -Uninferable uninferable +Uninferable unittest unraisablehook untriggered diff --git a/doc/data/messages/a/anomalous-backslash-in-string/bad.py b/doc/data/messages/a/anomalous-backslash-in-string/bad.py index 08d8d1d6f4..32da7ddcc0 100644 --- a/doc/data/messages/a/anomalous-backslash-in-string/bad.py +++ b/doc/data/messages/a/anomalous-backslash-in-string/bad.py @@ -1 +1 @@ -string = "\z" # [anomalous-backslash-in-string] +string = "\z" # [syntax-error] diff --git a/doc/data/messages/a/anomalous-backslash-in-string/details.rst b/doc/data/messages/a/anomalous-backslash-in-string/details.rst index e716bc2d9c..7f73b513e9 100644 --- a/doc/data/messages/a/anomalous-backslash-in-string/details.rst +++ b/doc/data/messages/a/anomalous-backslash-in-string/details.rst @@ -1,2 +1,6 @@ ``\z`` is same as ``\\z`` because there's no escape sequence for ``z``. But it is not clear for the reader of the code. + +The only reason this is demonstrated to raise ``syntax-error`` is because +pylint's CI now runs on Python 3.12, where this truly raises a ``SyntaxError``. +We hope to address this discrepancy in the documentation in the future. 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 40275f0551..21d25eadf0 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%b" % b"0394") # [anomalous-unicode-escape-in-string] +print(b"\u%b" % b"0394") # [syntax-error] diff --git a/doc/data/messages/c/c-extension-no-member/details.rst b/doc/data/messages/c/c-extension-no-member/details.rst index c1e9b976ac..3f19677ff4 100644 --- a/doc/data/messages/c/c-extension-no-member/details.rst +++ b/doc/data/messages/c/c-extension-no-member/details.rst @@ -1 +1,4 @@ -You can help us make the doc better `by contributing `_ ! +``c-extension-no-member`` is an informational variant of ``no-member`` to encourage +allowing introspection of C extensions as described in the +`page `_ +for ``no-member``. diff --git a/doc/data/messages/d/declare-non-slot/bad.py b/doc/data/messages/d/declare-non-slot/bad.py new file mode 100644 index 0000000000..5e39d47953 --- /dev/null +++ b/doc/data/messages/d/declare-non-slot/bad.py @@ -0,0 +1,5 @@ +class Student: + __slots__ = ("name",) + + name: str + surname: str # [declare-non-slot] diff --git a/doc/data/messages/d/declare-non-slot/good.py b/doc/data/messages/d/declare-non-slot/good.py new file mode 100644 index 0000000000..1ca1de19c1 --- /dev/null +++ b/doc/data/messages/d/declare-non-slot/good.py @@ -0,0 +1,5 @@ +class Student: + __slots__ = ("name", "surname") + + name: str + surname: str diff --git a/doc/data/messages/i/invalid-name/details.rst b/doc/data/messages/i/invalid-name/details.rst index 14cfe6e592..7cbf756380 100644 --- a/doc/data/messages/i/invalid-name/details.rst +++ b/doc/data/messages/i/invalid-name/details.rst @@ -23,7 +23,7 @@ name is found in, and not the type of object assigned. +--------------------+---------------------------------------------------------------------------------------------------+ | ``class-attribute``| Attributes defined in class bodies. | +--------------------+---------------------------------------------------------------------------------------------------+ -| ``class-const`` | Enum constants and class variables annotated with ``ClassVar`` | +| ``class-const`` | Enum constants and class variables annotated with ``Final`` | +--------------------+---------------------------------------------------------------------------------------------------+ | ``inlinevar`` | Loop variables in list comprehensions and generator expressions. | +--------------------+---------------------------------------------------------------------------------------------------+ diff --git a/doc/data/messages/p/possibly-used-before-assignment/details.rst b/doc/data/messages/p/possibly-used-before-assignment/details.rst index 5f87081a5c..6ebce4a3e2 100644 --- a/doc/data/messages/p/possibly-used-before-assignment/details.rst +++ b/doc/data/messages/p/possibly-used-before-assignment/details.rst @@ -30,11 +30,25 @@ lets some error cases through, as pylint does not assess the intervening code: if guarded(): var = 1 + # what if code here affects the result of guarded()? + + if guarded(): + print(var) + +But this exception is limited to the repeating the exact same test. +This warns: + +.. sourcecode:: python + if guarded(): - print(var) # emits possibly-used-before-assignment + var = 1 + + if guarded() or other_condition: + print(var) # [possibly-used-before-assignment] -you may be concerned that ``possibly-used-before-assignment`` is not totally useful -in this instance. However, consider that pylint, as a static analysis tool, does -not know if ``guarded()`` is deterministic or talks to -a database. (Likewise, for ``guarded`` instead of ``guarded()``, any other -part of your program may have changed its value in the meantime.) +If you find this surprising, consider that pylint, as a static analysis +tool, does not know if ``guarded()`` is deterministic or talks to +a database. For constants (e.g. ``guarded`` versus ``guarded()``), +this is less of an issue, so in this case, +``possibly-used-before-assignment`` acts more like a future-proofing style +preference than an error, per se. diff --git a/doc/data/messages/t/too-many-ancestors/good.py b/doc/data/messages/t/too-many-ancestors/good.py index 5853b25c92..ea6003c174 100644 --- a/doc/data/messages/t/too-many-ancestors/good.py +++ b/doc/data/messages/t/too-many-ancestors/good.py @@ -28,6 +28,6 @@ class Playtypus(Mammal): beaver_tailed = True can_swim = True has_beak = True - lays_egg = False + lays_egg = True protected_specie = True venomous = True diff --git a/doc/data/messages/t/too-many-positional-arguments/bad.py b/doc/data/messages/t/too-many-positional-arguments/bad.py new file mode 100644 index 0000000000..2a2c5b4cac --- /dev/null +++ b/doc/data/messages/t/too-many-positional-arguments/bad.py @@ -0,0 +1,5 @@ +class FiveArgumentMethods: + """The max positional arguments default is 5.""" + + def take_five_args(self, a, b, c, d, e): # [too-many-positional-arguments] + pass diff --git a/doc/data/messages/t/too-many-positional-arguments/details.rst b/doc/data/messages/t/too-many-positional-arguments/details.rst new file mode 100644 index 0000000000..bed5bfb806 --- /dev/null +++ b/doc/data/messages/t/too-many-positional-arguments/details.rst @@ -0,0 +1,10 @@ +Positional arguments work well for cases where the the use cases are +self-evident, such as unittest's ``assertEqual(first, second, msg=None)``. +Comprehensibility suffers beyond a handful of arguments, though, so for +functions that take more inputs, require that additional arguments be +passed by *keyword only* by preceding them with ``*``: + +.. code-block:: python + + def make_noise(self, volume, *, color=noise.PINK, debug=True): + ... diff --git a/doc/data/messages/t/too-many-positional-arguments/good.py b/doc/data/messages/t/too-many-positional-arguments/good.py new file mode 100644 index 0000000000..ab4a9f2735 --- /dev/null +++ b/doc/data/messages/t/too-many-positional-arguments/good.py @@ -0,0 +1,5 @@ +class FiveArgumentMethods: + """The max positional arguments default is 5.""" + + def take_five_args(self, a, b, c, d, *, e=False): + pass diff --git a/doc/data/messages/t/too-many-positional-arguments/pylintrc b/doc/data/messages/t/too-many-positional-arguments/pylintrc new file mode 100644 index 0000000000..271f58965b --- /dev/null +++ b/doc/data/messages/t/too-many-positional-arguments/pylintrc @@ -0,0 +1,2 @@ +[MESSAGES CONTROL] +disable=too-many-arguments diff --git a/doc/data/messages/t/too-many-positional/related.rst b/doc/data/messages/t/too-many-positional-arguments/related.rst similarity index 100% rename from doc/data/messages/t/too-many-positional/related.rst rename to doc/data/messages/t/too-many-positional-arguments/related.rst diff --git a/doc/data/messages/t/too-many-positional/details.rst b/doc/data/messages/t/too-many-positional/details.rst deleted file mode 100644 index b9acd905f0..0000000000 --- a/doc/data/messages/t/too-many-positional/details.rst +++ /dev/null @@ -1 +0,0 @@ -Reserved message name, not yet implemented. diff --git a/doc/data/messages/t/typevar-name-incorrect-variance/details.rst b/doc/data/messages/t/typevar-name-incorrect-variance/details.rst new file mode 100644 index 0000000000..ae7e3db648 --- /dev/null +++ b/doc/data/messages/t/typevar-name-incorrect-variance/details.rst @@ -0,0 +1 @@ +When naming type vars, only use a ``_co`` suffix when indicating covariance or ``_contra`` when indicating contravariance. diff --git a/doc/data/messages/u/unnecessary-default-type-args/bad.py b/doc/data/messages/u/unnecessary-default-type-args/bad.py new file mode 100644 index 0000000000..e3d97799a5 --- /dev/null +++ b/doc/data/messages/u/unnecessary-default-type-args/bad.py @@ -0,0 +1,4 @@ +from collections.abc import AsyncGenerator, Generator + +a1: AsyncGenerator[int, None] # [unnecessary-default-type-args] +b1: Generator[int, None, None] # [unnecessary-default-type-args] diff --git a/doc/data/messages/u/unnecessary-default-type-args/details.rst b/doc/data/messages/u/unnecessary-default-type-args/details.rst new file mode 100644 index 0000000000..754f532caa --- /dev/null +++ b/doc/data/messages/u/unnecessary-default-type-args/details.rst @@ -0,0 +1,6 @@ +At the moment, this check only works for ``Generator`` and ``AsyncGenerator``. + +Starting with Python 3.13, the ``SendType`` and ``ReturnType`` default to ``None``. +As such it's no longer necessary to specify them. The ``collections.abc`` variants +don't validate the number of type arguments. Therefore the defaults for these +can be used in earlier versions as well. diff --git a/doc/data/messages/u/unnecessary-default-type-args/good.py b/doc/data/messages/u/unnecessary-default-type-args/good.py new file mode 100644 index 0000000000..e77c0ee429 --- /dev/null +++ b/doc/data/messages/u/unnecessary-default-type-args/good.py @@ -0,0 +1,4 @@ +from collections.abc import AsyncGenerator, Generator + +a1: AsyncGenerator[int] +b1: Generator[int] diff --git a/doc/data/messages/u/unnecessary-default-type-args/pylintrc b/doc/data/messages/u/unnecessary-default-type-args/pylintrc new file mode 100644 index 0000000000..825e13ec0b --- /dev/null +++ b/doc/data/messages/u/unnecessary-default-type-args/pylintrc @@ -0,0 +1,2 @@ +[main] +load-plugins=pylint.extensions.typing diff --git a/doc/data/messages/u/unnecessary-default-type-args/related.rst b/doc/data/messages/u/unnecessary-default-type-args/related.rst new file mode 100644 index 0000000000..1f988ae98b --- /dev/null +++ b/doc/data/messages/u/unnecessary-default-type-args/related.rst @@ -0,0 +1,2 @@ +- `Python documentation for AsyncGenerator `_ +- `Python documentation for Generator `_ diff --git a/doc/data/messages/u/unrecognize-option/details.rst b/doc/data/messages/u/unrecognize-option/details.rst deleted file mode 100644 index efa9a206cd..0000000000 --- a/doc/data/messages/u/unrecognize-option/details.rst +++ /dev/null @@ -1,3 +0,0 @@ -``Pylint`` warns about options it doesn't recognize both in configuration files -and on the command-line. For example, this message would be raised when invoking -pylint with ``pylint --unknown-option=yes test.py``. diff --git a/doc/data/messages/u/unrecognized-option/details.rst b/doc/data/messages/u/unrecognized-option/details.rst index 6644085778..5cd4d220f1 100644 --- a/doc/data/messages/u/unrecognized-option/details.rst +++ b/doc/data/messages/u/unrecognized-option/details.rst @@ -2,7 +2,9 @@ One of your options is not recognized. There's nothing to change in your code, but your pylint configuration or the way you launch pylint needs to be modified. -For example you might be launching pylint with the following ``toml`` configuration:: +For example, this message would be raised when invoking pylint with +``pylint --unknown-option=yes test.py``. Or you might be launching +pylint with the following ``toml`` configuration:: [tool.pylint] jars = "10" diff --git a/doc/data/messages/u/using-assignment-expression-in-unsupported-version/bad.py b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/bad.py new file mode 100644 index 0000000000..ca4e4a64ae --- /dev/null +++ b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/bad.py @@ -0,0 +1,5 @@ +import random + +# +1: [using-assignment-expression-in-unsupported-version] +if zero_or_one := random.randint(0, 1): + assert zero_or_one == 1 diff --git a/doc/data/messages/u/using-assignment-expression-in-unsupported-version/details.rst b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/details.rst new file mode 100644 index 0000000000..b8ea375aac --- /dev/null +++ b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/details.rst @@ -0,0 +1 @@ +The assignment expression (walrus) operator (`:=`) was introduced in Python 3.8; to use it, please use a more recent version of Python. diff --git a/doc/data/messages/u/using-assignment-expression-in-unsupported-version/good.py b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/good.py new file mode 100644 index 0000000000..a31a74a664 --- /dev/null +++ b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/good.py @@ -0,0 +1,5 @@ +import random + +zero_or_one = random.randint(0, 1) +if zero_or_one: + assert zero_or_one == 1 diff --git a/doc/data/messages/u/using-assignment-expression-in-unsupported-version/pylintrc b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/pylintrc new file mode 100644 index 0000000000..77eb3be645 --- /dev/null +++ b/doc/data/messages/u/using-assignment-expression-in-unsupported-version/pylintrc @@ -0,0 +1,2 @@ +[main] +py-version=3.7 diff --git a/doc/data/messages/u/using-exception-groups-in-unsupported-version/bad.py b/doc/data/messages/u/using-exception-groups-in-unsupported-version/bad.py new file mode 100644 index 0000000000..c225e6e6a8 --- /dev/null +++ b/doc/data/messages/u/using-exception-groups-in-unsupported-version/bad.py @@ -0,0 +1,12 @@ +def f(): + excs = [OSError("error 1"), SystemError("error 2")] + # +1: [using-exception-groups-in-unsupported-version] + raise ExceptionGroup("there were problems", excs) + + +try: # [using-exception-groups-in-unsupported-version] + f() +except* OSError as e: + print("There were OSErrors") +except* SystemError as e: + print("There were SystemErrors") diff --git a/doc/data/messages/u/using-exception-groups-in-unsupported-version/details.rst b/doc/data/messages/u/using-exception-groups-in-unsupported-version/details.rst new file mode 100644 index 0000000000..8d05a037a1 --- /dev/null +++ b/doc/data/messages/u/using-exception-groups-in-unsupported-version/details.rst @@ -0,0 +1 @@ +Exception groups were introduced in Python 3.11; to use it, please use a more recent version of Python. diff --git a/doc/data/messages/u/using-exception-groups-in-unsupported-version/good.py b/doc/data/messages/u/using-exception-groups-in-unsupported-version/good.py new file mode 100644 index 0000000000..e7ac7a5b7a --- /dev/null +++ b/doc/data/messages/u/using-exception-groups-in-unsupported-version/good.py @@ -0,0 +1,10 @@ +def f(): + raise OSError("error 1") + + +try: + f() +except OSError as e: + print("There were OSErrors") +except SystemError as e: + print("There were SystemErrors") diff --git a/doc/data/messages/u/using-exception-groups-in-unsupported-version/pylintrc b/doc/data/messages/u/using-exception-groups-in-unsupported-version/pylintrc new file mode 100644 index 0000000000..d36622d880 --- /dev/null +++ b/doc/data/messages/u/using-exception-groups-in-unsupported-version/pylintrc @@ -0,0 +1,2 @@ +[main] +py-version=3.10 diff --git a/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/bad.py b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/bad.py new file mode 100644 index 0000000000..b47bddd33e --- /dev/null +++ b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/bad.py @@ -0,0 +1 @@ +type Vector = list[float] # [using-generic-type-syntax-in-unsupported-version] diff --git a/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/details.rst b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/details.rst new file mode 100644 index 0000000000..731616e853 --- /dev/null +++ b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/details.rst @@ -0,0 +1 @@ +Generic type syntax was introduced in Python 3.12; to use it, please use a more recent version of Python. diff --git a/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/good.py b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/good.py new file mode 100644 index 0000000000..3f80b01c52 --- /dev/null +++ b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/good.py @@ -0,0 +1,3 @@ +from typing import TypeAlias + +Vector: TypeAlias = list[float] diff --git a/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/pylintrc b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/pylintrc new file mode 100644 index 0000000000..8e00cbfea2 --- /dev/null +++ b/doc/data/messages/u/using-generic-type-syntax-in-unsupported-version/pylintrc @@ -0,0 +1,2 @@ +[main] +py-version=3.11 diff --git a/doc/data/messages/u/using-positional-only-args-in-unsupported-version/bad.py b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/bad.py new file mode 100644 index 0000000000..3923db1682 --- /dev/null +++ b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/bad.py @@ -0,0 +1,2 @@ +def add(x, y, /): # [using-positional-only-args-in-unsupported-version] + return x + y diff --git a/doc/data/messages/u/using-positional-only-args-in-unsupported-version/details.rst b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/details.rst new file mode 100644 index 0000000000..b00ed79889 --- /dev/null +++ b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/details.rst @@ -0,0 +1 @@ +Positional-only arguments were introduced in Python 3.8; to use them, please use a more recent version of Python. diff --git a/doc/data/messages/u/using-positional-only-args-in-unsupported-version/good.py b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/good.py new file mode 100644 index 0000000000..bfc542d6aa --- /dev/null +++ b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/good.py @@ -0,0 +1,3 @@ +# pylint: disable=missing-function-docstring, missing-module-docstring +def add(x, y): + return x + y diff --git a/doc/data/messages/u/using-positional-only-args-in-unsupported-version/pylintrc b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/pylintrc new file mode 100644 index 0000000000..77eb3be645 --- /dev/null +++ b/doc/data/messages/u/using-positional-only-args-in-unsupported-version/pylintrc @@ -0,0 +1,2 @@ +[main] +py-version=3.7 diff --git a/doc/data/ruff.toml b/doc/data/ruff.toml index 773f7a313d..2ad67f6753 100644 --- a/doc/data/ruff.toml +++ b/doc/data/ruff.toml @@ -1,13 +1,20 @@ -ignore = [] # Reading ease is drastically reduced on read the doc after 103 chars # (Because of horizontal scrolling) line-length = 103 + +extend-exclude = [ + "messages/d/duplicate-argument-name/bad.py", + "messages/s/syntax-error/bad.py", +] + +[lint] +ignore = [] select = ["E501", "I"] -[per-file-ignores] -"doc/data/messages/r/reimported/bad.py" = ["I"] -"doc/data/messages/w/wrong-import-order/bad.py" = ["I"] -"doc/data/messages/u/ungrouped-imports/bad.py" = ["I"] -"doc/data/messages/m/misplaced-future/bad.py" = ["I"] -"doc/data/messages/m/multiple-imports/bad.py" = ["I"] +[lint.per-file-ignores] +"messages/m/misplaced-future/bad.py" = ["I"] +"messages/m/multiple-imports/bad.py" = ["I"] +"messages/r/reimported/bad.py" = ["I"] +"messages/u/ungrouped-imports/bad.py" = ["I"] +"messages/w/wrong-import-order/bad.py" = ["I"] diff --git a/doc/development_guide/contributor_guide/index.rst b/doc/development_guide/contributor_guide/index.rst index 3576c68ff5..0226f48239 100644 --- a/doc/development_guide/contributor_guide/index.rst +++ b/doc/development_guide/contributor_guide/index.rst @@ -12,5 +12,6 @@ The contributor guide will help you if you want to contribute to pylint itself. contribute tests/index profiling + oss_fuzz release governance diff --git a/doc/development_guide/contributor_guide/oss_fuzz.rst b/doc/development_guide/contributor_guide/oss_fuzz.rst new file mode 100644 index 0000000000..1d8e3b1dda --- /dev/null +++ b/doc/development_guide/contributor_guide/oss_fuzz.rst @@ -0,0 +1,165 @@ +====================== + OSS-Fuzz integration +====================== + +Platform overview +----------------- + +`OSS-Fuzz `_ is Google's free fuzzing platform for open source +software. It runs astroid's fuzz targets to help detect reliability issues that could affect astroid +and Pylint. + +Google provides public `build logs `_ +and `fuzzing stats `_, but most +of the details about bug reports and fuzzed testcases require approved access. + +Gaining access +^^^^^^^^^^^^^^ + +The configuration files for the OSS-Fuzz integration can be found in the +`OSS-Fuzz repository `_. +The ``project.yaml`` file controls who has access to bug reports and testcases. Ping the +maintainers if you'd like to be added to the list (note: a Google account is required for +access). + +Fuzzing progress +---------------- + +Once you have access to OSS-Fuzz, you can log in to https://oss-fuzz.com/ with your Google account +to see a dashboard of astroid's fuzzing progress. + +Testcases +^^^^^^^^^ + +The dashboard contains a link to a `testcases page `_ +that lists all testcases that currently trigger a bug in astroid. + +Every testcase has a dedicated page with links to view and download a minimized testcase for +reproducing the failure. Each testcase page also contains a stacktrace for the failure and stats +about how often the failure is encountered while fuzzing. + +Reproducing a failure +""""""""""""""""""""" + +You can download a minimized testcase and run it locally to debug a failure on your machine. +For example, to reproduce a failure with the ``fuzz_parse`` fuzz target, you can run the following +commands: + +.. code:: bash + + # Note: Atheris doesn't support Python 3.12+ yet: + # https://github.com/google/atheris/issues/82 + mkdir fuzzing-repro + cd fuzzing-repro + + pyenv install --skip-existing 3.11 + pyenv shell 3.11 + + python -m venv .venv-fuzzing-repro + source .venv-fuzzing-repro/bin/activate + + git clone https://github.com/pylint-dev/astroid.git + cd astroid + + pip install atheris + pip install --editable . + + # Save the minimized testcase as `minimized.py` in the astroid directory + + cat << EOF > ./run_fuzz_parse.py + + import astroid + import atheris + + with open('minimized.py', 'rb') as f: + fdp = atheris.FuzzedDataProvider(f.read()) + + code = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 4096)) + astroid.builder.parse(code) + EOF + + python ./run_fuzz_parse.py + + +If the failure does not reproduce locally, you can try reproducing the issue in an OSS-Fuzz +container: + +.. code:: bash + + git clone https://github.com/google/oss-fuzz.git + cd oss-fuzz + + python infra/helper.py build_image astroid + python infra/helper.py build_fuzzers astroid + python infra/helper.py reproduce astroid fuzz_parse minimized.py + +Some failures may only be reproducible in an OSS-Fuzz container because of differences in Python +versions between the OSS-Fuzz platform and your local environment. + +Code coverage +^^^^^^^^^^^^^ + +The dashboard also links to code coverage data for individual fuzz targets and combined code +coverage data for all targets (click on the "TOTAL COVERAGE" link for the combined data). + +The combined coverage data is helpful for identifying coverage gaps, insufficient corpus data, and +potential candidates for future fuzz targets. + +Bug reports +^^^^^^^^^^^ + +Bug reports for new failures are automatically filed in the OSS-Fuzz bug tracker with an +`astroid label `_. +Make sure you are logged in to view all existing issues. + +Build maintenance +----------------- + +Google runs compiled fuzz targets on Google Compute Engine VMs. This architecture requires each +project to provide a ``Dockerfile`` and ``build.sh`` script to download code, configure +dependencies, compile fuzz targets, and package any corpus files. + +astroid's build files and fuzz-target code can be found in the +`OSS-Fuzz repo `_. + +If dependencies change or if new fuzz targets are added, then you may need to modify the build files +and build a new Docker image for OSS-Fuzz. + +Building an image +^^^^^^^^^^^^^^^^^ + +Run the following commands to build astroid's OSS-Fuzz image and fuzz targets: + +.. code:: bash + + git clone https://github.com/google/oss-fuzz.git + cd oss-fuzz + + python infra/helper.py build_image astroid + python infra/helper.py build_fuzzers astroid + +Any changes you make to the build files must be submitted as pull requests to the OSS-Fuzz repo. + +Debugging build failures +"""""""""""""""""""""""" + +You can debug build failures during the ``build_fuzzers`` stage by creating a container and manually +running the ``compile`` command: + +.. code:: bash + + # Create a container for building fuzz targets + python infra/helper.py shell astroid + + # Run this command inside the container to build the fuzz targets + compile + +The ``build.sh`` script will be located at ``/src/build.sh`` inside the container. + +Quick links +----------- + +- `OSS-Fuzz dashboard `_ +- `OSS-Fuzz configuration files, build scripts, and fuzz targets for astroid `_ +- `All open OSS-Fuzz bugs for astroid `_ +- `Google's OSS-Fuzz documentation `_ diff --git a/doc/development_guide/contributor_guide/profiling.rst b/doc/development_guide/contributor_guide/profiling.rst index bb49038fff..9472247eb9 100644 --- a/doc/development_guide/contributor_guide/profiling.rst +++ b/doc/development_guide/contributor_guide/profiling.rst @@ -90,7 +90,7 @@ and thus is displayed as being 0. Often you are more interested in the cumulative time (per call). This refers to the time spent within the function and any of the functions it called or the functions they called (etc.). In our example, the ``visit_importfrom`` -method and all of its child-functions took a little over 8 seconds to exectute, with an execution time of +method and all of its child-functions took a little over 8 seconds to execute, with an execution time of 0.013 ms per call. You can also search the ``profiler_stats`` for an individual function you want to check. For example diff --git a/doc/development_guide/contributor_guide/release.rst b/doc/development_guide/contributor_guide/release.rst index 65d52771a7..61944b333e 100644 --- a/doc/development_guide/contributor_guide/release.rst +++ b/doc/development_guide/contributor_guide/release.rst @@ -55,7 +55,7 @@ For example: scratch. - Delete the ``maintenance/X.Y-1.x`` branch. (For example: ``maintenance/2.3.x``) -- Select all the issues labelled ``backport maintenance/X.Y-1.x`` and +- Select all the *closed* issues labelled ``backport maintenance/X.Y-1.x`` and label them ``backported``, then rename the ``backport maintenance/X.Y-1.x`` label to ``backport maintenance/X.Y.x`` (for example rename diff --git a/doc/development_guide/contributor_guide/tests/launching_test.rst b/doc/development_guide/contributor_guide/tests/launching_test.rst index 78f42e144a..48c3a0649b 100644 --- a/doc/development_guide/contributor_guide/tests/launching_test.rst +++ b/doc/development_guide/contributor_guide/tests/launching_test.rst @@ -30,7 +30,7 @@ tox You can also *optionally* install tox_ and run our tests using the tox_ package, as in:: python -m tox - python -m tox -epy38 # for Python 3.8 suite only + python -m tox -epy312 # for Python 3.12 suite only python -m tox -epylint # for running Pylint over Pylint's codebase python -m tox -eformatting # for running formatting checks over Pylint's codebase diff --git a/doc/development_guide/contributor_guide/tests/writing_test.rst b/doc/development_guide/contributor_guide/tests/writing_test.rst index 9ce9ca1f0d..481bd27cef 100644 --- a/doc/development_guide/contributor_guide/tests/writing_test.rst +++ b/doc/development_guide/contributor_guide/tests/writing_test.rst @@ -21,7 +21,7 @@ Unittest tests Most other tests reside in the '/pylint/test' directory. These unittests can be used to test almost all functionality within Pylint. A good step before writing any new unittests is to look -at some tests that test a similar funcitionality. This can often help write new tests. +at some tests that test a similar functionality. This can often help write new tests. If your new test requires any additional files you can put those in the ``/pylint/test/regrtest_data`` directory. This is the directory we use to store any data needed for diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py index 7fc694bcc2..6da0506443 100644 --- a/doc/exts/pylint_messages.py +++ b/doc/exts/pylint_messages.py @@ -12,7 +12,7 @@ from inspect import getmodule from itertools import chain, groupby from pathlib import Path -from typing import DefaultDict, Dict, List, NamedTuple, Tuple +from typing import NamedTuple from sphinx.application import Sphinx @@ -34,6 +34,27 @@ MSG_TYPES_DOC = {k: v if v != "info" else "information" for k, v in MSG_TYPES.items()} +MESSAGES_WITHOUT_EXAMPLES = { + "astroid-error", # internal + "bad-configuration-section", # configuration + "bad-plugin-value", # internal + "c-extension-no-member", # not easy to implement in the current doc framework + "config-parse-error", # configuration + "fatal", # internal + "import-self", # not easy to implement in the current doc framework + "invalid-character-nul", # not easy to implement in the current doc framework + "invalid-characters-in-docstring", # internal in py-enchant + "invalid-unicode-codec", # placeholder (not implemented yet) + "method-check-failed", # internal + "parse-error", # internal + "raw-checker-failed", # internal + "unrecognized-option", # configuration +} +MESSAGES_WITHOUT_BAD_EXAMPLES = { + "invalid-character-carriage-return", # can't be raised in normal pylint use + "return-arg-in-generator", # can't be raised in modern python +} + class MessageData(NamedTuple): checker: str @@ -52,8 +73,8 @@ class ExampleType(str, Enum): BAD = "bad" -MessagesDict = Dict[str, List[MessageData]] -OldMessagesDict = Dict[str, DefaultDict[Tuple[str, str], List[Tuple[str, str]]]] +MessagesDict = dict[str, list[MessageData]] +OldMessagesDict = dict[str, defaultdict[tuple[str, str], list[tuple[str, str]]]] """DefaultDict is indexed by tuples of (old name symbol, old name id) and values are tuples of (new name symbol, new name category). """ @@ -99,6 +120,11 @@ def _get_pylintrc_code(data_path: Path) -> str: def _get_demo_code_for(data_path: Path, example_type: ExampleType) -> str: """Get code examples while handling multi-file code templates.""" + if data_path.name in MESSAGES_WITHOUT_EXAMPLES or ( + data_path.name in MESSAGES_WITHOUT_BAD_EXAMPLES + and example_type is ExampleType.BAD + ): + return "" single_file_path = data_path / f"{example_type.value}.py" multiple_code_path = data_path / f"{example_type.value}" @@ -130,8 +156,9 @@ def _get_demo_code_for(data_path: Path, example_type: ExampleType) -> str: """ ) return _get_titled_rst(title=title, text="\n".join(files)) - - return "" + raise AssertionError( + f"Please add a {example_type.value} code example for {data_path}" + ) def _check_placeholders( @@ -187,9 +214,7 @@ def _get_ini_as_rst(code_path: Path) -> str: """ -def _get_all_messages( - linter: PyLinter, -) -> tuple[MessagesDict, OldMessagesDict]: +def _get_all_messages(linter: PyLinter) -> tuple[MessagesDict, OldMessagesDict]: """Get all messages registered to a linter and return a dictionary indexed by message type. @@ -241,8 +266,8 @@ def _get_all_messages( if message.old_names: for old_name in message.old_names: category = MSG_TYPES_DOC[old_name[0][0]] - # We check if the message is already in old_messages so - # we don't duplicate shared messages. + # We check if the message is already in old_messages, so we don't + # duplicate shared messages. if (message.symbol, msg_type) not in old_messages[category][ (old_name[1], old_name[0]) ]: @@ -328,7 +353,7 @@ def _generate_single_message_body(message: MessageData) -> str: """ - body += "\n" + message.example_code + "\n" + body += f"\n{message.example_code}\n" if message.checker_module_name.startswith("pylint.extensions."): body += f""" diff --git a/doc/exts/pylint_options.py b/doc/exts/pylint_options.py index d06a9b87d0..7ec9baa26e 100644 --- a/doc/exts/pylint_options.py +++ b/doc/exts/pylint_options.py @@ -10,7 +10,7 @@ from collections import defaultdict from inspect import getmodule from pathlib import Path -from typing import Dict, List, NamedTuple +from typing import NamedTuple import tomlkit from sphinx.application import Sphinx @@ -30,7 +30,7 @@ class OptionsData(NamedTuple): extension: bool -OptionsDataDict = Dict[str, List[OptionsData]] +OptionsDataDict = dict[str, list[OptionsData]] PYLINT_BASE_PATH = Path(__file__).resolve().parent.parent.parent """Base path to the project folder.""" diff --git a/doc/faq.rst b/doc/faq.rst index 26f793988b..3706c25914 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -67,6 +67,28 @@ to not be included as default messages. You can see the plugin you need to explicitly :ref:`load in the technical reference `. +I want to use pylint on each keystroke in my IDE, how can I do that ? +--------------------------------------------------------------------- + +Don't do it: pylint's full suite of checks is not fast enough for that and never +will be. pylint is best suited for linting on save for small projects, or for a continuous +integration job or a git ``pre-push`` hook for big projects. The larger your repository +is, the slower pylint will be. + +If you want to make pylint faster for this type of use case, you can use the ``--errors-only`` +option, which will remove all the refactor, convention, and warning checks. You can also disable +checks with inherently high complexity that need to analyse the full code base like +``duplicate-code`` or ``cyclic-import`` (this list is not exhaustive). + +Why do I have non-deterministic results when I try to parallelize pylint ? +-------------------------------------------------------------------------- + +pylint should analyse all your code at once in order to best infer the +actual values that result from calls. If only some of the files are given, pylint might +miss a particular value's type and produce inferior inference for the subset. Parallelization +of pylint is not easy; we also discourage the use of the ``-j`` option if this matters to you. + + Which messages should I disable to avoid duplicates if I use other popular linters ? ------------------------------------------------------------------------------------ diff --git a/doc/requirements.txt b/doc/requirements.txt index f4373c3deb..2c91bf9062 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,5 +1,5 @@ -Sphinx==7.3.7 +Sphinx==7.4.7 sphinx-reredirects<1 -towncrier~=23.11 -furo==2024.5.6 +towncrier~=24.8 +furo==2024.8.6 -e . diff --git a/doc/symilar.rst b/doc/symilar.rst index fd7124e1db..b79a8357a3 100644 --- a/doc/symilar.rst +++ b/doc/symilar.rst @@ -3,8 +3,8 @@ Symilar ------- -The console script ``symilar`` finds copy pasted blocks in a set of files. It provides a command line interface to the ``Similar`` class, which includes the logic for -Pylint's ``duplicate-code`` message. +The console script ``symilar`` finds copy pasted block of text in a set of files. It provides a command line interface to check only the ``duplicate-code`` message. + It can be invoked with:: symilar [-d|--duplicates min_duplicated_lines] [-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] [--ignore-signatures] file1... diff --git a/doc/test_messages_documentation.py b/doc/test_messages_documentation.py index 96d98ed6c5..6898384f79 100644 --- a/doc/test_messages_documentation.py +++ b/doc/test_messages_documentation.py @@ -6,21 +6,9 @@ from __future__ import annotations -import sys - -if sys.version_info[:2] > (3, 9): - from collections import Counter -else: - from collections import Counter as _Counter - - class Counter(_Counter): - def total(self): - return len(tuple(self.elements())) - - +from collections import Counter from pathlib import Path -from typing import Counter as CounterType -from typing import TextIO, Tuple +from typing import TextIO import pytest @@ -31,7 +19,7 @@ def total(self): from pylint.testutils.constants import _EXPECTED_RE from pylint.testutils.reporter_for_tests import FunctionalTestReporter -MessageCounter = CounterType[Tuple[int, str]] +MessageCounter = Counter[tuple[int, str]] def get_functional_test_files_from_directory(input_dir: Path) -> list[tuple[str, Path]]: diff --git a/doc/user_guide/checkers/extensions.rst b/doc/user_guide/checkers/extensions.rst index 95462f9218..00f9963b72 100644 --- a/doc/user_guide/checkers/extensions.rst +++ b/doc/user_guide/checkers/extensions.rst @@ -688,6 +688,9 @@ Typing checker Messages :consider-alternative-union-syntax (R6003): *Consider using alternative Union syntax instead of '%s'%s* Emitted when 'typing.Union' or 'typing.Optional' is used instead of the alternative Union syntax 'int | None'. +:unnecessary-default-type-args (R6007): *Type `%s` has unnecessary default type args. Change it to `%s`.* + Emitted when types have default type args which can be omitted. Mainly used + for `typing.Generator` and `typing.AsyncGenerator`. :redundant-typehint-argument (R6006): *Type `%s` is used more than once in union type annotation. Remove redundant typehints.* Duplicated type arguments will be skipped by `mypy` tool, therefore should be removed to avoid confusion. diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index cb63930a01..4ade3c5311 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -65,7 +65,7 @@ Basic checker Messages methods and is instantiated. :star-needs-assignment-target (E0114): *Can use starred expression only in assignment target* Emitted when a star expression is not used in an assignment target. -:duplicate-argument-name (E0108): *Duplicate argument name %s in function definition* +:duplicate-argument-name (E0108): *Duplicate argument name %r in function definition* Duplicate argument names in function definitions are syntax errors. :return-in-init (E0101): *Explicit return in __init__* Used when the special class method __init__ has an explicit return value. @@ -282,6 +282,9 @@ Classes checker Messages Used when a method has an attribute different the "self" as first argument. This is considered as an error since this is a so common convention that you shouldn't break it! +:declare-non-slot (E0245): *No such name %r in __slots__* + Raised when a type annotation on a class is absent from the list of names in + __slots__, and __slots__ does not contain a __dict__ entry. :unexpected-special-method-signature (E0302): *The special method %r expects %s param(s), %d %s given* Emitted when a special method was defined with an invalid number of parameters. If it has too few or too many, it might not work at all. @@ -325,7 +328,7 @@ Classes checker Messages interface or in an overridden method. :protected-access (W0212): *Access to a protected member %s of a client class* Used when a protected member (i.e. class member with a name beginning with an - underscore) is access outside the class or a descendant of the class where + underscore) is accessed outside the class or a descendant of the class where it's defined. :attribute-defined-outside-init (W0201): *Attribute %r defined outside __init__* Used when an instance attribute is defined outside the __init__ method. @@ -434,10 +437,8 @@ Design checker Messages simpler (and so easier to use) class. :too-many-locals (R0914): *Too many local variables (%s/%s)* Used when a function or method has too many local variables. -:too-many-positional (R0917): *Too many positional arguments in a function call.* - Will be implemented in https://github.com/pylint- - dev/pylint/issues/9099,msgid/symbol pair reserved for compatibility with - ruff, see https://github.com/astral-sh/ruff/issues/8946. +:too-many-positional-arguments (R0917): *Too many positional arguments (%s/%s)* + Used when a function has too many positional arguments. :too-many-public-methods (R0904): *Too many public methods (%s/%s)* Used when class has too many public methods, try to reduce this to get a simpler (and so easier to use) class. @@ -473,9 +474,8 @@ Exceptions checker Messages :raising-bad-type (E0702): *Raising %s while only classes or instances are allowed* Used when something which is neither a class nor an instance is raised (i.e. a `TypeError` will be raised). -:raising-non-exception (E0710): *Raising a new style class which doesn't inherit from BaseException* - Used when a new style class which doesn't inherit from BaseException is - raised. +:raising-non-exception (E0710): *Raising a class which doesn't inherit from BaseException* + Used when a class which doesn't inherit from BaseException is raised. :misplaced-bare-raise (E0704): *The raise statement is not inside an except clause* Used when a bare raise is not used inside an except clause. This generates an error, since there are no active exceptions to be reraised. An exception to @@ -684,8 +684,7 @@ Method Args checker Messages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :positional-only-arguments-expected (E3102): *`%s()` got some positional-only arguments passed as keyword arguments: %s* Emitted when positional-only arguments have been passed as keyword arguments. - Remove the keywords for the affected arguments in the function call. This - message can't be emitted when using Python < 3.8. + Remove the keywords for the affected arguments in the function call. :missing-timeout (W3101): *Missing timeout argument for method '%s' can cause your program to hang indefinitely* Used when a method needs a 'timeout' parameter in order to avoid waiting for a long time. If no timeout is specified explicitly the default value is used. @@ -919,16 +918,16 @@ 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-to-string (C1804): *"%s" can be simplified to "%s", if it is striclty a string, as an empty string is falsey* - Empty string are considered false in a boolean context. Following this check - blindly in weakly typed code base can create hard to debug issues. If the - value can be something else that is falsey but not a string (for example - ``None``, an empty sequence, or ``0``) the code will not be equivalent. :use-implicit-booleaness-not-comparison (C1803): *"%s" can be simplified to "%s", if it is strictly a sequence, as an empty %s is falsey* Empty sequences are considered false in a boolean context. Following this check blindly in weakly typed code base can create hard to debug issues. If the value can be something else that is falsey but not a sequence (for example ``None``, an empty string, or ``0``) the code will not be equivalent. +:use-implicit-booleaness-not-comparison-to-string (C1804): *"%s" can be simplified to "%s", if it is strictly a string, as an empty string is falsey* + Empty string are considered false in a boolean context. Following this check + blindly in weakly typed code base can create hard to debug issues. If the + value can be something else that is falsey but not a string (for example + ``None``, an empty sequence, or ``0``) the code will not be equivalent. :use-implicit-booleaness-not-comparison-to-zero (C1805): *"%s" can be simplified to "%s", if it is strictly an int, as 0 is falsey* 0 is considered false in a boolean context. Following this check blindly in weakly typed code base can create hard to debug issues. If the value can be @@ -1349,9 +1348,21 @@ Verbatim name of the checker is ``unsupported_version``. Unsupported Version checker Messages ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:using-assignment-expression-in-unsupported-version (W2605): *Assignment expression is not supported by all versions included in the py-version setting* + Used when the py-version set by the user is lower than 3.8 and pylint + encounters an assignment expression (walrus) operator. +:using-exception-groups-in-unsupported-version (W2603): *Exception groups are not supported by all versions included in the py-version setting* + Used when the py-version set by the user is lower than 3.11 and pylint + encounters ``except*`` or `ExceptionGroup``. :using-f-string-in-unsupported-version (W2601): *F-strings are not supported by all versions included in the py-version setting* Used when the py-version set by the user is lower than 3.6 and pylint encounters an f-string. +:using-generic-type-syntax-in-unsupported-version (W2604): *Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting* + Used when the py-version set by the user is lower than 3.12 and pylint + encounters generic type syntax. +:using-positional-only-args-in-unsupported-version (W2606): *Positional-only arguments are not supported by all versions included in the py-version setting* + Used when the py-version set by the user is lower than 3.8 and pylint + encounters positional-only arguments. :using-final-decorator-in-unsupported-version (W2602): *typing.final is not supported by all versions included in the py-version setting* Used when the py-version set by the user is lower than 3.8 and pylint encounters a ``typing.final`` decorator. diff --git a/doc/user_guide/configuration/all-options.rst b/doc/user_guide/configuration/all-options.rst index eb7d72f2a3..568cdde85c 100644 --- a/doc/user_guide/configuration/all-options.rst +++ b/doc/user_guide/configuration/all-options.rst @@ -765,6 +765,13 @@ Standard Checkers **Default:** ``7`` +--max-positional-arguments +"""""""""""""""""""""""""" +*Maximum number of positional arguments for function / method.* + +**Default:** ``5`` + + --max-public-methods """""""""""""""""""" *Maximum number of public methods for a class (see R0904).* @@ -822,6 +829,8 @@ Standard Checkers max-parents = 7 + max-positional-arguments = 5 + max-public-methods = 20 max-returns = 6 diff --git a/doc/user_guide/installation/pre-commit-integration.rst b/doc/user_guide/installation/pre-commit-integration.rst index 62dfbcfd02..4aa66dda7d 100644 --- a/doc/user_guide/installation/pre-commit-integration.rst +++ b/doc/user_guide/installation/pre-commit-integration.rst @@ -3,10 +3,28 @@ Pre-commit integration ====================== -``pylint`` can be used as a `pre-commit `_ hook. +``pylint`` can be used as a `pre-commit `_ hook. We however +discourage it as pylint -- due to its speed -- is more suited to a continuous integration +job or a git ``pre-push`` hook, especially if your repository is large. Since ``pylint`` needs to import modules and dependencies to work correctly, the -hook only works with a local installation of ``pylint`` (in your environment). +hook only works with a local installation of ``pylint`` (in your environment). It means +it can't be used with ``pre-commit.ci``, and you will need to add the following to your +``.pre-commit-config.yaml`` :: + +.. sourcecode:: yaml + + ci: + skip: [pylint] + +Another limitation is that pylint should analyse all your code at once in order to best infer the +actual values that result from calls. If only some of the files are given, pylint might +miss a particular value's type and produce inferior inference for the subset. Since pre-commit slices +the files given to it in order to parallelize the processing, the result can be degraded. +It can also be unexpectedly different when the file set changes because the new slicing can change +the inference. Thus the ``require_serial`` option should be set to ``true`` if correctness and determinism +are more important than parallelization to you. + If you installed ``pylint`` locally it can be added to ``.pre-commit-config.yaml`` as follows: @@ -19,6 +37,7 @@ as follows: entry: pylint language: system types: [python] + require_serial: true args: [ "-rn", # Only display messages diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index 99cf238480..fc487fc25c 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -69,6 +69,7 @@ All messages in the error category: error/catching-non-exception error/class-variable-slots-conflict error/continue-in-finally + error/declare-non-slot error/dict-iter-missing-items error/duplicate-argument-name error/duplicate-bases @@ -347,9 +348,13 @@ All messages in the warning category: warning/useless-parent-delegation warning/useless-type-doc warning/useless-with-lock + warning/using-assignment-expression-in-unsupported-version warning/using-constant-test + warning/using-exception-groups-in-unsupported-version warning/using-f-string-in-unsupported-version warning/using-final-decorator-in-unsupported-version + warning/using-generic-type-syntax-in-unsupported-version + warning/using-positional-only-args-in-unsupported-version warning/while-used warning/wildcard-import warning/wrong-exception-operation @@ -534,12 +539,13 @@ All messages in the refactor category: refactor/too-many-instance-attributes refactor/too-many-locals refactor/too-many-nested-blocks - refactor/too-many-positional + refactor/too-many-positional-arguments refactor/too-many-public-methods refactor/too-many-return-statements refactor/too-many-statements refactor/trailing-comma-tuple refactor/unnecessary-comprehension + refactor/unnecessary-default-type-args refactor/unnecessary-dict-index-lookup refactor/unnecessary-list-index-lookup refactor/use-a-generator diff --git a/doc/user_guide/usage/run.rst b/doc/user_guide/usage/run.rst index 3136181254..8c4d520cea 100644 --- a/doc/user_guide/usage/run.rst +++ b/doc/user_guide/usage/run.rst @@ -18,8 +18,7 @@ On versions below 2.15, specifying a directory that is not an explicit package mydir/__init__.py:1:0: F0010: error while code parsing: Unable to load file mydir/__init__.py: [Errno 2] No such file or directory: 'mydir/__init__.py' (parse-error) -Thus, on versions before 2.15, or when dealing with certain edge cases that have not yet been solved, -using the ``--recursive=y`` option allows for linting a namespace package:: +Thus, on versions before 2.15 using the ``--recursive=y`` option allows for linting a namespace package:: pylint --recursive=y mydir mymodule mypackage @@ -153,6 +152,7 @@ Parallel execution It is possible to speed up the execution of Pylint. If the running computer has more CPUs than one, then the work for checking all files could be spread across all cores via Pylints's sub-processes. + This functionality is exposed via the ``-j`` command-line parameter. If the provided number is 0, then the total number of CPUs will be autodetected and used. @@ -164,6 +164,15 @@ This will spawn 4 parallel Pylint sub-process, where each provided module will be checked in parallel. Discovered problems by checkers are not displayed immediately. They are shown just after checking a module is complete. +You can also do your own parallelization by launching pylint multiple times on subsets +of your files (like ``pre-commit`` with the default ``require_serial=false`` does). +Be aware, though: pylint should analyse all your code at once in order to best infer +the actual values that result from calls. If only some of the files are given, pylint +might miss a particular value's type and produce inferior inference for the subset. +It can also be unexpectedly different when the file set changes because the new +slicing can change the inference. So, don't do this if correctness and determinism +are important to you. + Exit codes ---------- diff --git a/doc/whatsnew/0/0.x.rst b/doc/whatsnew/0/0.x.rst index 68abe04767..793f07a941 100644 --- a/doc/whatsnew/0/0.x.rst +++ b/doc/whatsnew/0/0.x.rst @@ -1001,7 +1001,7 @@ Release date: 2004-10-19 * avoid importing analyzed modules ! * new Refactor and Convention message categories. Some Warnings have been - remaped into those new categories + remapped into those new categories * added "similar", a tool to find copied and pasted lines of code, both using a specific command line tool and integrated as a diff --git a/doc/whatsnew/1/1.5.rst b/doc/whatsnew/1/1.5.rst index f8ce036060..51c7cf2ce9 100644 --- a/doc/whatsnew/1/1.5.rst +++ b/doc/whatsnew/1/1.5.rst @@ -435,7 +435,7 @@ Release date: 2015-11-29 * astroid.utils.LocalsVisitor was moved to pylint.pyreverse.LocalsVisitor. * pylint.checkers.utils.excepts_import_error was removed. - Use pylint.chekcers.utils.error_of_type instead. + Use pylint.checkers.utils.error_of_type instead. * Don't emit undefined-all-variables for nodes which can't be inferred (YES nodes). diff --git a/doc/whatsnew/2/2.0/summary.rst b/doc/whatsnew/2/2.0/summary.rst index c74f4dc5b8..52d8945073 100644 --- a/doc/whatsnew/2/2.0/summary.rst +++ b/doc/whatsnew/2/2.0/summary.rst @@ -221,7 +221,7 @@ Other Changes ``pylint`` should be a bit faster as well. We added a new flag, ``max_inferable_values`` on ``astroid.MANAGER`` for - limitting the maximum amount of values that ``astroid`` can infer when inferring + limiting the maximum amount of values that ``astroid`` can infer when inferring values. This change should improve the performance when dealing with large frameworks such as ``django``. You can also control this behaviour with ``pylint --limit-inference-results`` diff --git a/doc/whatsnew/2/2.13/summary.rst b/doc/whatsnew/2/2.13/summary.rst index ddfb98f840..e2ad8698e6 100644 --- a/doc/whatsnew/2/2.13/summary.rst +++ b/doc/whatsnew/2/2.13/summary.rst @@ -81,7 +81,7 @@ New checkers creates memory leaks by never letting the instance get garbage collected. Closes #5670 - Clsoes #6180 + Closes #6180 Removed checkers ================ diff --git a/doc/whatsnew/2/2.4/full.rst b/doc/whatsnew/2/2.4/full.rst index 079e37707b..ab871c8b5c 100644 --- a/doc/whatsnew/2/2.4/full.rst +++ b/doc/whatsnew/2/2.4/full.rst @@ -137,7 +137,7 @@ Release date: 2019-09-24 Closes #2925 * ``useless-suppression`` check now ignores ``cyclic-import`` suppressions, - which could lead to false postiives due to incomplete context at the time + which could lead to false positives due to incomplete context at the time of the check. Closes #3064 diff --git a/doc/whatsnew/2/2.6/full.rst b/doc/whatsnew/2/2.6/full.rst index 3896677dc0..dba2143497 100644 --- a/doc/whatsnew/2/2.6/full.rst +++ b/doc/whatsnew/2/2.6/full.rst @@ -54,7 +54,7 @@ Release date: 2020-08-20 * Add ``raise-missing-from`` check for exceptions that should have a cause. -* Support both isort 4 and isort 5. If you have pinned isort 4 in your projet requirements, nothing changes. If you use isort 5, though, note that the ``known-standard-library`` option is not interpreted the same in isort 4 and isort 5 (see the migration guide in isort documentation for further details). For compatibility's sake for most pylint users, the ``known-standard-library`` option in pylint now maps to ``extra-standard-library`` in isort 5. If you really want what ``known-standard-library`` now means in isort 5, you must disable the ``wrong-import-order`` check in pylint and run isort manually with a proper isort configuration file. +* Support both isort 4 and isort 5. If you have pinned isort 4 in your project requirements, nothing changes. If you use isort 5, though, note that the ``known-standard-library`` option is not interpreted the same in isort 4 and isort 5 (see the migration guide in isort documentation for further details). For compatibility's sake for most pylint users, the ``known-standard-library`` option in pylint now maps to ``extra-standard-library`` in isort 5. If you really want what ``known-standard-library`` now means in isort 5, you must disable the ``wrong-import-order`` check in pylint and run isort manually with a proper isort configuration file. Closes #3722 diff --git a/doc/whatsnew/2/2.6/summary.rst b/doc/whatsnew/2/2.6/summary.rst index e4aee082f6..9061bd00c0 100644 --- a/doc/whatsnew/2/2.6/summary.rst +++ b/doc/whatsnew/2/2.6/summary.rst @@ -25,4 +25,4 @@ Other Changes * Fix superfluous-parens false-positive for the walrus operator -* Add support for both isort 4 and isort 5. If you have pinned isort 4 in your projet requirements, nothing changes. If you use isort 5, though, note that the ``known-standard-library`` option is not interpreted the same in isort 4 and isort 5 (see `the migration guide in isort documentation` (no longer available) for further details). For compatibility's sake for most pylint users, the ``known-standard-library`` option in pylint now maps to ``extra-standard-library`` in isort 5. If you really want what ``known-standard-library`` now means in isort 5, you must disable the ``wrong-import-order`` check in pylint and run isort manually with a proper isort configuration file. +* Add support for both isort 4 and isort 5. If you have pinned isort 4 in your project requirements, nothing changes. If you use isort 5, though, note that the ``known-standard-library`` option is not interpreted the same in isort 4 and isort 5 (see `the migration guide in isort documentation` (no longer available) for further details). For compatibility's sake for most pylint users, the ``known-standard-library`` option in pylint now maps to ``extra-standard-library`` in isort 5. If you really want what ``known-standard-library`` now means in isort 5, you must disable the ``wrong-import-order`` check in pylint and run isort manually with a proper isort configuration file. diff --git a/doc/whatsnew/3/3.0/index.rst b/doc/whatsnew/3/3.0/index.rst index aad65c984c..cc975ca40d 100644 --- a/doc/whatsnew/3/3.0/index.rst +++ b/doc/whatsnew/3/3.0/index.rst @@ -388,7 +388,7 @@ New Checks Closes #8260 (`#8260 `_) -- Add a new checker ``kwarg-superseded-by-positional-arg`` to warn when a function is called with a keyword argument which shares a name with a positional-only parameter and the function contains a keyword variadic parameter dictionary. It may be surprising behaviour when the keyword argument is added to the keyword variadic parameter dictionary. +- Add a new message ``kwarg-superseded-by-positional-arg`` to warn when a function is called with a keyword argument which shares a name with a positional-only parameter and the function contains a keyword variadic parameter dictionary. It may be surprising behaviour when the keyword argument is added to the keyword variadic parameter dictionary. Closes #8558 (`#8558 `_) diff --git a/doc/whatsnew/3/3.1/index.rst b/doc/whatsnew/3/3.1/index.rst index 1abe832dcb..900a5ef0b6 100644 --- a/doc/whatsnew/3/3.1/index.rst +++ b/doc/whatsnew/3/3.1/index.rst @@ -118,7 +118,7 @@ Other Bug Fixes The message will report imports as follows: For "import X", it will report "(standard/third party/first party/local) import X" For "import X.Y" and "from X import Y", it will report "(standard/third party/first party/local) import X.Y" - The import category is specified to provide explanation as to why pylint has issued the message and guidence to the developer on how to fix the problem. + The import category is specified to provide explanation as to why pylint has issued the message and guidance to the developer on how to fix the problem. Closes #8808 (`#8808 `_) diff --git a/doc/whatsnew/3/3.3/index.rst b/doc/whatsnew/3/3.3/index.rst new file mode 100644 index 0000000000..775afd7065 --- /dev/null +++ b/doc/whatsnew/3/3.3/index.rst @@ -0,0 +1,221 @@ + +*************************** + What's New in Pylint 3.3 +*************************** + +.. toctree:: + :maxdepth: 2 + +:Release:3.3 +:Date: 2024-09-20 + +Summary -- Release highlights +============================= + +.. towncrier release notes start + +What's new in Pylint 3.3.3? +--------------------------- +Release date: 2024-12-23 + + +False Positives Fixed +--------------------- + +- Fix false positives for ``undefined-variable`` for classes using Python 3.12 + generic type syntax. + + Closes #9335 (`#9335 `_) + +- Fix a false positive for `use-implicit-booleaness-not-len`. No lint should be emitted for + generators (`len` is not defined for generators). + + Refs #10100 (`#10100 `_) + + + +Other Bug Fixes +--------------- + +- Fix ``Unable to import 'collections.abc' (import-error)`` on Python 3.13.1. + + Closes #10112 (`#10112 `_) + + + +What's new in Pylint 3.3.2? +--------------------------- +Release date: 2024-12-01 + + +False Positives Fixed +--------------------- + +- Fix a false positive for `potential-index-error` when an indexed iterable + contains a starred element that evaluates to more than one item. + + Closes #10076 (`#10076 `_) + + + +Other Bug Fixes +--------------- + +- Fixes the issue with --source-root option not working when the source files are in a subdirectory of the source root (e.g. when using a /src layout). + + Closes #10026 (`#10026 `_) + + + +What's new in Pylint 3.3.1? +--------------------------- +Release date: 2024-09-24 + + +False Positives Fixed +--------------------- + +- Fix regression causing some f-strings to not be inferred as strings. + + Closes #9947 (`#9947 `_) + + + +What's new in Pylint 3.3.0? +--------------------------- +Release date: 2024-09-20 + + +Changes requiring user actions +------------------------------ + +- We migrated ``symilar`` to argparse, from getopt, so the error and help output changed + (for the better). We exit with 2 instead of sometime 1, sometime 2. The error output + is not captured by the runner anymore. It's not possible to use a value for the + boolean options anymore (``--ignore-comments 1`` should become ``--ignore-comments``). + + Refs #9731 (`#9731 `_) + + + +New Features +------------ + +- Add new `declare-non-slot` error which reports when a class has a `__slots__` member and a type hint on the class is not present in `__slots__`. + + Refs #9499 (`#9499 `_) + + + +New Checks +---------- + +- Added `too-many-positional-arguments` to allow distinguishing the configuration for too many + total arguments (with keyword-only params specified after `*`) from the configuration + for too many positional-or-keyword or positional-only arguments. + + As part of evaluating whether this check makes sense for your project, ensure you + adjust the value of `--max-positional-arguments`. + + Closes #9099 (`#9099 `_) + +- Add `using-exception-groups-in-unsupported-version` and + `using-generic-type-syntax-in-unsupported-version` for uses of Python 3.11+ or + 3.12+ features on lower supported versions provided with `--py-version`. + + Closes #9791 (`#9791 `_) + +- Add `using-assignment-expression-in-unsupported-version` for uses of `:=` (walrus operator) + on Python versions below 3.8 provided with `--py-version`. + + Closes #9820 (`#9820 `_) + +- Add `using-positional-only-args-in-unsupported-version` for uses of positional-only args on + Python versions below 3.8 provided with `--py-version`. + + Closes #9823 (`#9823 `_) + +- Add ``unnecessary-default-type-args`` to the ``typing`` extension to detect the use + of unnecessary default type args for ``typing.Generator`` and ``typing.AsyncGenerator``. + + Refs #9938 (`#9938 `_) + + + +False Negatives Fixed +--------------------- + +- Fix computation of never-returning function: `Never` is handled in addition to `NoReturn`, and priority is given to the explicit `--never-returning-functions` option. + + Closes #7565. (`#7565 `_) + +- Fix a false negative for `await-outside-async` when await is inside Lambda. + + Refs #9653 (`#9653 `_) + +- Fix a false negative for ``duplicate-argument-name`` by including ``positional-only``, ``*args`` and ``**kwargs`` arguments in the check. + + Closes #9669 (`#9669 `_) + +- Fix false negative for `multiple-statements` when multiple statements are present on `else` and `finally` lines of `try`. + + Refs #9759 (`#9759 `_) + +- Fix false negatives when `isinstance` does not have exactly two arguments. + pylint now emits a `too-many-function-args` or `no-value-for-parameter` + appropriately for `isinstance` calls. + + Closes #9847 (`#9847 `_) + + + +Other Bug Fixes +--------------- + +- `--enable` with `--disable=all` now produces an error, when an unknown msg code is used. Internal `pylint` messages are no longer affected by `--disable=all`. + + Closes #9403 (`#9403 `_) + +- Impossible to compile regexes for paths in the configuration or argument given to pylint won't crash anymore but + raise an argparse error and display the error message from ``re.compile`` instead. + + Closes #9680 (`#9680 `_) + +- Fix a bug where a ``tox.ini`` file with pylint configuration was ignored and it exists in the current directory. + + ``.cfg`` and ``.ini`` files containing a ``Pylint`` configuration may now use a section named ``[pylint]``. This enhancement impacts the scenario where these file types are used as defaults when they are present and have not been explicitly referred to, using the ``--rcfile`` option. + + Closes #9727 (`#9727 `_) + +- Improve file discovery for directories that are not python packages. + + Closes #9764 (`#9764 `_) + + + +Other Changes +------------- + +- Remove support for launching pylint with Python 3.8. + Code that supports Python 3.8 can still be linted with the ``--py-version=3.8`` setting. + + Refs #9774 (`#9774 `_) + +- Add support for Python 3.13. + + Refs #9852 (`#9852 `_) + + + +Internal Changes +---------------- + +- All variables, classes, functions and file names containing the word 'similar', when it was, + in fact, referring to 'symilar' (the standalone program for the duplicate-code check) were renamed + to 'symilar'. + + Closes #9734 (`#9734 `_) + +- Remove old-style classes (Python 2) code and remove check for new-style class since everything is new-style in Python 3. Updated doc for exception checker to remove reference to new style class. + + Refs #9925 (`#9925 `_) diff --git a/doc/whatsnew/3/index.rst b/doc/whatsnew/3/index.rst index 5c44fa39a6..223a2af420 100644 --- a/doc/whatsnew/3/index.rst +++ b/doc/whatsnew/3/index.rst @@ -6,6 +6,7 @@ This is the full list of change in pylint 3.x minors, by categories. .. toctree:: :maxdepth: 2 + 3.3/index 3.2/index 3.1/index 3.0/index diff --git a/examples/pylintrc b/examples/pylintrc index 24d91bd4e9..97064b3e95 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -93,7 +93,7 @@ prefer-stubs=no # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.10 +py-version=3.12 # Discover python modules and packages in the file system subtree. recursive=no @@ -307,6 +307,9 @@ max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 +# Maximum number of positional arguments for function / method. +max-positional-arguments=5 + # Maximum number of public methods for a class (see R0904). max-public-methods=20 diff --git a/examples/pyproject.toml b/examples/pyproject.toml index 8b3c7b29f7..ec4790b26f 100644 --- a/examples/pyproject.toml +++ b/examples/pyproject.toml @@ -83,7 +83,7 @@ persistent = true # Minimum Python version to use for version dependent checks. Will default to the # version used to run pylint. -py-version = "3.10" +py-version = "3.12" # Discover python modules and packages in the file system subtree. # recursive = @@ -269,6 +269,9 @@ max-locals = 15 # Maximum number of parents for a class (see R0901). max-parents = 7 +# Maximum number of positional arguments for function / method. +max-positional-arguments = 5 + # Maximum number of public methods for a class (see R0904). max-public-methods = 20 diff --git a/pylint/__init__.py b/pylint/__init__.py index 75cf2b6ee4..74bde8a394 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -61,9 +61,9 @@ def run_symilar(argv: Sequence[str] | None = None) -> NoReturn: argv can be a sequence of strings normally supplied as arguments on the command line """ - from pylint.checkers.similar import Run as SimilarRun + from pylint.checkers.symilar import Run as SymilarRun - SimilarRun(argv or sys.argv[1:]) + SymilarRun(argv or sys.argv[1:]) def modify_sys_path() -> None: diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 0f4bd5319f..625eeee1b0 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "3.2.7" +__version__ = "3.3.3" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 0f99859181..9f65eb6e1f 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -110,6 +110,7 @@ def table_lines_from_stats( ("error", "NC"), ] + # pylint: disable=possibly-used-before-assignment for index, value in enumerate(new): new_value = value[1] old_value = old[index][1] diff --git a/pylint/checkers/base/basic_error_checker.py b/pylint/checkers/base/basic_error_checker.py index e58be23096..d6e10f31d0 100644 --- a/pylint/checkers/base/basic_error_checker.py +++ b/pylint/checkers/base/basic_error_checker.py @@ -7,8 +7,6 @@ from __future__ import annotations import itertools -from collections.abc import Iterator -from typing import Any import astroid from astroid import nodes @@ -145,7 +143,7 @@ class BasicErrorChecker(_BasicChecker): "pre-decrement operator -- and ++, which doesn't exist in Python.", ), "E0108": ( - "Duplicate argument name %s in function definition", + "Duplicate argument name %r in function definition", "duplicate-argument-name", "Duplicate argument names in function definitions are syntax errors.", ), @@ -285,8 +283,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self.add_message("return-in-init", node=node) # Check for duplicate names by clustering args with same name for detailed report arg_clusters = {} - arguments: Iterator[Any] = filter(None, [node.args.args, node.args.kwonlyargs]) - for arg in itertools.chain.from_iterable(arguments): + for arg in node.args.arguments: if arg.name in arg_clusters: self.add_message( "duplicate-argument-name", diff --git a/pylint/checkers/base/function_checker.py b/pylint/checkers/base/function_checker.py index f7d92a4649..2826b40b4d 100644 --- a/pylint/checkers/base/function_checker.py +++ b/pylint/checkers/base/function_checker.py @@ -45,7 +45,7 @@ def _check_contextmanager_generator_missing_cleanup( :param node: FunctionDef node to check :type node: nodes.FunctionDef """ - # if function does not use a Yield statement, it cant be a generator + # if function does not use a Yield statement, it can't be a generator with_nodes = list(node.nodes_of_class(nodes.With)) if not with_nodes: return diff --git a/pylint/checkers/base/name_checker/checker.py b/pylint/checkers/base/name_checker/checker.py index 3514829fb1..1d8589a576 100644 --- a/pylint/checkers/base/name_checker/checker.py +++ b/pylint/checkers/base/name_checker/checker.py @@ -14,7 +14,7 @@ from collections.abc import Iterable from enum import Enum, auto from re import Pattern -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING import astroid from astroid import nodes @@ -34,7 +34,7 @@ if TYPE_CHECKING: from pylint.lint.pylinter import PyLinter -_BadNamesTuple = Tuple[nodes.NodeNG, str, str, interfaces.Confidence] +_BadNamesTuple = tuple[nodes.NodeNG, str, str, interfaces.Confidence] # Default patterns for name types that do not have styles DEFAULT_PATTERNS = { diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index c3e1a081c8..0caeeadcdf 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -523,7 +523,7 @@ def _has_same_layout_slots( "Access to a protected member %s of a client class", # E0214 "protected-access", "Used when a protected member (i.e. class member with a name " - "beginning with an underscore) is access outside the class or a " + "beginning with an underscore) is accessed outside the class or a " "descendant of the class where it's defined.", ), "W0213": ( @@ -702,6 +702,12 @@ def _has_same_layout_slots( "Used when a class tries to extend an inherited Enum class. " "Doing so will raise a TypeError at runtime.", ), + "E0245": ( + "No such name %r in __slots__", + "declare-non-slot", + "Raised when a type annotation on a class is absent from the list of names in __slots__, " + "and __slots__ does not contain a __dict__ entry.", + ), "R0202": ( "Consider using a decorator instead of calling classmethod", "no-classmethod-decorator", @@ -870,6 +876,7 @@ def _dummy_rgx(self) -> Pattern[str]: "invalid-enum-extension", "subclassed-final-class", "implicit-flag-alias", + "declare-non-slot", ) def visit_classdef(self, node: nodes.ClassDef) -> None: """Init visit variable _accessed.""" @@ -878,6 +885,50 @@ def visit_classdef(self, node: nodes.ClassDef) -> None: self._check_proper_bases(node) self._check_typing_final(node) self._check_consistent_mro(node) + self._check_declare_non_slot(node) + + def _check_declare_non_slot(self, node: nodes.ClassDef) -> None: + if not self._has_valid_slots(node): + return + + slot_names = self._get_classdef_slots_names(node) + + # Stop if empty __slots__ in the class body, this likely indicates that + # this class takes part in multiple inheritance with other slotted classes. + if not slot_names: + return + + # Stop if we find __dict__, since this means attributes can be set + # dynamically + if "__dict__" in slot_names: + return + + for base in node.bases: + ancestor = safe_infer(base) + if not isinstance(ancestor, nodes.ClassDef): + continue + # if any base doesn't have __slots__, attributes can be set dynamically, so stop + if not self._has_valid_slots(ancestor): + return + for slot_name in self._get_classdef_slots_names(ancestor): + if slot_name == "__dict__": + return + slot_names.append(slot_name) + + # Every class in bases has __slots__, our __slots__ is non-empty and there is no __dict__ + + for child in node.body: + if isinstance(child, nodes.AnnAssign): + if child.value is not None: + continue + if isinstance(child.target, nodes.AssignName): + if child.target.name not in slot_names: + self.add_message( + "declare-non-slot", + args=child.target.name, + node=child.target, + confidence=INFERENCE, + ) def _check_consistent_mro(self, node: nodes.ClassDef) -> None: """Detect that a class has a consistent mro or duplicate bases.""" @@ -1482,6 +1533,28 @@ def _check_functools_or_not(self, decorator: nodes.Attribute) -> bool: return "functools" in dict(import_node.names) + def _has_valid_slots(self, node: nodes.ClassDef) -> bool: + if "__slots__" not in node.locals: + return False + + try: + inferred_slots = tuple(node.ilookup("__slots__")) + except astroid.InferenceError: + return False + for slots in inferred_slots: + # check if __slots__ is a valid type + if isinstance(slots, util.UninferableBase): + return False + if not is_iterable(slots) and not is_comprehension(slots): + return False + if isinstance(slots, nodes.Const): + return False + if not hasattr(slots, "itered"): + # we can't obtain the values, maybe a .deque? + return False + + return True + def _check_slots(self, node: nodes.ClassDef) -> None: if "__slots__" not in node.locals: return @@ -1519,13 +1592,23 @@ def _check_slots(self, node: nodes.ClassDef) -> None: continue self._check_redefined_slots(node, slots, values) - def _check_redefined_slots( - self, - node: nodes.ClassDef, - slots_node: nodes.NodeNG, - slots_list: list[nodes.NodeNG], - ) -> None: - """Check if `node` redefines a slot which is defined in an ancestor class.""" + def _get_classdef_slots_names(self, node: nodes.ClassDef) -> list[str]: + + slots_names: list[str] = [] + try: + inferred_slots = tuple(node.ilookup("__slots__")) + except astroid.InferenceError: # pragma: no cover + return slots_names + for slots in inferred_slots: + if isinstance(slots, nodes.Dict): + values = [item[0] for item in slots.items] + else: + values = slots.itered() + slots_names.extend(self._get_slots_names(values)) + + return slots_names + + def _get_slots_names(self, slots_list: list[nodes.NodeNG]) -> list[str]: slots_names: list[str] = [] for slot in slots_list: if isinstance(slot, nodes.Const): @@ -1535,6 +1618,16 @@ def _check_redefined_slots( inferred_slot_value = getattr(inferred_slot, "value", None) if isinstance(inferred_slot_value, str): slots_names.append(inferred_slot_value) + return slots_names + + def _check_redefined_slots( + self, + node: nodes.ClassDef, + slots_node: nodes.NodeNG, + slots_list: list[nodes.NodeNG], + ) -> None: + """Check if `node` redefines a slot which is defined in an ancestor class.""" + slots_names: list[str] = self._get_slots_names(slots_list) # Slots of all parent classes ancestors_slots_names = { @@ -1685,7 +1778,7 @@ def _check_in_slots(self, node: nodes.AssignAttr) -> None: klass = inferred._proxied if not has_known_bases(klass): return - if "__slots__" not in klass.locals or not klass.newstyle: + if "__slots__" not in klass.locals: return # If `__setattr__` is defined on the class, then we can't reason about # what will happen when assigning to an attribute. diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index 78378e92c9..5c1adbc888 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -16,6 +16,7 @@ from pylint.checkers import BaseChecker from pylint.checkers.utils import is_enum, only_required_for_messages +from pylint.interfaces import HIGH from pylint.typing import MessageDefinitionTuple if TYPE_CHECKING: @@ -81,11 +82,9 @@ "Used when an if statement contains too many boolean expressions.", ), "R0917": ( - "Too many positional arguments in a function call.", - "too-many-positional", - "Will be implemented in https://github.com/pylint-dev/pylint/issues/9099," - "msgid/symbol pair reserved for compatibility with ruff, " - "see https://github.com/astral-sh/ruff/issues/8946.", + "Too many positional arguments (%s/%s)", + "too-many-positional-arguments", + "Used when a function has too many positional arguments.", ), } ) @@ -311,6 +310,15 @@ class MisdesignChecker(BaseChecker): "help": "Maximum number of arguments for function / method.", }, ), + ( + "max-positional-arguments", + { + "default": 5, + "type": "int", + "metavar": "", + "help": "Maximum number of positional arguments for function / method.", + }, + ), ( "max-locals", { @@ -525,6 +533,7 @@ def leave_classdef(self, node: nodes.ClassDef) -> None: "too-many-branches", "too-many-arguments", "too-many-locals", + "too-many-positional-arguments", "too-many-statements", "keyword-arg-before-vararg", ) @@ -536,13 +545,20 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: self._returns.append(0) # check number of arguments args = node.args.args + node.args.posonlyargs + node.args.kwonlyargs + pos_args = node.args.args + node.args.posonlyargs ignored_argument_names = self.linter.config.ignored_argument_names if args is not None: ignored_args_num = 0 if ignored_argument_names: - ignored_args_num = sum( - 1 for arg in args if ignored_argument_names.match(arg.name) + ignored_pos_args_num = sum( + 1 for arg in pos_args if ignored_argument_names.match(arg.name) + ) + ignored_kwonly_args_num = sum( + 1 + for arg in node.args.kwonlyargs + if ignored_argument_names.match(arg.name) ) + ignored_args_num = ignored_pos_args_num + ignored_kwonly_args_num argnum = len(args) - ignored_args_num if argnum > self.linter.config.max_args: @@ -551,6 +567,16 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: node=node, args=(len(args), self.linter.config.max_args), ) + pos_args_count = ( + len(args) - len(node.args.kwonlyargs) - ignored_pos_args_num + ) + if pos_args_count > self.linter.config.max_positional_arguments: + self.add_message( + "too-many-positional-arguments", + node=node, + args=(pos_args_count, self.linter.config.max_positional_arguments), + confidence=HIGH, + ) else: ignored_args_num = 0 # check number of local variables diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 688dc829a0..3834f260be 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -35,7 +35,7 @@ def predicate(obj: Any) -> bool: def _annotated_unpack_infer( stmt: nodes.NodeNG, context: InferenceContext | None = None -) -> Generator[tuple[nodes.NodeNG, SuccessfulInferenceResult], None, None]: +) -> Generator[tuple[nodes.NodeNG, SuccessfulInferenceResult]]: """Recursively generate nodes inferred by the given statement. If the inferred value is a list or a tuple, recurse on the elements. @@ -92,10 +92,9 @@ def _is_raising(body: list[nodes.NodeNG]) -> bool: {"old_names": [("E0703", "bad-exception-context")]}, ), "E0710": ( - "Raising a new style class which doesn't inherit from BaseException", + "Raising a class which doesn't inherit from BaseException", "raising-non-exception", - "Used when a new style class which doesn't inherit from " - "BaseException is raised.", + "Used when a class which doesn't inherit from BaseException is raised.", ), "E0711": ( "NotImplemented raised - should raise NotImplementedError", @@ -262,12 +261,11 @@ def visit_instance(self, instance: objects.ExceptionInstance) -> None: 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, - confidence=INFERENCE, - ) + self._checker.add_message( + "raising-non-exception", + node=self._node, + confidence=INFERENCE, + ) def visit_tuple(self, _: nodes.Tuple) -> None: self._checker.add_message( diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index f8aecbda64..12fa930162 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -496,6 +496,10 @@ def visit_default(self, node: nodes.NodeNG) -> None: prev_sibl = node.previous_sibling() if prev_sibl is not None: prev_line = prev_sibl.fromlineno + elif isinstance( + node.parent, nodes.Try + ) and self._is_first_node_in_else_finally_body(node, node.parent): + prev_line = self._infer_else_finally_line_number(node, node.parent) elif isinstance(node.parent, nodes.Module): prev_line = 0 else: @@ -520,6 +524,28 @@ def visit_default(self, node: nodes.NodeNG) -> None: except KeyError: lines.append("") + def _is_first_node_in_else_finally_body( + self, node: nodes.NodeNG, parent: nodes.Try + ) -> bool: + if parent.orelse and node == parent.orelse[0]: + return True + if parent.finalbody and node == parent.finalbody[0]: + return True + return False + + def _infer_else_finally_line_number( + self, node: nodes.NodeNG, parent: nodes.Try + ) -> int: + last_line_of_prev_block = 0 + if node in parent.finalbody and parent.orelse: + last_line_of_prev_block = parent.orelse[-1].tolineno + elif parent.handlers and parent.handlers[-1].body: + last_line_of_prev_block = parent.handlers[-1].body[-1].tolineno + elif parent.body: + last_line_of_prev_block = parent.body[-1].tolineno + + return last_line_of_prev_block + 1 if last_line_of_prev_block else 0 + def _check_multi_statement_line(self, node: nodes.NodeNG, line: int) -> None: """Check for lines containing multiple statements.""" if isinstance(node, nodes.With): diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index afef0277ee..2fa212cd7a 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -13,7 +13,7 @@ from collections import defaultdict from collections.abc import ItemsView, Sequence from functools import cached_property -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import TYPE_CHECKING, Any, Union import astroid from astroid import nodes @@ -43,7 +43,7 @@ # 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]]] +_ImportTree = dict[str, Union[list[dict[str, Any]], list[str]]] DEPRECATED_MODULES = { (0, 0, 0): {"tkinter.tix", "fpectl"}, @@ -79,6 +79,7 @@ "uu", "xdrlib", }, + (3, 13, 0): {"getopt"}, } diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py index 8a02d662ba..d057c78ecb 100644 --- a/pylint/checkers/logging.py +++ b/pylint/checkers/logging.py @@ -16,6 +16,7 @@ from pylint import checkers from pylint.checkers import utils from pylint.checkers.utils import infer_all +from pylint.interfaces import HIGH from pylint.typing import MessageDefinitionTuple if TYPE_CHECKING: @@ -369,7 +370,7 @@ def _check_format_string(self, node: nodes.Call, format_arg: Literal[0, 1]) -> N self.add_message("logging-format-truncated", node=node) return if num_args > required_num_args: - self.add_message("logging-too-many-args", node=node) + self.add_message("logging-too-many-args", node=node, confidence=HIGH) elif num_args < required_num_args: self.add_message("logging-too-few-args", node=node) diff --git a/pylint/checkers/method_args.py b/pylint/checkers/method_args.py index 59083fa25a..565309d282 100644 --- a/pylint/checkers/method_args.py +++ b/pylint/checkers/method_args.py @@ -41,7 +41,6 @@ class MethodArgsChecker(BaseChecker): "positional-only-arguments-expected", "Emitted when positional-only arguments have been passed as keyword arguments. " "Remove the keywords for the affected arguments in the function call.", - {"minversion": (3, 8)}, ), } options = ( diff --git a/pylint/checkers/nested_min_max.py b/pylint/checkers/nested_min_max.py index c8231fe7d2..2a3e05459e 100644 --- a/pylint/checkers/nested_min_max.py +++ b/pylint/checkers/nested_min_max.py @@ -14,7 +14,6 @@ from pylint.checkers import BaseChecker from pylint.checkers.utils import only_required_for_messages, safe_infer -from pylint.constants import PY39_PLUS from pylint.interfaces import INFERENCE if TYPE_CHECKING: @@ -139,8 +138,8 @@ def _is_splattable_expression(self, arg: nodes.NodeNG) -> bool: return self._is_splattable_expression( arg.left ) and self._is_splattable_expression(arg.right) - # Support dict merge (operator __or__ in Python 3.9) - if isinstance(arg, nodes.BinOp) and arg.op == "|" and PY39_PLUS: + # Support dict merge (operator __or__) + if isinstance(arg, nodes.BinOp) and arg.op == "|": return self._is_splattable_expression( arg.left ) and self._is_splattable_expression(arg.right) diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index 0c2c559fe8..920c8cc41e 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -12,11 +12,7 @@ from astroid import nodes from pylint.checkers import BaseChecker -from pylint.checkers.utils import ( - has_known_bases, - node_frame_class, - only_required_for_messages, -) +from pylint.checkers.utils import node_frame_class, only_required_for_messages from pylint.typing import MessageDefinitionTuple if TYPE_CHECKING: @@ -72,55 +68,51 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None: ): continue - # super should not be used on an old style class - if klass.newstyle or not has_known_bases(klass): - # super first arg should not be the class - if not call.args: - continue - - # calling super(type(self), self) can lead to recursion loop - # in derived classes - arg0 = call.args[0] - if ( - isinstance(arg0, nodes.Call) - and isinstance(arg0.func, nodes.Name) - and arg0.func.name == "type" - ): - self.add_message("bad-super-call", node=call, args=("type",)) - continue - - # calling super(self.__class__, self) can lead to recursion loop - # in derived classes - if ( - len(call.args) >= 2 - and isinstance(call.args[1], nodes.Name) - and call.args[1].name == "self" - and isinstance(arg0, nodes.Attribute) - and arg0.attrname == "__class__" - ): - self.add_message( - "bad-super-call", node=call, args=("self.__class__",) - ) - continue - - try: - supcls = call.args and next(call.args[0].infer(), None) - except astroid.InferenceError: - continue - - # If the supcls is in the ancestors of klass super can be used to skip - # a step in the mro() and get a method from a higher parent - if klass is not supcls and all(i != supcls for i in klass.ancestors()): - name = None - # if supcls is not Uninferable, then supcls was inferred - # and use its name. Otherwise, try to look - # for call.args[0].name - if supcls: - name = supcls.name - elif call.args and hasattr(call.args[0], "name"): - name = call.args[0].name - if name: - self.add_message("bad-super-call", node=call, args=(name,)) + # super first arg should not be the class + if not call.args: + continue + + # calling super(type(self), self) can lead to recursion loop + # in derived classes + arg0 = call.args[0] + if ( + isinstance(arg0, nodes.Call) + and isinstance(arg0.func, nodes.Name) + and arg0.func.name == "type" + ): + self.add_message("bad-super-call", node=call, args=("type",)) + continue + + # calling super(self.__class__, self) can lead to recursion loop + # in derived classes + if ( + len(call.args) >= 2 + and isinstance(call.args[1], nodes.Name) + and call.args[1].name == "self" + and isinstance(arg0, nodes.Attribute) + and arg0.attrname == "__class__" + ): + self.add_message("bad-super-call", node=call, args=("self.__class__",)) + continue + + try: + supcls = call.args and next(call.args[0].infer(), None) + except astroid.InferenceError: + continue + + # If the supcls is in the ancestors of klass super can be used to skip + # a step in the mro() and get a method from a higher parent + if klass is not supcls and all(i != supcls for i in klass.ancestors()): + name = None + # if supcls is not Uninferable, then supcls was inferred + # and use its name. Otherwise, try to look + # for call.args[0].name + if supcls: + name = supcls.name + elif call.args and hasattr(call.args[0], "name"): + name = call.args[0].name + if name: + self.add_message("bad-super-call", node=call, args=(name,)) visit_asyncfunctiondef = visit_functiondef diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index 5818c2f4ac..7522faeb1c 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -81,7 +81,7 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): "equivalent.", ), "C1804": ( - '"%s" can be simplified to "%s", if it is striclty a string, as an empty string is falsey', + '"%s" can be simplified to "%s", if it is strictly a string, as an empty string is falsey', "use-implicit-booleaness-not-comparison-to-string", "Empty string are considered false in a boolean context. Following this" " check blindly in weakly typed code base can create hard to debug issues." @@ -125,14 +125,8 @@ def visit_call(self, node: nodes.Call) -> None: if not utils.is_test_condition(node, parent): return len_arg = node.args[0] - generator_or_comprehension = ( - nodes.ListComp, - nodes.SetComp, - nodes.DictComp, - nodes.GeneratorExp, - ) - if isinstance(len_arg, generator_or_comprehension): - # The node is a generator or comprehension as in len([x for x in ...]) + if isinstance(len_arg, (nodes.ListComp, nodes.SetComp, nodes.DictComp)): + # The node is a comprehension as in len([x for x in ...]) self.add_message( "use-implicit-booleaness-not-len", node=node, @@ -209,19 +203,17 @@ def _check_compare_to_str_or_zero(self, node: nodes.Compare) -> None: continue op_1 = all_ops[ops_idx] op_3 = all_ops[ops_idx + 2] - error_detected = False if self.linter.is_message_enabled( "use-implicit-booleaness-not-comparison-to-zero" ): + op = None # 0 ?? X if _is_constant_zero(op_1): - error_detected = True op = op_3 # X ?? 0 elif _is_constant_zero(op_3): - error_detected = True op = op_1 - if error_detected: + if op is not None: original = f"{op_1.as_string()} {op_2} {op_3.as_string()}" suggestion = ( op.as_string() @@ -234,20 +226,17 @@ def _check_compare_to_str_or_zero(self, node: nodes.Compare) -> None: node=node, confidence=HIGH, ) - error_detected = False if self.linter.is_message_enabled( "use-implicit-booleaness-not-comparison-to-str" ): - node_name = "" + node_name = None # x ?? "" if utils.is_empty_str_literal(op_1): - error_detected = True node_name = op_3.as_string() # '' ?? X elif utils.is_empty_str_literal(op_3): - error_detected = True node_name = op_1.as_string() - if error_detected: + if node_name is not None: suggestion = ( f"not {node_name}" if op_2 in {"==", "is"} else node_name ) diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 459e28f184..c67c8dd232 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -44,6 +44,7 @@ ( "_io.open", # regular 'open()' call "pathlib.Path.open", + "pathlib._local.Path.open", # Python 3.13 "codecs.open", "urllib.request.urlopen", "tempfile.NamedTemporaryFile", @@ -1691,7 +1692,7 @@ def _check_consider_using_with(self, node: nodes.Call) -> None: return inferred = utils.safe_infer(node.func) if not inferred or not isinstance( - inferred, (nodes.FunctionDef, nodes.ClassDef, bases.UnboundMethod) + inferred, (nodes.FunctionDef, nodes.ClassDef, bases.BoundMethod) ): return could_be_used_in_with = ( @@ -2026,12 +2027,13 @@ def _is_node_return_ended(self, node: nodes.NodeNG) -> bool: if isinstance(node, nodes.Return): return True if isinstance(node, nodes.Call): - try: - funcdef_node = node.func.inferred()[0] - if self._is_function_def_never_returning(funcdef_node): - return True - except astroid.InferenceError: - pass + return any( + ( + isinstance(maybe_func, (nodes.FunctionDef, bases.BoundMethod)) + and self._is_function_def_never_returning(maybe_func) + ) + for maybe_func in utils.infer_all(node.func) + ) if isinstance(node, nodes.While): # A while-loop is considered return-ended if it has a # truthy test and no break statements @@ -2083,22 +2085,23 @@ def _is_function_def_never_returning( Returns: bool: True if the function never returns, False otherwise. """ - if isinstance(node, (nodes.FunctionDef, astroid.BoundMethod)): - try: - returns: nodes.NodeNG | None = node.returns - except AttributeError: - return False # the BoundMethod proxy may be a lambda without a returns - if returns is not None: - return ( - isinstance(returns, nodes.Attribute) - and returns.attrname == "NoReturn" - or isinstance(returns, nodes.Name) - and returns.name == "NoReturn" - ) try: - return node.qname() in self._never_returning_functions + if node.qname() in self._never_returning_functions: + return True except (TypeError, AttributeError): - return False + pass + + try: + returns: nodes.NodeNG | None = node.returns + except AttributeError: + return False # the BoundMethod proxy may be a lambda without a returns + + return ( + isinstance(returns, nodes.Attribute) + and returns.attrname in {"NoReturn", "Never"} + or isinstance(returns, nodes.Name) + and returns.name in {"NoReturn", "Never"} + ) def _check_return_at_the_end(self, node: nodes.FunctionDef) -> None: """Check for presence of a *single* return statement at the end of a diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index 10c1d54bfc..9225cd4d26 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -8,7 +8,7 @@ import sys from collections.abc import Iterable -from typing import TYPE_CHECKING, Any, Dict, Set, Tuple +from typing import TYPE_CHECKING, Any import astroid from astroid import nodes, util @@ -22,7 +22,7 @@ if TYPE_CHECKING: from pylint.lint import PyLinter -DeprecationDict = Dict[Tuple[int, int, int], Set[str]] +DeprecationDict = dict[tuple[int, int, int], set[str]] OPEN_FILES_MODE = ("open", "file") OPEN_FILES_FUNCS = (*OPEN_FILES_MODE, "read_text", "write_text") @@ -33,7 +33,8 @@ ENV_GETTERS = ("os.getenv",) SUBPROCESS_POPEN = "subprocess.Popen" SUBPROCESS_RUN = "subprocess.run" -OPEN_MODULE = {"_io", "pathlib"} +OPEN_MODULE = {"_io", "pathlib", "pathlib._local"} +PATHLIB_MODULE = {"pathlib", "pathlib._local"} DEBUG_BREAKPOINTS = ("builtins.breakpoint", "sys.breakpointhook", "pdb.set_trace") LRU_CACHE = { "functools.lru_cache", # Inferred for @lru_cache @@ -53,6 +54,9 @@ "bool": ((None, "x"),), "float": ((None, "x"),), }, + (3, 5, 0): { + "importlib._bootstrap_external.cache_from_source": ((1, "debug_override"),), + }, (3, 8, 0): { "asyncio.tasks.sleep": ((None, "loop"),), "asyncio.tasks.gather": ((None, "loop"),), @@ -89,6 +93,9 @@ "email.utils.localtime": ((1, "isdst"),), "shutil.rmtree": ((2, "onerror"),), }, + (3, 13, 0): { + "dis.get_instructions": ((2, "show_caches"),), + }, } DEPRECATED_DECORATORS: DeprecationDict = { @@ -99,6 +106,7 @@ "abc.abstractproperty", }, (3, 4, 0): {"importlib.util.module_for_loader"}, + (3, 13, 0): {"typing.no_type_check_decorator"}, } @@ -268,6 +276,10 @@ "unittest.TestProgram.usageExit", }, (3, 12, 0): { + "asyncio.get_child_watcher", + "asyncio.set_child_watcher", + "asyncio.AbstractEventLoopPolicy.get_child_watcher", + "asyncio.AbstractEventLoopPolicy.set_child_watcher", "builtins.bool.__invert__", "datetime.datetime.utcfromtimestamp", "datetime.datetime.utcnow", @@ -277,6 +289,19 @@ "pty.slave_open", "xml.etree.ElementTree.Element.__bool__", }, + (3, 13, 0): { + "ctypes.SetPointerType", + "pathlib.PurePath.is_reserved", + "platform.java_ver", + "pydoc.is_package", + "sys._enablelegacywindowsfsencoding", + "wave.Wave_read.getmark", + "wave.Wave_read.getmarkers", + "wave.Wave_read.setmark", + "wave.Wave_write.getmark", + "wave.Wave_write.getmarkers", + "wave.Wave_write.setmark", + }, }, } @@ -333,6 +358,9 @@ "typing": { "Text", }, + "urllib.parse": { + "Quoter", + }, "webbrowser": { "MacOSX", }, @@ -365,6 +393,15 @@ "Sized", }, }, + (3, 13, 0): { + "glob": { + "glob.glob0", + "glob.glob1", + }, + "http.server": { + "CGIHTTPRequestHandler", + }, + }, } @@ -375,10 +412,18 @@ (3, 12, 0): { "calendar.January", "calendar.February", + "sqlite3.version", + "sqlite3.version_info", "sys.last_traceback", "sys.last_type", "sys.last_value", }, + (3, 13, 0): { + "dis.HAVE_ARGUMENT", + "tarfile.TarFile.tarfile", + "traceback.TracebackException.exc_type", + "typing.AnyStr", + }, } @@ -784,7 +829,7 @@ def _check_open_call( mode_arg = utils.get_argument_from_call( node, position=1, keyword="mode" ) - elif open_module == "pathlib": + elif open_module in PATHLIB_MODULE: mode_arg = utils.get_argument_from_call( node, position=0, keyword="mode" ) @@ -814,7 +859,7 @@ def _check_open_call( ): confidence = HIGH try: - if open_module == "pathlib": + if open_module in PATHLIB_MODULE: if node.func.attrname == "read_text": encoding_arg = utils.get_argument_from_call( node, position=0, keyword="encoding" diff --git a/pylint/checkers/similar.py b/pylint/checkers/symilar.py similarity index 87% rename from pylint/checkers/similar.py rename to pylint/checkers/symilar.py index ee1b608433..1e82633e65 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/symilar.py @@ -39,20 +39,9 @@ import warnings from collections import defaultdict from collections.abc import Callable, Generator, Iterable, Sequence -from getopt import GetoptError, getopt from io import BufferedIOBase, BufferedReader, BytesIO from itertools import chain -from typing import ( - TYPE_CHECKING, - Dict, - List, - NamedTuple, - NewType, - NoReturn, - TextIO, - Tuple, - Union, -) +from typing import TYPE_CHECKING, NamedTuple, NewType, NoReturn, TextIO, Union import astroid from astroid import nodes @@ -84,10 +73,10 @@ class LineSpecifs(NamedTuple): # Links LinesChunk object to the starting indices (in lineset's stripped lines) # of the different chunk of lines that are used to compute the hash -HashToIndex_T = Dict["LinesChunk", List[Index]] +HashToIndex_T = dict["LinesChunk", list[Index]] # Links index in the lineset's stripped lines to the real lines in the file -IndexToLines_T = Dict[Index, "SuccessiveLinesLimits"] +IndexToLines_T = dict[Index, "SuccessiveLinesLimits"] # The types the streams read by pylint can take. Originating from astroid.nodes.Module.stream() and open() STREAM_TYPES = Union[TextIO, BufferedReader, BytesIO] @@ -113,7 +102,7 @@ def __init__( # Links the indices to the starting line in both lineset's stripped lines to # the start and end lines in both files -CplIndexToCplLines_T = Dict["LineSetStartCouple", CplSuccessiveLinesLimits] +CplIndexToCplLines_T = dict["LineSetStartCouple", CplSuccessiveLinesLimits] class LinesChunk: @@ -212,7 +201,7 @@ def increment(self, value: Index) -> LineSetStartCouple: ) -LinesChunkLimits_T = Tuple["LineSet", LineNumber, LineNumber] +LinesChunkLimits_T = tuple["LineSet", LineNumber, LineNumber] def hash_lineset( @@ -343,7 +332,7 @@ class Commonality(NamedTuple): snd_file_end: LineNumber -class Similar: +class Symilar: """Finds copy-pasted lines of code in a project.""" def __init__( @@ -479,7 +468,7 @@ def _get_similarity_report( # pylint: disable = too-many-locals def _find_common( self, lineset1: LineSet, lineset2: LineSet - ) -> Generator[Commonality, None, None]: + ) -> Generator[Commonality]: """Find similarities in the two given linesets. This the core of the algorithm. The idea is to compute the hashes of a @@ -552,7 +541,7 @@ def _find_common( if eff_cmn_nb > self.namespace.min_similarity_lines: yield com - def _iter_sims(self) -> Generator[Commonality, None, None]: + def _iter_sims(self) -> Generator[Commonality]: """Iterate on similarities among all files, by making a Cartesian product. """ @@ -596,37 +585,41 @@ def stripped_lines( the line :return: the collection of line/line number/line type tuples """ + ignore_lines: set[int] = set() if ignore_imports or ignore_signatures: tree = astroid.parse("".join(lines)) - if ignore_imports: - import_lines = {} - for node in tree.nodes_of_class((nodes.Import, nodes.ImportFrom)): - for lineno in range(node.lineno, (node.end_lineno or node.lineno) + 1): - import_lines[lineno] = True - if ignore_signatures: - - def _get_functions( - functions: list[nodes.NodeNG], tree: nodes.NodeNG - ) -> list[nodes.NodeNG]: - """Recursively get all functions including nested in the classes from the - tree. - """ - for node in tree.body: - if isinstance(node, (nodes.FunctionDef, nodes.AsyncFunctionDef)): - functions.append(node) - - if isinstance( - node, - (nodes.ClassDef, nodes.FunctionDef, nodes.AsyncFunctionDef), - ): - _get_functions(functions, node) - - return functions - - functions = _get_functions([], tree) - signature_lines = set( - chain( - *( + if ignore_imports: + ignore_lines.update( + chain.from_iterable( + range(node.lineno, (node.end_lineno or node.lineno) + 1) + for node in tree.nodes_of_class((nodes.Import, nodes.ImportFrom)) + ) + ) + if ignore_signatures: + + def _get_functions( + functions: list[nodes.NodeNG], tree: nodes.NodeNG + ) -> list[nodes.NodeNG]: + """Recursively get all functions including nested in the classes from + the. + + tree. + """ + for node in tree.body: + if isinstance(node, (nodes.FunctionDef, nodes.AsyncFunctionDef)): + functions.append(node) + + if isinstance( + node, + (nodes.ClassDef, nodes.FunctionDef, nodes.AsyncFunctionDef), + ): + _get_functions(functions, node) + + return functions + + functions = _get_functions([], tree) + ignore_lines.update( + chain.from_iterable( range( func.lineno, func.body[0].lineno if func.body else func.tolineno + 1, @@ -634,7 +627,6 @@ def _get_functions( for func in functions ) ) - ) strippedlines = [] docstring = None @@ -656,13 +648,9 @@ def _get_functions( if line.endswith(docstring): docstring = None line = "" - if ignore_imports: - current_line_is_import = import_lines.get(lineno, False) - if current_line_is_import: - line = "" if ignore_comments: line = line.split("#", 1)[0].strip() - if ignore_signatures and lineno in signature_lines: + if lineno in ignore_lines: line = "" if line: strippedlines.append( @@ -752,19 +740,21 @@ def report_similarities( # wrapper to get a pylint checker from the similar class -class SimilarChecker(BaseRawFileChecker, Similar): +class SimilaritiesChecker(BaseRawFileChecker, Symilar): """Checks for similarities and duplicated code. This computation may be memory / CPU intensive, so you should disable it if you experience some problems. """ - # configuration section name name = "similarities" - # messages msgs = MSGS - # configuration options - # for available dict keys/values see the optik parser 'add_option' method + MIN_SIMILARITY_HELP = "Minimum lines number of a similarity." + IGNORE_COMMENTS_HELP = "Comments are removed from the similarity computation" + IGNORE_DOCSTRINGS_HELP = "Docstrings are removed from the similarity computation" + IGNORE_IMPORTS_HELP = "Imports are removed from the similarity computation" + IGNORE_SIGNATURES_HELP = "Signatures are removed from the similarity computation" + # for available dict keys/values see the option parser 'add_option' method options: Options = ( ( "min-similarity-lines", @@ -772,7 +762,7 @@ class SimilarChecker(BaseRawFileChecker, Similar): "default": DEFAULT_MIN_SIMILARITY_LINE, "type": "int", "metavar": "", - "help": "Minimum lines number of a similarity.", + "help": MIN_SIMILARITY_HELP, }, ), ( @@ -781,7 +771,7 @@ class SimilarChecker(BaseRawFileChecker, Similar): "default": True, "type": "yn", "metavar": "", - "help": "Comments are removed from the similarity computation", + "help": IGNORE_COMMENTS_HELP, }, ), ( @@ -790,7 +780,7 @@ class SimilarChecker(BaseRawFileChecker, Similar): "default": True, "type": "yn", "metavar": "", - "help": "Docstrings are removed from the similarity computation", + "help": IGNORE_DOCSTRINGS_HELP, }, ), ( @@ -799,7 +789,7 @@ class SimilarChecker(BaseRawFileChecker, Similar): "default": True, "type": "yn", "metavar": "", - "help": "Imports are removed from the similarity computation", + "help": IGNORE_IMPORTS_HELP, }, ), ( @@ -808,16 +798,15 @@ class SimilarChecker(BaseRawFileChecker, Similar): "default": True, "type": "yn", "metavar": "", - "help": "Signatures are removed from the similarity computation", + "help": IGNORE_SIGNATURES_HELP, }, ), ) - # reports reports = (("RP0801", "Duplication", report_similarities),) def __init__(self, linter: PyLinter) -> None: BaseRawFileChecker.__init__(self, linter) - Similar.__init__( + Symilar.__init__( self, min_lines=self.linter.config.min_similarity_lines, ignore_comments=self.linter.config.ignore_comments, @@ -874,7 +863,7 @@ def close(self) -> None: def get_map_data(self) -> list[LineSet]: """Passthru override.""" - return Similar.get_map_data(self) + return Symilar.get_map_data(self) def reduce_map_data(self, linter: PyLinter, data: list[list[LineSet]]) -> None: """Reduces and recombines data into a format that we can report on. @@ -883,75 +872,61 @@ def reduce_map_data(self, linter: PyLinter, data: list[list[LineSet]]) -> None: Calls self.close() to actually calculate and report duplicate code. """ - Similar.combine_mapreduce_data(self, linesets_collection=data) + Symilar.combine_mapreduce_data(self, linesets_collection=data) self.close() def register(linter: PyLinter) -> None: - linter.register_checker(SimilarChecker(linter)) - - -def usage(status: int = 0) -> NoReturn: - """Display command line usage information.""" - print("finds copy pasted blocks in a set of files") - print() - print( - "Usage: symilar [-d|--duplicates min_duplicated_lines] \ -[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] [--ignore-signatures] file1..." - ) - sys.exit(status) + linter.register_checker(SimilaritiesChecker(linter)) def Run(argv: Sequence[str] | None = None) -> NoReturn: """Standalone command line access point.""" - if argv is None: - argv = sys.argv[1:] - - s_opts = "hd:i:" - l_opts = [ - "help", - "duplicates=", - "ignore-comments", - "ignore-imports", - "ignore-docstrings", - "ignore-signatures", - ] - min_lines = DEFAULT_MIN_SIMILARITY_LINE - ignore_comments = False - ignore_docstrings = False - ignore_imports = False - ignore_signatures = False - try: - opts, args = getopt(list(argv), s_opts, l_opts) - except GetoptError as e: - print(e) - usage(2) - for opt, val in opts: - if opt in {"-d", "--duplicates"}: - try: - min_lines = int(val) - except ValueError as e: - print(e) - usage(2) - elif opt in {"-h", "--help"}: - usage() - elif opt in {"-i", "--ignore-comments"}: - ignore_comments = True - elif opt in {"--ignore-docstrings"}: - ignore_docstrings = True - elif opt in {"--ignore-imports"}: - ignore_imports = True - elif opt in {"--ignore-signatures"}: - ignore_signatures = True - if not args: - usage(1) - sim = Similar( - min_lines, ignore_comments, ignore_docstrings, ignore_imports, ignore_signatures + parser = argparse.ArgumentParser( + prog="symilar", description="Finds copy pasted blocks in a set of files." + ) + parser.add_argument("files", nargs="+") + parser.add_argument( + "-d", + "--duplicates", + type=int, + default=DEFAULT_MIN_SIMILARITY_LINE, + help=SimilaritiesChecker.MIN_SIMILARITY_HELP, + ) + parser.add_argument( + "-i", + "--ignore-comments", + action="store_true", + help=SimilaritiesChecker.IGNORE_COMMENTS_HELP, + ) + parser.add_argument( + "--ignore-docstrings", + action="store_true", + help=SimilaritiesChecker.IGNORE_DOCSTRINGS_HELP, + ) + parser.add_argument( + "--ignore-imports", + action="store_true", + help=SimilaritiesChecker.IGNORE_IMPORTS_HELP, + ) + parser.add_argument( + "--ignore-signatures", + action="store_true", + help=SimilaritiesChecker.IGNORE_SIGNATURES_HELP, + ) + parsed_args = parser.parse_args(args=argv) + similar_runner = Symilar( + min_lines=parsed_args.duplicates, + ignore_comments=parsed_args.ignore_comments, + ignore_docstrings=parsed_args.ignore_docstrings, + ignore_imports=parsed_args.ignore_imports, + ignore_signatures=parsed_args.ignore_signatures, ) - for filename in args: + for filename in parsed_args.files: with open(filename, encoding="utf-8") as stream: - sim.append_stream(filename, stream) - sim.run() + similar_runner.append_stream(filename, stream) + similar_runner.run() + # the sys exit must be kept because of the unit tests that rely on it sys.exit(0) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 9e6465531d..bc7ddfc2a4 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -1419,9 +1419,27 @@ def _check_argument_order( 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: nodes.Call) -> None: - if len(node.args) != 2: - # isinstance called with wrong number of args + def _check_isinstance_args(self, node: nodes.Call, callable_name: str) -> None: + if len(node.args) > 2: + # for when isinstance called with too many args + self.add_message( + "too-many-function-args", + node=node, + args=(callable_name,), + confidence=HIGH, + ) + elif len(node.args) < 2: + # NOTE: Hard-coding the parameters for `isinstance` is fragile, + # but as noted elsewhere, built-in functions do not provide + # argument info, making this necessary for now. + parameters = ("'_obj'", "'__class_or_tuple'") + for parameter in parameters[len(node.args) :]: + self.add_message( + "no-value-for-parameter", + node=node, + args=(parameter, callable_name), + confidence=HIGH, + ) return second_arg = node.args[1] @@ -1451,7 +1469,7 @@ def visit_call(self, node: nodes.Call) -> None: if called.args.args is None: if called.name == "isinstance": # Verify whether second argument of isinstance is a valid type - self._check_isinstance_args(node) + self._check_isinstance_args(node, callable_name) # Built-in functions have no argument information. return @@ -1554,7 +1572,9 @@ def visit_call(self, node: nodes.Call) -> None: elif not overload_function: # Too many positional arguments. self.add_message( - "too-many-function-args", node=node, args=(callable_name,) + "too-many-function-args", + node=node, + args=(callable_name,), ) break @@ -2135,6 +2155,7 @@ def visit_subscript(self, node: nodes.Subscript) -> None: msg = "unsupported-delete-operation" if isinstance(node.value, nodes.SetComp): + # pylint: disable-next=possibly-used-before-assignment self.add_message(msg, args=node.value.as_string(), node=node.value) return @@ -2197,7 +2218,7 @@ def _check_await_outside_coroutine(self, node: nodes.Await) -> None: while not isinstance(node_scope, nodes.Module): if isinstance(node_scope, nodes.AsyncFunctionDef): return - if isinstance(node_scope, nodes.FunctionDef): + if isinstance(node_scope, (nodes.FunctionDef, nodes.Lambda)): break node_scope = node_scope.parent.scope() self.add_message("await-outside-async", node=node) diff --git a/pylint/checkers/unsupported_version.py b/pylint/checkers/unsupported_version.py index 64f2630d8b..53b5f63fba 100644 --- a/pylint/checkers/unsupported_version.py +++ b/pylint/checkers/unsupported_version.py @@ -18,6 +18,7 @@ safe_infer, uninferable_final_decorators, ) +from pylint.interfaces import HIGH if TYPE_CHECKING: from pylint.lint import PyLinter @@ -42,6 +43,30 @@ class UnsupportedVersionChecker(BaseChecker): "Used when the py-version set by the user is lower than 3.8 and pylint encounters " "a ``typing.final`` decorator.", ), + "W2603": ( + "Exception groups are not supported by all versions included in the py-version setting", + "using-exception-groups-in-unsupported-version", + "Used when the py-version set by the user is lower than 3.11 and pylint encounters " + "``except*`` or `ExceptionGroup``.", + ), + "W2604": ( + "Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting", + "using-generic-type-syntax-in-unsupported-version", + "Used when the py-version set by the user is lower than 3.12 and pylint encounters " + "generic type syntax.", + ), + "W2605": ( + "Assignment expression is not supported by all versions included in the py-version setting", + "using-assignment-expression-in-unsupported-version", + "Used when the py-version set by the user is lower than 3.8 and pylint encounters " + "an assignment expression (walrus) operator.", + ), + "W2606": ( + "Positional-only arguments are not supported by all versions included in the py-version setting", + "using-positional-only-args-in-unsupported-version", + "Used when the py-version set by the user is lower than 3.8 and pylint encounters " + "positional-only arguments.", + ), } def open(self) -> None: @@ -49,12 +74,34 @@ def open(self) -> None: py_version = self.linter.config.py_version self._py36_plus = py_version >= (3, 6) self._py38_plus = py_version >= (3, 8) + self._py311_plus = py_version >= (3, 11) + self._py312_plus = py_version >= (3, 12) @only_required_for_messages("using-f-string-in-unsupported-version") def visit_joinedstr(self, node: nodes.JoinedStr) -> None: """Check f-strings.""" if not self._py36_plus: - self.add_message("using-f-string-in-unsupported-version", node=node) + self.add_message( + "using-f-string-in-unsupported-version", node=node, confidence=HIGH + ) + + @only_required_for_messages("using-assignment-expression-in-unsupported-version") + def visit_namedexpr(self, node: nodes.JoinedStr) -> None: + if not self._py38_plus: + self.add_message( + "using-assignment-expression-in-unsupported-version", + node=node, + confidence=HIGH, + ) + + @only_required_for_messages("using-positional-only-args-in-unsupported-version") + def visit_arguments(self, node: nodes.Arguments) -> None: + if not self._py38_plus and node.posonlyargs: + self.add_message( + "using-positional-only-args-in-unsupported-version", + node=node, + confidence=HIGH, + ) @only_required_for_messages("using-final-decorator-in-unsupported-version") def visit_decorators(self, node: nodes.Decorators) -> None: @@ -76,7 +123,72 @@ def _check_typing_final(self, node: nodes.Decorators) -> None: for decorator in decorators or uninferable_final_decorators(node): self.add_message( - "using-final-decorator-in-unsupported-version", node=decorator + "using-final-decorator-in-unsupported-version", + node=decorator, + confidence=HIGH, + ) + + @only_required_for_messages("using-exception-groups-in-unsupported-version") + def visit_trystar(self, node: nodes.TryStar) -> None: + if not self._py311_plus: + self.add_message( + "using-exception-groups-in-unsupported-version", + node=node, + confidence=HIGH, + ) + + @only_required_for_messages("using-exception-groups-in-unsupported-version") + def visit_excepthandler(self, node: nodes.ExceptHandler) -> None: + if ( + not self._py311_plus + and isinstance(node.type, nodes.Name) + and node.type.name == "ExceptionGroup" + ): + self.add_message( + "using-exception-groups-in-unsupported-version", + node=node, + confidence=HIGH, + ) + + @only_required_for_messages("using-exception-groups-in-unsupported-version") + def visit_raise(self, node: nodes.Raise) -> None: + if ( + not self._py311_plus + and isinstance(node.exc, nodes.Call) + and isinstance(node.exc.func, nodes.Name) + and node.exc.func.name == "ExceptionGroup" + ): + self.add_message( + "using-exception-groups-in-unsupported-version", + node=node, + confidence=HIGH, + ) + + @only_required_for_messages("using-generic-type-syntax-in-unsupported-version") + def visit_typealias(self, node: nodes.TypeAlias) -> None: + if not self._py312_plus: + self.add_message( + "using-generic-type-syntax-in-unsupported-version", + node=node, + confidence=HIGH, + ) + + @only_required_for_messages("using-generic-type-syntax-in-unsupported-version") + def visit_typevar(self, node: nodes.TypeVar) -> None: + if not self._py312_plus: + self.add_message( + "using-generic-type-syntax-in-unsupported-version", + node=node, + confidence=HIGH, + ) + + @only_required_for_messages("using-generic-type-syntax-in-unsupported-version") + def visit_typevartuple(self, node: nodes.TypeVarTuple) -> None: + if not self._py312_plus: + self.add_message( + "using-generic-type-syntax-in-unsupported-version", + node=node, + confidence=HIGH, ) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 34c744d094..bfc4bc61da 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -6,18 +6,18 @@ from __future__ import annotations +import _string import builtins import fnmatch import itertools import numbers import re import string -from collections.abc import Iterable, Iterator +from collections.abc import Callable, Iterable, Iterator from functools import lru_cache, partial from re import Match -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar -import _string import astroid.objects from astroid import TooManyLevelsError, nodes, util from astroid.context import InferenceContext @@ -1843,10 +1843,7 @@ def is_sys_guard(node: nodes.If) -> bool: """Return True if IF stmt is a sys.version_info guard. >>> import sys - >>> if sys.version_info > (3, 8): - >>> from typing import Literal - >>> else: - >>> from typing_extensions import Literal + >>> from typing import Literal """ if isinstance(node.test, nodes.Compare): value = node.test.left diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 0491b3b61e..35d6d7ce0c 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -32,7 +32,7 @@ is_sys_guard, overridden_method, ) -from pylint.constants import PY39_PLUS, TYPING_NEVER, TYPING_NORETURN +from pylint.constants import TYPING_NEVER, TYPING_NORETURN from pylint.interfaces import CONTROL_FLOW, HIGH, INFERENCE, INFERENCE_FAILURE from pylint.typing import MessageDefinitionTuple @@ -249,9 +249,7 @@ class C: ... return frame.lineno < defframe.lineno # type: ignore[no-any-return] -def _infer_name_module( - node: nodes.Import, name: str -) -> Generator[InferenceResult, None, None]: +def _infer_name_module(node: nodes.Import, name: str) -> Generator[InferenceResult]: context = astroid.context.InferenceContext() context.lookupname = name return node.infer(context, asname=False) # type: ignore[no-any-return] @@ -1770,6 +1768,11 @@ def _should_node_be_skipped( if utils.is_ancestor_name(consumer.node, node) or ( not is_start_index and self._ignore_class_scope(node) ): + if any( + node.name == param.name.name for param in consumer.node.type_params + ): + return False + return True # Ignore inner class scope for keywords in class definition @@ -1980,7 +1983,9 @@ def _check_consumer( ) return (VariableVisitConsumerAction.RETURN, found_nodes) - elif isinstance(defstmt, nodes.ClassDef): + elif ( + isinstance(defstmt, nodes.ClassDef) and defnode not in defframe.type_params + ): return self._is_first_level_self_reference(node, defstmt, found_nodes) elif isinstance(defnode, nodes.NamedExpr): @@ -2349,23 +2354,6 @@ def _is_variable_violation( and defnode.col_offset < node.col_offset ) or (defnode.lineno < node.lineno) - or ( - # Issue in the `ast` module until py39 - # Nodes in a multiline string have the same lineno - # Could be false-positive without check - not PY39_PLUS - and defnode.lineno == node.lineno - and isinstance( - defstmt, - ( - nodes.Assign, - nodes.AnnAssign, - nodes.AugAssign, - nodes.Return, - ), - ) - and isinstance(defstmt.value, nodes.JoinedStr) - ) ) ): # Relation of a name to the same name in a named expression @@ -2376,6 +2364,13 @@ def _is_variable_violation( maybe_before_assign = defnode.value is node or any( anc is defnode.value for anc in node.node_ancestors() ) + elif ( + isinstance(defframe, nodes.ClassDef) + and defnode in defframe.type_params + ): + # Generic on parent class: + # class Child[_T](Parent[_T]) + maybe_before_assign = False return maybe_before_assign, annotation_return, use_outer_definition @@ -2630,6 +2625,7 @@ def _loopvar_name(self, node: astroid.Name) -> None: ): return # TODO: 4.0: Consider using utils.is_terminating_func + # after merging it with RefactoringChecker._is_function_def_never_returning if isinstance(else_stmt, nodes.Expr) and isinstance( else_stmt.value, nodes.Call ): @@ -3374,6 +3370,19 @@ def visit_subscript(self, node: nodes.Subscript) -> None: self._check_potential_index_error(node, inferred_slice) + def _inferred_iterable_length(self, iterable: nodes.Tuple | nodes.List) -> int: + length = 0 + for elt in iterable.elts: + if not isinstance(elt, nodes.Starred): + length += 1 + continue + unpacked = utils.safe_infer(elt.value) + if isinstance(unpacked, nodes.BaseContainer): + length += len(unpacked.elts) + else: + length += 1 + return length + def _check_potential_index_error( self, node: nodes.Subscript, inferred_slice: nodes.NodeNG | None ) -> None: @@ -3387,7 +3396,7 @@ def _check_potential_index_error( # If the node.value is a Tuple or List without inference it is defined in place if isinstance(node.value, (nodes.Tuple, nodes.List)): # Add 1 because iterables are 0-indexed - if len(node.value.elts) < inferred_slice.value + 1: + if self._inferred_iterable_length(node.value) < inferred_slice.value + 1: self.add_message( "potential-index-error", node=node, confidence=INFERENCE ) diff --git a/pylint/config/argument.py b/pylint/config/argument.py index 2d2a46a3fd..a515a942b4 100644 --- a/pylint/config/argument.py +++ b/pylint/config/argument.py @@ -13,9 +13,10 @@ import os import pathlib import re -from collections.abc import Callable +from collections.abc import Callable, Sequence from glob import glob -from typing import Any, Literal, Pattern, Sequence, Tuple, Union +from re import Pattern +from typing import Any, Literal, Union from pylint import interfaces from pylint import utils as pylint_utils @@ -30,7 +31,7 @@ Pattern[str], Sequence[str], Sequence[Pattern[str]], - Tuple[int, ...], + tuple[int, ...], ] """List of possible argument types.""" @@ -103,7 +104,7 @@ def _py_version_transformer(value: str) -> tuple[int, ...]: def _regex_transformer(value: str) -> Pattern[str]: - """Return `re.compile(value)`.""" + """Prevents 're.error' from propagating and crash pylint.""" try: return re.compile(value) except re.error as e: @@ -124,7 +125,7 @@ def _regexp_paths_csv_transfomer(value: str) -> Sequence[Pattern[str]]: patterns: list[Pattern[str]] = [] for pattern in _csv_transformer(value): patterns.append( - re.compile( + _regex_transformer( str(pathlib.PureWindowsPath(pattern)).replace("\\", "\\\\") + "|" + pathlib.PureWindowsPath(pattern).as_posix() diff --git a/pylint/config/config_file_parser.py b/pylint/config/config_file_parser.py index efc085e590..4ceed28d6e 100644 --- a/pylint/config/config_file_parser.py +++ b/pylint/config/config_file_parser.py @@ -10,7 +10,7 @@ import os import sys from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Tuple +from typing import TYPE_CHECKING from pylint.config.utils import _parse_rich_type_value @@ -22,7 +22,7 @@ if TYPE_CHECKING: from pylint.lint import PyLinter -PylintConfigFileData = Tuple[Dict[str, str], List[str]] +PylintConfigFileData = tuple[dict[str, str], list[str]] class _RawConfParser: diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index 346393cf9a..7e53e77207 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -22,7 +22,7 @@ Path(".pylintrc.toml"), ) PYPROJECT_NAME = Path("pyproject.toml") -CONFIG_NAMES = (*RC_NAMES, PYPROJECT_NAME, Path("setup.cfg")) +CONFIG_NAMES = (*RC_NAMES, PYPROJECT_NAME, Path("setup.cfg"), Path("tox.ini")) def _find_pyproject() -> Path: @@ -55,13 +55,16 @@ def _toml_has_config(path: Path | str) -> bool: return "pylint" in content.get("tool", []) -def _cfg_has_config(path: Path | str) -> bool: +def _cfg_or_ini_has_config(path: Path | str) -> bool: parser = configparser.ConfigParser() try: parser.read(path, encoding="utf-8") except configparser.Error: return False - return any(section.startswith("pylint.") for section in parser.sections()) + return any( + section == "pylint" or section.startswith("pylint.") + for section in parser.sections() + ) def _yield_default_files() -> Iterator[Path]: @@ -71,7 +74,10 @@ def _yield_default_files() -> Iterator[Path]: if config_name.is_file(): if config_name.suffix == ".toml" and not _toml_has_config(config_name): continue - if config_name.suffix == ".cfg" and not _cfg_has_config(config_name): + if config_name.suffix in { + ".cfg", + ".ini", + } and not _cfg_or_ini_has_config(config_name): continue yield config_name.resolve() diff --git a/pylint/constants.py b/pylint/constants.py index f147e5189a..0ba20162a1 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -14,8 +14,6 @@ from pylint.__pkginfo__ import __version__ from pylint.typing import MessageTypesFullName -PY38_PLUS = sys.version_info[:2] >= (3, 8) -PY39_PLUS = sys.version_info[:2] >= (3, 9) PY310_PLUS = sys.version_info[:2] >= (3, 10) PY311_PLUS = sys.version_info[:2] >= (3, 11) PY312_PLUS = sys.version_info[:2] >= (3, 12) diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py index 622601c75f..00d539500c 100644 --- a/pylint/extensions/code_style.py +++ b/pylint/extensions/code_style.py @@ -5,7 +5,7 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Tuple, Type, cast +from typing import TYPE_CHECKING, cast from astroid import nodes @@ -152,7 +152,7 @@ def _check_dict_consider_namedtuple_dataclass(self, node: nodes.Dict) -> None: if len(node.items) > 1 and all( isinstance(dict_value, nodes.Dict) for _, dict_value in node.items ): - KeyTupleT = Tuple[Type[nodes.NodeNG], str] + KeyTupleT = tuple[type[nodes.NodeNG], str] # Makes sure all keys are 'Const' string nodes keys_checked: set[KeyTupleT] = set() diff --git a/pylint/extensions/docparams.py b/pylint/extensions/docparams.py index 5d672131a2..b19560b7fb 100644 --- a/pylint/extensions/docparams.py +++ b/pylint/extensions/docparams.py @@ -321,6 +321,9 @@ def visit_raise(self, node: nodes.Raise) -> None: for found_exc in found_excs_class_names: if found_exc == expected.name: break + if found_exc == "error" and expected.name == "PatternError": + # Python 3.13: re.error aliases re.PatternError + break if any(found_exc == ancestor.name for ancestor in expected.ancestors()): break else: @@ -653,7 +656,7 @@ def _add_raise_message( """Adds a message on :param:`node` for the missing exception type. :param missing_exceptions: A list of missing exception types. - :param node: The node show the message on. + :param node: The node to show the message on. """ if node.is_abstract(): try: diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 2956465cf6..f9ef83babb 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -85,6 +85,7 @@ class DeprecatedTypingAliasMsg(NamedTuple): parent_subscript: bool = False +# pylint: disable-next=too-many-instance-attributes class TypingChecker(BaseChecker): """Find issue specifically related to type annotations.""" @@ -130,6 +131,12 @@ class TypingChecker(BaseChecker): "Duplicated type arguments will be skipped by `mypy` tool, therefore should be " "removed to avoid confusion.", ), + "R6007": ( + "Type `%s` has unnecessary default type args. Change it to `%s`.", + "unnecessary-default-type-args", + "Emitted when types have default type args which can be omitted. " + "Mainly used for `typing.Generator` and `typing.AsyncGenerator`.", + ), } options = ( ( @@ -174,6 +181,7 @@ def open(self) -> None: self._py37_plus = py_version >= (3, 7) self._py39_plus = py_version >= (3, 9) self._py310_plus = py_version >= (3, 10) + self._py313_plus = py_version >= (3, 13) self._should_check_typing_alias = self._py39_plus or ( self._py37_plus and self.linter.config.runtime_typing is False @@ -248,6 +256,33 @@ def visit_annassign(self, node: nodes.AnnAssign) -> None: self._check_union_types(types, node) + @only_required_for_messages("unnecessary-default-type-args") + def visit_subscript(self, node: nodes.Subscript) -> None: + inferred = safe_infer(node.value) + if ( # pylint: disable=too-many-boolean-expressions + isinstance(inferred, nodes.ClassDef) + and ( + inferred.qname() in {"typing.Generator", "typing.AsyncGenerator"} + and self._py313_plus + or inferred.qname() + in {"_collections_abc.Generator", "_collections_abc.AsyncGenerator"} + ) + and isinstance(node.slice, nodes.Tuple) + and all( + (isinstance(el, nodes.Const) and el.value is None) + for el in node.slice.elts[1:] + ) + ): + suggested_str = ( + f"{node.value.as_string()}[{node.slice.elts[0].as_string()}]" + ) + self.add_message( + "unnecessary-default-type-args", + args=(node.as_string(), suggested_str), + node=node, + confidence=HIGH, + ) + @staticmethod def _is_deprecated_union_annotation( annotation: nodes.NodeNG, union_name: str diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 04e7018843..a7d31dea6b 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -33,7 +33,7 @@ def discover_package_path(modulepath: str, source_roots: Sequence[str]) -> str: # Look for a source root that contains the module directory for source_root in source_roots: source_root = os.path.realpath(os.path.expanduser(source_root)) - if os.path.commonpath([source_root, dirname]) == source_root: + if os.path.commonpath([source_root, dirname]) in [dirname, source_root]: return source_root # Fall back to legacy discovery by looking for __init__.py upwards as @@ -122,7 +122,7 @@ def expand_modules( ) except ImportError: # Might not be acceptable, don't crash. - is_namespace = False + is_namespace = not os.path.exists(filepath) is_directory = os.path.isdir(something) else: is_namespace = modutils.is_namespace(spec) diff --git a/pylint/lint/message_state_handler.py b/pylint/lint/message_state_handler.py index 2ddd7d4db3..5c4498af96 100644 --- a/pylint/lint/message_state_handler.py +++ b/pylint/lint/message_state_handler.py @@ -18,7 +18,7 @@ ) from pylint.interfaces import HIGH from pylint.message import MessageDefinition -from pylint.typing import ManagedMessage +from pylint.typing import ManagedMessage, MessageDefinitionTuple from pylint.utils.pragma_parser import ( OPTION_PO, InvalidPragmaError, @@ -37,6 +37,11 @@ class _MessageStateHandler: def __init__(self, linter: PyLinter) -> None: self.linter = linter + self.default_enabled_messages: dict[str, MessageDefinitionTuple] = { + k: v + for k, v in self.linter.msgs.items() + if len(v) == 3 or v[3].get("default_enabled", True) + } self._msgs_state: dict[str, bool] = {} self._options_methods = { "enable": self.enable, @@ -84,6 +89,14 @@ def _get_messages_to_set( message_definitions.extend( self._get_messages_to_set(_msgid, enable, ignore_unknown) ) + if not enable: + # "all" should not disable pylint's own warnings + message_definitions = list( + filter( + lambda m: m.msgid not in self.default_enabled_messages, + message_definitions, + ) + ) return message_definitions # msgid is a category? diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index dce95e0036..cc4605b96a 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -48,7 +48,6 @@ report_total_messages_stats, ) from pylint.lint.utils import ( - _is_relative_to, augmented_sys_path, get_fatal_error_message, prepare_crash_report, @@ -920,7 +919,7 @@ def _get_namespace_for_file( self, filepath: Path, namespaces: DirectoryNamespaceDict ) -> argparse.Namespace | None: for directory in namespaces: - if _is_relative_to(filepath, directory): + if Path.is_relative_to(filepath, directory): namespace = self._get_namespace_for_file( filepath, namespaces[directory][1] ) diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 1a8d594a04..2bbbb337b9 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -175,9 +175,13 @@ def __init__( sys.exit(code) return - # Display help if there are no files to lint or no checks enabled - if not args or len(linter.config.disable) == len( - linter.msgs_store._messages_definitions + # Display help if there are no files to lint or only internal checks enabled (`--disable=all`) + disable_all_msg_set = set( + msg.symbol for msg in linter.msgs_store.messages + ) - set(msg[1] for msg in linter.default_enabled_messages.values()) + if not args or ( + len(linter.config.enable) == 0 + and set(linter.config.disable) == disable_all_msg_set ): print("No files to lint: exiting.") sys.exit(32) diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index a7fbfd0bc3..c5487a8c6d 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -133,16 +133,3 @@ def augmented_sys_path(additional_paths: Sequence[str]) -> Iterator[None]: yield finally: sys.path[:] = original - - -def _is_relative_to(self: Path, *other: Path) -> bool: - """Checks if self is relative to other. - - Backport of pathlib.Path.is_relative_to for Python <3.9 - TODO: py39: Remove this backport and use stdlib function. - """ - try: - self.relative_to(*other) - return True - except ValueError: - return False diff --git a/pylint/message/_deleted_message_ids.py b/pylint/message/_deleted_message_ids.py index 60289e8053..149a800b7c 100644 --- a/pylint/message/_deleted_message_ids.py +++ b/pylint/message/_deleted_message_ids.py @@ -4,7 +4,7 @@ from __future__ import annotations -from functools import lru_cache +from functools import cache from typing import NamedTuple @@ -131,7 +131,7 @@ class DeletedMessage(NamedTuple): } -@lru_cache(maxsize=None) +@cache def is_deleted_symbol(symbol: str) -> str | None: """Return the explanation for removal if the message was removed.""" for explanation, deleted_messages in DELETED_MESSAGES_IDS.items(): @@ -143,7 +143,7 @@ def is_deleted_symbol(symbol: str) -> str | None: return None -@lru_cache(maxsize=None) +@cache def is_deleted_msgid(msgid: str) -> str | None: """Return the explanation for removal if the message was removed.""" for explanation, deleted_messages in DELETED_MESSAGES_IDS.items(): @@ -155,7 +155,7 @@ def is_deleted_msgid(msgid: str) -> str | None: return None -@lru_cache(maxsize=None) +@cache def is_moved_symbol(symbol: str) -> str | None: """Return the explanation for moving if the message was moved to extensions.""" for explanation, moved_messages in MOVED_TO_EXTENSIONS.items(): @@ -167,7 +167,7 @@ def is_moved_symbol(symbol: str) -> str | None: return None -@lru_cache(maxsize=None) +@cache def is_moved_msgid(msgid: str) -> str | None: """Return the explanation for moving if the message was moved to extensions.""" for explanation, moved_messages in MOVED_TO_EXTENSIONS.items(): diff --git a/pylint/message/message_definition_store.py b/pylint/message/message_definition_store.py index cf271d7ffc..d56308541a 100644 --- a/pylint/message/message_definition_store.py +++ b/pylint/message/message_definition_store.py @@ -5,9 +5,9 @@ from __future__ import annotations import collections -import functools import sys from collections.abc import Sequence, ValuesView +from functools import cache from typing import TYPE_CHECKING from pylint.exceptions import UnknownMessageError @@ -58,9 +58,7 @@ def register_message(self, message: MessageDefinition) -> None: # and the arguments are relatively small we do not run the # risk of creating a large memory leak. # See discussion in: https://github.com/pylint-dev/pylint/pull/5673 - @functools.lru_cache( # pylint: disable=method-cache-max-size-none # noqa: B019 - maxsize=None - ) + @cache # pylint: disable=method-cache-max-size-none # noqa: B019 def get_message_definitions(self, msgid_or_symbol: str) -> list[MessageDefinition]: """Returns the Message definition for either a numeric or symbolic id. diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index 88aea482ed..59a2f59560 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -84,7 +84,7 @@ def add_class(self, node: nodes.ClassDef) -> None: def get_ancestors( self, node: nodes.ClassDef, level: int - ) -> Generator[nodes.ClassDef, None, None]: + ) -> Generator[nodes.ClassDef]: """Return ancestor nodes of a class node.""" if level == 0: return @@ -95,7 +95,7 @@ def get_ancestors( def get_associated( self, klass_node: nodes.ClassDef, level: int - ) -> Generator[nodes.ClassDef, None, None]: + ) -> Generator[nodes.ClassDef]: """Return associated nodes of a class node.""" if level == 0: return diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 23ccfa6f30..8825363fa7 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -13,7 +13,8 @@ import os import traceback from abc import ABC, abstractmethod -from typing import Callable, Optional +from collections.abc import Callable +from typing import Optional import astroid from astroid import nodes diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index bdd28dc7c3..5ad92d3231 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -11,7 +11,8 @@ import shutil import subprocess import sys -from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Union +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Optional, Union import astroid from astroid import nodes @@ -22,9 +23,9 @@ _CallbackT = Callable[ [nodes.NodeNG], - Union[Tuple[ClassDiagram], Tuple[PackageDiagram, ClassDiagram], None], + Union[tuple[ClassDiagram], tuple[PackageDiagram, ClassDiagram], None], ] - _CallbackTupleT = Tuple[Optional[_CallbackT], Optional[_CallbackT]] + _CallbackTupleT = tuple[Optional[_CallbackT], Optional[_CallbackT]] RCFILE = ".pyreverserc" diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index 95d45ba919..071879ca1e 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -6,7 +6,7 @@ import collections from collections.abc import MutableSequence -from typing import TYPE_CHECKING, DefaultDict, List, Tuple +from typing import TYPE_CHECKING from pylint.exceptions import EmptyReportError from pylint.reporters.ureports.nodes import Section @@ -17,7 +17,9 @@ from pylint.checkers import BaseChecker from pylint.lint.pylinter import PyLinter -ReportsDict = DefaultDict["BaseChecker", List[Tuple[str, str, ReportsCallable]]] +ReportsDict = collections.defaultdict[ + "BaseChecker", list[tuple[str, str, ReportsCallable]] +] class ReportsHandlerMixIn: diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 0e3577199a..894207ad7d 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -15,7 +15,7 @@ import sys import warnings from dataclasses import asdict, fields -from typing import TYPE_CHECKING, Dict, NamedTuple, TextIO +from typing import TYPE_CHECKING, NamedTuple, TextIO from pylint.message import Message from pylint.reporters import BaseReporter @@ -65,7 +65,7 @@ def _colorize_ansi(self, msg: str) -> str: return msg -ColorMappingDict = Dict[str, MessageStyle] +ColorMappingDict = dict[str, MessageStyle] TITLE_UNDERLINES = ["", "=", "-", "."] diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index 59443996db..c414865121 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -9,8 +9,8 @@ from __future__ import annotations -from collections.abc import Iterable, Iterator -from typing import Any, Callable, TypeVar +from collections.abc import Callable, Iterable, Iterator +from typing import Any, TypeVar from pylint.reporters.ureports.base_writer import BaseWriter diff --git a/pylint/testutils/_primer/primer.py b/pylint/testutils/_primer/primer.py index 87c37d3fe4..5fd83f442e 100644 --- a/pylint/testutils/_primer/primer.py +++ b/pylint/testutils/_primer/primer.py @@ -105,6 +105,7 @@ def __init__(self, primer_directory: Path, json_path: Path) -> None: command_class = RunCommand elif self.config.command == "compare": command_class = CompareCommand + # pylint: disable-next=possibly-used-before-assignment self.command = command_class(self.primer_directory, self.packages, self.config) def run(self) -> None: diff --git a/pylint/testutils/_primer/primer_command.py b/pylint/testutils/_primer/primer_command.py index 817c1a0d31..01c2bed368 100644 --- a/pylint/testutils/_primer/primer_command.py +++ b/pylint/testutils/_primer/primer_command.py @@ -7,7 +7,7 @@ import abc import argparse from pathlib import Path -from typing import Dict, TypedDict +from typing import TypedDict from pylint.reporters.json_reporter import OldJsonExport from pylint.testutils._primer import PackageToLint @@ -18,7 +18,7 @@ class PackageData(TypedDict): messages: list[OldJsonExport] -PackageMessages = Dict[str, PackageData] +PackageMessages = dict[str, PackageData] class PrimerCommand: diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py index 3ffbbc44ad..951f38c0b9 100644 --- a/pylint/testutils/checker_test_case.py +++ b/pylint/testutils/checker_test_case.py @@ -10,7 +10,6 @@ from astroid import nodes -from pylint.constants import IS_PYPY, PY39_PLUS from pylint.testutils.global_test_linter import linter from pylint.testutils.output_line import MessageTest from pylint.testutils.unittest_linter import UnittestLinter @@ -41,7 +40,7 @@ def assertNoMessages(self) -> Iterator[None]: @contextlib.contextmanager def assertAddsMessages( self, *messages: MessageTest, ignore_position: bool = False - ) -> Generator[None, None, None]: + ) -> Generator[None]: """Assert that exactly the given method adds the given messages. The list of messages must exactly match *all* the messages added by the @@ -76,9 +75,8 @@ def assertAddsMessages( assert expected_msg.line == gotten_msg.line, msg assert expected_msg.col_offset == gotten_msg.col_offset, msg - if not IS_PYPY or PY39_PLUS: - assert expected_msg.end_line == gotten_msg.end_line, msg - assert expected_msg.end_col_offset == gotten_msg.end_col_offset, msg + assert expected_msg.end_line == gotten_msg.end_line, msg + assert expected_msg.end_col_offset == gotten_msg.end_col_offset, msg def walk(self, node: nodes.NodeNG) -> None: """Recursive walk on the given node.""" diff --git a/pylint/testutils/configuration_test.py b/pylint/testutils/configuration_test.py index a38c8646bb..ce2239e5c2 100644 --- a/pylint/testutils/configuration_test.py +++ b/pylint/testutils/configuration_test.py @@ -11,15 +11,14 @@ import logging import unittest from pathlib import Path -from typing import Any, Dict -from unittest.mock import Mock +from typing import Any from pylint.lint import Run # We use Any in this typing because the configuration contains real objects and constants # that could be a lot of things. ConfigurationValue = Any -PylintConfiguration = Dict[str, ConfigurationValue] +PylintConfiguration = dict[str, ConfigurationValue] def get_expected_or_default( @@ -135,18 +134,15 @@ def get_expected_output( def run_using_a_configuration_file( configuration_path: Path | str, file_to_lint: str = __file__ -) -> tuple[Mock, Mock, Run]: +) -> Run: """Simulate a run with a configuration without really launching the checks.""" configuration_path = str(configuration_path) args = ["--rcfile", configuration_path, file_to_lint] - # We do not capture the `SystemExit` as then the `runner` variable - # would not be accessible outside the `with` block. - with unittest.mock.patch("sys.exit") as mocked_exit: - # Do not actually run checks, that could be slow. We don't mock - # `PyLinter.check`: it calls `PyLinter.initialize` which is - # needed to properly set up messages inclusion/exclusion - # in `_msg_states`, used by `is_message_enabled`. - check = "pylint.lint.pylinter.check_parallel" - with unittest.mock.patch(check) as mocked_check_parallel: - runner = Run(args) - return mocked_exit, mocked_check_parallel, runner + # Do not actually run checks, that could be slow. We don't mock + # `PyLinter.check`: it calls `PyLinter.initialize` which is + # needed to properly set up messages inclusion/exclusion + # in `_msg_states`, used by `is_message_enabled`. + check = "pylint.lint.pylinter.check_parallel" + with unittest.mock.patch(check): + runner = Run(args, exit=False) + return runner diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 48ee5a0b2f..37839c8908 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -11,15 +11,13 @@ from collections import Counter from io import StringIO from pathlib import Path -from typing import Counter as CounterType -from typing import TextIO, Tuple +from typing import TextIO import pytest from _pytest.config import Config from pylint import checkers from pylint.config.config_initialization import _config_initialization -from pylint.constants import IS_PYPY from pylint.lint import PyLinter from pylint.message.message import Message from pylint.testutils.constants import _EXPECTED_RE, _OPERATORS, UPDATE_OPTION @@ -33,7 +31,7 @@ from pylint.testutils.output_line import OutputLine from pylint.testutils.reporter_for_tests import FunctionalTestReporter -MessageCounter = CounterType[Tuple[int, str]] +MessageCounter = Counter[tuple[int, str]] PYLINTRC = Path(__file__).parent / "testing_pylintrc" @@ -108,9 +106,6 @@ def __init__( self._check_end_position = ( sys.version_info >= self._linter.config.min_pyver_end_position ) - # TODO: PY3.9: PyPy supports end_lineno from 3.9 and above - if self._check_end_position and IS_PYPY: - self._check_end_position = sys.version_info >= (3, 9) # pragma: no cover self._config = config diff --git a/pylint/testutils/output_line.py b/pylint/testutils/output_line.py index c979a049c3..fbe5b3bbce 100644 --- a/pylint/testutils/output_line.py +++ b/pylint/testutils/output_line.py @@ -44,8 +44,8 @@ class OutputLine(NamedTuple): def from_msg(cls, msg: Message, check_endline: bool = True) -> OutputLine: """Create an OutputLine from a Pylint Message.""" column = cls._get_column(msg.column) - end_line = cls._get_py38_none_value(msg.end_line, check_endline) - end_column = cls._get_py38_none_value(msg.end_column, check_endline) + end_line = cls._get_end_line_and_end_col(msg.end_line, check_endline) + end_column = cls._get_end_line_and_end_col(msg.end_column, check_endline) return cls( msg.symbol, msg.line, @@ -63,7 +63,7 @@ def _get_column(column: str | int) -> int: return int(column) @staticmethod - def _get_py38_none_value(value: _T, check_endline: bool) -> _T | None: + def _get_end_line_and_end_col(value: _T, check_endline: bool) -> _T | None: """Used to make end_line and end_column None as indicated by our version compared to `min_pyver_end_position`. """ @@ -84,10 +84,10 @@ def from_csv( line = int(row[1]) column = cls._get_column(row[2]) end_line = cls._value_to_optional_int( - cls._get_py38_none_value(row[3], check_endline) + cls._get_end_line_and_end_col(row[3], check_endline) ) end_column = cls._value_to_optional_int( - cls._get_py38_none_value(row[4], check_endline) + cls._get_end_line_and_end_col(row[4], check_endline) ) # symbol, line, column, end_line, end_column, node, msg, confidences assert len(row) == 8 diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index c621f9e7a9..115eda416a 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -25,6 +25,7 @@ class PyreverseConfig( def __init__( self, + *, mode: str = "PUB_ONLY", classes: list[str] | None = None, show_ancestors: int | None = None, diff --git a/pylint/testutils/utils.py b/pylint/testutils/utils.py index 1ff999b28d..3036d1fd62 100644 --- a/pylint/testutils/utils.py +++ b/pylint/testutils/utils.py @@ -27,7 +27,7 @@ def _patch_streams(out: TextIO) -> Iterator[None]: @contextlib.contextmanager def _test_sys_path( replacement_sys_path: list[str] | None = None, -) -> Generator[None, None, None]: +) -> Generator[None]: original_path = sys.path try: if replacement_sys_path is not None: @@ -40,7 +40,7 @@ def _test_sys_path( @contextlib.contextmanager def _test_cwd( current_working_directory: str | Path | None = None, -) -> Generator[None, None, None]: +) -> Generator[None]: original_dir = os.getcwd() try: if current_working_directory is not None: @@ -53,7 +53,7 @@ def _test_cwd( @contextlib.contextmanager def _test_environ_pythonpath( new_pythonpath: str | None = None, -) -> Generator[None, None, None]: +) -> Generator[None]: original_pythonpath = os.environ.get("PYTHONPATH") if new_pythonpath: os.environ["PYTHONPATH"] = new_pythonpath diff --git a/pylint/typing.py b/pylint/typing.py index f9dde2e405..963222871f 100644 --- a/pylint/typing.py +++ b/pylint/typing.py @@ -7,20 +7,17 @@ from __future__ import annotations import argparse +from collections.abc import Iterable from pathlib import Path +from re import Pattern from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - Iterable, Literal, NamedTuple, Optional, - Pattern, Protocol, - Tuple, - Type, TypedDict, Union, ) @@ -93,7 +90,7 @@ class ManagedMessage(NamedTuple): """All possible message categories.""" -OptionDict = Dict[ +OptionDict = dict[ str, Union[ None, @@ -102,12 +99,12 @@ class ManagedMessage(NamedTuple): int, Pattern[str], Iterable[Union[str, int, Pattern[str]]], - Type["_CallbackAction"], + type["_CallbackAction"], Callable[[Any], Any], Callable[[Any, Any, Any, Any], Any], ], ] -Options = Tuple[Tuple[str, OptionDict], ...] +Options = tuple[tuple[str, OptionDict], ...] ReportsCallable = Callable[["Section", "LinterStats", Optional["LinterStats"]], None] @@ -126,10 +123,10 @@ class ExtraMessageOptions(TypedDict, total=False): MessageDefinitionTuple = Union[ - Tuple[str, str, str], - Tuple[str, str, str, ExtraMessageOptions], + tuple[str, str, str], + tuple[str, str, str, ExtraMessageOptions], ] -DirectoryNamespaceDict = Dict[Path, Tuple[argparse.Namespace, "DirectoryNamespaceDict"]] +DirectoryNamespaceDict = dict[Path, tuple[argparse.Namespace, "DirectoryNamespaceDict"]] class GetProjectCallable(Protocol): diff --git a/pylint/utils/ast_walker.py b/pylint/utils/ast_walker.py index 367a39b817..6cbc7751e7 100644 --- a/pylint/utils/ast_walker.py +++ b/pylint/utils/ast_walker.py @@ -7,8 +7,8 @@ import sys import traceback from collections import defaultdict -from collections.abc import Sequence -from typing import TYPE_CHECKING, Callable +from collections.abc import Callable +from typing import TYPE_CHECKING from astroid import nodes @@ -75,12 +75,8 @@ def walk(self, astroid: nodes.NodeNG) -> None: """ cid = astroid.__class__.__name__.lower() - # Detect if the node is a new name for a deprecated alias. - # In this case, favour the methods for the deprecated - # alias if any, in order to maintain backwards - # compatibility. - visit_events: Sequence[AstCallback] = self.visit_events.get(cid, ()) - leave_events: Sequence[AstCallback] = self.leave_events.get(cid, ()) + visit_events = self.visit_events[cid] + leave_events = self.leave_events[cid] # pylint: disable = too-many-try-statements try: diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index bc2763eaa4..45217bb7ea 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -7,7 +7,7 @@ import collections from collections import defaultdict from collections.abc import Iterator -from typing import TYPE_CHECKING, Dict, Literal +from typing import TYPE_CHECKING, Literal from astroid import nodes @@ -21,7 +21,7 @@ from pylint.message import MessageDefinition, MessageDefinitionStore -MessageStateDict = Dict[str, Dict[int, bool]] +MessageStateDict = dict[str, dict[int, bool]] class FileState: diff --git a/pylint/utils/pragma_parser.py b/pylint/utils/pragma_parser.py index 12513e2843..5e066653e4 100644 --- a/pylint/utils/pragma_parser.py +++ b/pylint/utils/pragma_parser.py @@ -86,7 +86,7 @@ class InvalidPragmaError(PragmaParserError): """Thrown in case the pragma is invalid.""" -def parse_pragma(pylint_pragma: str) -> Generator[PragmaRepresenter, None, None]: +def parse_pragma(pylint_pragma: str) -> Generator[PragmaRepresenter]: action: str | None = None messages: list[str] = [] assignment_required = False diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 73e9e6a5f3..9316bcb7aa 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -25,17 +25,8 @@ from collections import deque from collections.abc import Iterable, Sequence from io import BufferedReader, BytesIO -from typing import ( - TYPE_CHECKING, - Any, - List, - Literal, - Pattern, - TextIO, - Tuple, - TypeVar, - Union, -) +from re import Pattern +from typing import TYPE_CHECKING, Any, Literal, TextIO, TypeVar, Union from astroid import Module, modutils, nodes @@ -76,10 +67,10 @@ "T_GlobalOptionReturnTypes", bool, int, - List[str], + list[str], Pattern[str], - List[Pattern[str]], - Tuple[int, ...], + list[Pattern[str]], + tuple[int, ...], ) diff --git a/pylintrc b/pylintrc index a943b1cfd3..bd6e8a2e22 100644 --- a/pylintrc +++ b/pylintrc @@ -54,7 +54,7 @@ unsafe-load-any-extension=no extension-pkg-allow-list= # Minimum supported python version -py-version = 3.8.0 +py-version = 3.9.0 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or @@ -109,7 +109,6 @@ disable= # We anticipate #3512 where it will become optional fixme, consider-using-assignment-expr, - possibly-used-before-assignment, [REPORTS] @@ -401,7 +400,7 @@ spelling-ignore-words= spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:,pragma:,# noinspection # A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file=.pyenchant_pylint_custom_dict.txt +spelling-private-dict-file=custom_dict.txt # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. @@ -434,6 +433,9 @@ max-attributes=11 # Maximum number of statements in a try-block max-try-statements = 7 +# Maximum number of positional arguments (see R0917). +max-positional-arguments = 12 + [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. @@ -486,7 +488,7 @@ int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. -known-standard-library= +known-standard-library=_string # Force import order to recognize a module as part of a third party library. known-third-party=enchant diff --git a/pyproject.toml b/pyproject.toml index c61c8b38e9..8d56ce3a7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,11 +20,11 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Debuggers", @@ -32,7 +32,7 @@ classifiers = [ "Topic :: Software Development :: Testing", "Typing :: Typed" ] -requires-python = ">=3.8.0" +requires-python = ">=3.9.0" dependencies = [ "dill>=0.2;python_version<'3.11'", "dill>=0.3.6;python_version>='3.11'", @@ -41,7 +41,7 @@ dependencies = [ # Also upgrade requirements_test_min.txt. # Pinned to dev of second minor update to allow editable installs and fix primer issues, # see https://github.com/pylint-dev/astroid/issues/1341 - "astroid>=3.2.4,<=3.3.0-dev0", + "astroid>=3.3.8,<=3.4.0-dev0", "isort>=4.2.5,<6,!=5.13.0", "mccabe>=0.6,<0.8", "tomli>=1.1.0;python_version<'3.11'", @@ -107,6 +107,7 @@ markers = [ [tool.isort] profile = "black" known_third_party = ["platformdirs", "astroid", "sphinx", "isort", "pytest", "mccabe", "six", "toml"] +extra_standard_library = ["_string"] skip_glob = ["tests/functional/**", "tests/input/**", "tests/extensions/data/**", "tests/regrtest_data/**", "tests/data/**", "astroid/**", "venv/**"] src_paths = ["pylint"] @@ -144,6 +145,13 @@ module = [ # (for docstrings, strings and comments in particular). line-length = 115 +extend-exclude = [ + "tests/**/data/", + "tests/**/functional/", + "tests/input/", + "tests/regrtest_data/", +] + [tool.ruff.lint] select = [ "B", # bugbear @@ -192,3 +200,32 @@ ignore = [ [tool.ruff.lint.pydocstyle] convention = "pep257" + +[tool.codespell] +ignore-words = ["custom_dict.txt"] + +# Disabled the spelling files for obvious reason, but also, +# the test file with typing extension imported as 'te' and: +# tests/functional/i/implicit/implicit_str_concat_latin1.py: +# - bad encoding +# pylint/pyreverse/diagrams.py and tests/pyreverse/test_diagrams.py: +# - An API from pyreverse use 'classe', and would need to be deprecated +# pylint/checkers/imports.py: +# - 'THIRDPARTY' is a value from isort that would need to be handled even +# if isort fix the typo in newer versions +# tests/functional/m/member/member_checks.py: +# - typos are voluntary to create credible 'no-member' + +skip = """ +tests/checkers/unittest_spelling.py,\ +CODE_OF_CONDUCT.md,\ +CONTRIBUTORS.txt,\ +pylint/checkers/imports.py,\ +pylint/pyreverse/diagrams.py,\ +tests/pyreverse/test_diagrams.py,\ +tests/functional/i/implicit/implicit_str_concat_latin1.py,\ +tests/functional/m/member/member_checks.py,\ +tests/functional/t/type/typevar_naming_style_rgx.py,\ +tests/functional/t/type/typevar_naming_style_default.py,\ +tests/functional/m/member/member_checks_async.py,\ +""" diff --git a/requirements_test.txt b/requirements_test.txt index a4a0eba040..8d79c90767 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,11 +1,10 @@ -r requirements_test_min.txt -coverage~=7.5 +coverage~=7.6 tbump~=6.11.0 contributors-txt>=1.0.0 pytest-cov~=5.0 -pytest-profiling~=1.7 pytest-xdist~=3.6 six # Type packages for mypy -types-pkg_resources==0.1.3 +types-setuptools==75.6.0.20241126 tox>=3 diff --git a/requirements_test_min.txt b/requirements_test_min.txt index 1f3f7586d1..68ce269740 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,12 +1,12 @@ .[testutils,spelling] # astroid dependency is also defined in pyproject.toml -astroid==3.2.4 # Pinned to a specific version for tests -typing-extensions~=4.11 +astroid==3.3.8 # Pinned to a specific version for tests +typing-extensions~=4.12 py~=1.11.0 -pytest~=7.4 +pytest~=8.3 pytest-benchmark~=4.0 pytest-timeout~=2.3 -towncrier~=23.11 +towncrier~=24.8 requests # Voluntary for test purpose, not actually used in prod, see #8904 setuptools;python_version>='3.12' diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index d30e2dc055..651aa4c4e6 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -567,6 +567,10 @@ "mails": ["jaehoonhwang@users.noreply.github.com"], "name": "Jaehoon Hwang" }, + "jake.lishman@ibm.com": { + "mails": ["jake.lishman@ibm.com", "jake@binhbar.com"], + "name": "Jake Lishman" + }, "james.morgensen@gmail.com": { "comment": ": ignored-modules option applies to import errors.", "mails": ["james.morgensen@gmail.com"], diff --git a/tbump.toml b/tbump.toml index 6dfecce196..05075f6e2e 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/pylint" [version] -current = "3.2.7" +current = "3.3.3" regex = ''' ^(?P0|[1-9]\d*) \. diff --git a/tests/checkers/unittest_misc.py b/tests/checkers/unittest_misc.py index 2f292b81f1..dae07efae1 100644 --- a/tests/checkers/unittest_misc.py +++ b/tests/checkers/unittest_misc.py @@ -40,7 +40,7 @@ def test_xxx_without_space(self) -> None: def test_xxx_middle(self) -> None: code = """a = 1 - # midle XXX + # middle XXX """ with self.assertNoMessages(): self.checker.process_tokens(_tokenize_str(code)) diff --git a/tests/checkers/unittest_non_ascii_name.py b/tests/checkers/unittest_non_ascii_name.py index 0741a9fb08..2ef6c55e3a 100644 --- a/tests/checkers/unittest_non_ascii_name.py +++ b/tests/checkers/unittest_non_ascii_name.py @@ -4,7 +4,6 @@ from __future__ import annotations -import sys from collections.abc import Iterable import astroid @@ -20,9 +19,6 @@ class TestNonAsciiChecker(pylint.testutils.CheckerTestCase): CHECKER_CLASS = pylint.checkers.non_ascii_names.NonAsciiNameChecker checker: pylint.checkers.non_ascii_names.NonAsciiNameChecker - @pytest.mark.skipif( - sys.version_info < (3, 8), reason="requires python3.8 or higher" - ) def test_kwargs_and_position_only(self) -> None: """Even the new position only and keyword only should be found.""" node = astroid.extract_node( diff --git a/tests/checkers/unittest_stdlib.py b/tests/checkers/unittest_stdlib.py index df0bcfd518..9b45fd33b3 100644 --- a/tests/checkers/unittest_stdlib.py +++ b/tests/checkers/unittest_stdlib.py @@ -6,7 +6,7 @@ import contextlib from collections.abc import Callable, Iterator -from typing import Any, Type +from typing import Any import astroid from astroid import nodes @@ -16,7 +16,7 @@ from pylint.checkers import stdlib from pylint.testutils import CheckerTestCase -_NodeNGT = Type[nodes.NodeNG] +_NodeNGT = type[nodes.NodeNG] @contextlib.contextmanager diff --git a/tests/checkers/unittest_symilar.py b/tests/checkers/unittest_symilar.py index 91a9e5e545..0e551e4a16 100644 --- a/tests/checkers/unittest_symilar.py +++ b/tests/checkers/unittest_symilar.py @@ -7,9 +7,9 @@ from pathlib import Path import pytest +from _pytest.capture import CaptureFixture -from pylint.checkers import similar -from pylint.constants import IS_PYPY, PY39_PLUS +from pylint.checkers import symilar from pylint.lint import PyLinter from pylint.testutils import GenericTestReporter as Reporter @@ -31,7 +31,7 @@ def test_ignore_comments() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-comments", SIMILAR1, SIMILAR2]) + symilar.Run(["--ignore-comments", SIMILAR1, SIMILAR2]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -60,7 +60,7 @@ def test_ignore_comments() -> None: def test_ignore_docstrings() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-docstrings", SIMILAR1, SIMILAR2]) + symilar.Run(["--ignore-docstrings", SIMILAR1, SIMILAR2]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -95,7 +95,7 @@ def test_ignore_docstrings() -> None: def test_ignore_imports() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-imports", SIMILAR1, SIMILAR2]) + symilar.Run(["--ignore-imports", SIMILAR1, SIMILAR2]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -108,7 +108,7 @@ def test_ignore_imports() -> None: def test_multiline_imports() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([MULTILINE, MULTILINE]) + symilar.Run([MULTILINE, MULTILINE]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -131,14 +131,10 @@ def test_multiline_imports() -> None: ) -@pytest.mark.skipif( - IS_PYPY and not PY39_PLUS, - reason="Requires accurate 'end_lineno' value", -) def test_ignore_multiline_imports() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-imports", MULTILINE, MULTILINE]) + symilar.Run(["--ignore-imports", MULTILINE, MULTILINE]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -151,7 +147,7 @@ def test_ignore_multiline_imports() -> None: def test_ignore_signatures_fail() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([SIMILAR5, SIMILAR6]) + symilar.Run([SIMILAR5, SIMILAR6]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -189,7 +185,7 @@ def example(): def test_ignore_signatures_pass() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-signatures", SIMILAR5, SIMILAR6]) + symilar.Run(["--ignore-signatures", SIMILAR5, SIMILAR6]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -202,7 +198,7 @@ def test_ignore_signatures_pass() -> None: def test_ignore_signatures_class_methods_fail() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([SIMILAR_CLS_B, SIMILAR_CLS_A]) + symilar.Run([SIMILAR_CLS_B, SIMILAR_CLS_A]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -248,7 +244,7 @@ def _internal_func( def test_ignore_signatures_class_methods_pass() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-signatures", SIMILAR_CLS_B, SIMILAR_CLS_A]) + symilar.Run(["--ignore-signatures", SIMILAR_CLS_B, SIMILAR_CLS_A]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -261,7 +257,7 @@ def test_ignore_signatures_class_methods_pass() -> None: def test_ignore_signatures_empty_functions_fail() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([EMPTY_FUNCTION_1, EMPTY_FUNCTION_2]) + symilar.Run([EMPTY_FUNCTION_1, EMPTY_FUNCTION_2]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -285,7 +281,7 @@ def test_ignore_signatures_empty_functions_fail() -> None: def test_ignore_signatures_empty_functions_pass() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-signatures", EMPTY_FUNCTION_1, EMPTY_FUNCTION_2]) + symilar.Run(["--ignore-signatures", EMPTY_FUNCTION_1, EMPTY_FUNCTION_2]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -298,7 +294,7 @@ def test_ignore_signatures_empty_functions_pass() -> None: def test_no_hide_code_with_imports() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--ignore-imports"] + 2 * [HIDE_CODE_WITH_IMPORTS]) + symilar.Run(["--ignore-imports"] + 2 * [HIDE_CODE_WITH_IMPORTS]) assert ex.value.code == 0 assert "TOTAL lines=32 duplicates=0 percent=0.00" in output.getvalue() @@ -306,7 +302,7 @@ def test_no_hide_code_with_imports() -> None: def test_ignore_nothing() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([SIMILAR1, SIMILAR2]) + symilar.Run([SIMILAR1, SIMILAR2]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -329,7 +325,7 @@ def test_ignore_nothing() -> None: def test_lines_without_meaningful_content_do_not_trigger_similarity() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([SIMILAR3, SIMILAR4]) + symilar.Run([SIMILAR3, SIMILAR4]) assert ex.value.code == 0 assert ( output.getvalue().strip() @@ -362,29 +358,32 @@ def test_help() -> None: output = StringIO() with redirect_stdout(output): try: - similar.Run(["--help"]) + symilar.Run(["--help"]) except SystemExit as ex: assert ex.code == 0 else: pytest.fail("not system exit") -def test_no_args() -> None: +def test_no_args(capsys: CaptureFixture) -> None: output = StringIO() with redirect_stdout(output): try: - similar.Run([]) + symilar.Run([]) except SystemExit as ex: - assert ex.code == 1 + assert ex.code == 2 + out, err = capsys.readouterr() + assert not out + assert "the following arguments are required: files" in err else: pytest.fail("not system exit") def test_get_map_data() -> None: - """Tests that a SimilarChecker can return and reduce mapped data.""" + """Tests that a SymilarChecker can return and reduce mapped data.""" linter = PyLinter(reporter=Reporter()) # Add a parallel checker to ensure it can map and reduce - linter.register_checker(similar.SimilarChecker(linter)) + linter.register_checker(symilar.SimilaritiesChecker(linter)) source_streams = ( str(INPUT / "similar_lines_a.py"), str(INPUT / "similar_lines_b.py"), @@ -473,7 +472,7 @@ def test_get_map_data() -> None: # Manually perform a 'map' type function for source_fname in source_streams: - sim = similar.SimilarChecker(PyLinter()) + sim = symilar.SimilaritiesChecker(PyLinter()) sim.linter.set_option("ignore-imports", False) sim.linter.set_option("ignore-signatures", False) with open(source_fname, encoding="utf-8") as stream: @@ -494,35 +493,35 @@ def test_get_map_data() -> None: def test_set_duplicate_lines_to_zero() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["--duplicates=0", SIMILAR1, SIMILAR2]) + symilar.Run(["--duplicates=0", SIMILAR1, SIMILAR2]) assert ex.value.code == 0 assert output.getvalue() == "" -@pytest.mark.parametrize("v", ["d"]) -def test_bad_equal_short_form_option(v: str) -> None: +def test_equal_short_form_option() -> None: """Regression test for https://github.com/pylint-dev/pylint/issues/9343""" output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([f"-{v}=0", SIMILAR1, SIMILAR2]) - assert ex.value.code == 2 - assert "invalid literal for int() with base 10: '=0'" in output.getvalue() + symilar.Run(["-d=2", SIMILAR1, SIMILAR2]) + assert ex.value.code == 0 + assert "similar lines in" in output.getvalue() -@pytest.mark.parametrize("v", ["i", "d"]) -def test_space_short_form_option(v: str) -> None: +def test_space_short_form_option() -> None: """Regression test for https://github.com/pylint-dev/pylint/issues/9343""" output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run([f"-{v} 2", SIMILAR1, SIMILAR2]) + symilar.Run(["-d 2", SIMILAR1, SIMILAR2]) assert ex.value.code == 0 assert "similar lines in" in output.getvalue() -def test_bad_short_form_option() -> None: +def test_bad_short_form_option(capsys: CaptureFixture) -> None: """Regression test for https://github.com/pylint-dev/pylint/issues/9343""" output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: - similar.Run(["-j=0", SIMILAR1, SIMILAR2]) + symilar.Run(["-j=0", SIMILAR1, SIMILAR2]) + out, err = capsys.readouterr() assert ex.value.code == 2 - assert "option -j not recognized" in output.getvalue() + assert not out + assert "unrecognized arguments: -j=0" in err diff --git a/tests/config/functional/toml/issue_9680/bad_regex_in_ignore_paths.32.out b/tests/config/functional/toml/issue_9680/bad_regex_in_ignore_paths.32.out new file mode 100644 index 0000000000..b28c892103 --- /dev/null +++ b/tests/config/functional/toml/issue_9680/bad_regex_in_ignore_paths.32.out @@ -0,0 +1,2 @@ +usage: pylint [options] +pylint: error: argument --ignore-paths: Error in provided regular expression: project\\tooling_context\\**|project/tooling_context/** beginning at index 27: multiple repeat diff --git a/tests/config/functional/toml/issue_9680/bad_regex_in_ignore_paths.toml b/tests/config/functional/toml/issue_9680/bad_regex_in_ignore_paths.toml new file mode 100644 index 0000000000..528a6da5dd --- /dev/null +++ b/tests/config/functional/toml/issue_9680/bad_regex_in_ignore_paths.toml @@ -0,0 +1,3 @@ +# Check that we report regex error in configuration file properly +[tool.pylint."main"] +ignore-paths = ['project/tooling_context/**'] diff --git a/tests/config/functional/toml/unknown_msgid/enable_unknown_msgid.toml b/tests/config/functional/toml/unknown_msgid/enable_unknown_msgid.toml index 65fe560904..c25ca46a50 100644 --- a/tests/config/functional/toml/unknown_msgid/enable_unknown_msgid.toml +++ b/tests/config/functional/toml/unknown_msgid/enable_unknown_msgid.toml @@ -1,4 +1,4 @@ -# Check the behavior for unkonwn symbol/msgid +# Check the behavior for unknown symbol/msgid # (Originally) reported in https://github.com/pylint-dev/pylint/pull/6293 [tool.pylint."messages control"] diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 872b568a61..425f861894 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -55,10 +55,8 @@ def test_can_read_toml_env_variable(tmp_path: Path, file_to_lint_path: str) -> N ) env_var = "tmp_path_env" os.environ[env_var] = str(config_file) - mock_exit, _, runner = run_using_a_configuration_file( - f"${env_var}", file_to_lint_path - ) - mock_exit.assert_called_once_with(0) + runner = run_using_a_configuration_file(f"${env_var}", file_to_lint_path) + assert runner.linter.msg_status == 0 check_configuration_file_reader(runner) @@ -226,7 +224,7 @@ def test_disable_before_enable_all_takes_effect() -> None: runner = Run(["--disable=fixme", "--enable=all", str(FIXME_MODULE)], exit=False) assert not runner.linter.stats.by_msg - _, _, toml_runner = run_using_a_configuration_file( + toml_runner = run_using_a_configuration_file( HERE / "functional" / "toml" @@ -239,7 +237,7 @@ def test_enable_before_disable_all_takes_effect() -> None: runner = Run(["--enable=fixme", "--disable=all", str(FIXME_MODULE)], exit=False) assert runner.linter.stats.by_msg - _, _, toml_runner = run_using_a_configuration_file( + toml_runner = run_using_a_configuration_file( HERE / "functional" / "toml" diff --git a/tests/config/test_find_default_config_files.py b/tests/config/test_find_default_config_files.py index ae879a10d0..2c50a55682 100644 --- a/tests/config/test_find_default_config_files.py +++ b/tests/config/test_find_default_config_files.py @@ -18,7 +18,10 @@ from pytest import CaptureFixture from pylint import config, testutils -from pylint.config.find_default_config_files import _cfg_has_config, _toml_has_config +from pylint.config.find_default_config_files import ( + _cfg_or_ini_has_config, + _toml_has_config, +) from pylint.lint.run import Run @@ -307,12 +310,13 @@ def test_toml_has_config(content: str, expected: bool, 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: - f.write(content) - assert _cfg_has_config(fake_cfg) == expected +def test_has_config(content: str, expected: bool, tmp_path: Path) -> None: + """Test that a .cfg file or .ini file has a pylint config.""" + for file_name in ("fake.cfg", "tox.ini"): + fake_conf = tmp_path / file_name + with open(fake_conf, "w", encoding="utf8") as f: + f.write(content) + assert _cfg_or_ini_has_config(fake_conf) == expected def test_non_existent_home() -> None: diff --git a/tests/config/test_functional_config_loading.py b/tests/config/test_functional_config_loading.py index 0bce30701b..26341cf328 100644 --- a/tests/config/test_functional_config_loading.py +++ b/tests/config/test_functional_config_loading.py @@ -57,10 +57,8 @@ def default_configuration( ) -> PylintConfiguration: empty_pylintrc = tmp_path / "pylintrc" empty_pylintrc.write_text("") - mock_exit, _, runner = run_using_a_configuration_file( - str(empty_pylintrc), file_to_lint_path - ) - mock_exit.assert_called_once_with(0) + runner = run_using_a_configuration_file(str(empty_pylintrc), file_to_lint_path) + assert runner.linter.msg_status == 0 return runner.linter.config.__dict__ @@ -84,14 +82,24 @@ def test_functional_config_loading( expected_loaded_configuration = get_expected_configuration( configuration_path, default_configuration ) + runner = None # The runner can fail to init if conf is bad enough. with warnings.catch_warnings(): warnings.filterwarnings( "ignore", message="The use of 'MASTER'.*", category=UserWarning ) - mock_exit, _, runner = run_using_a_configuration_file( - configuration_path, file_to_lint_path - ) - mock_exit.assert_called_once_with(expected_code) + try: + runner = run_using_a_configuration_file( + configuration_path, file_to_lint_path + ) + assert runner.linter.msg_status == expected_code + except SystemExit as e: + # Case where the conf exit with an argparse error + assert e.code == expected_code + out, err = capsys.readouterr() + assert out == "" + assert err.rstrip() == expected_output.rstrip() + return + out, err = capsys.readouterr() # 'rstrip()' applied, so we can have a final newline in the expected test file assert expected_output.rstrip() == out.rstrip(), msg diff --git a/tests/data/ascript b/tests/data/a_script similarity index 100% rename from tests/data/ascript rename to tests/data/a_script diff --git a/tests/functional/a/access/access_attr_before_def_false_positive.rc b/tests/functional/a/access/access_attr_before_def_false_positive.rc new file mode 100644 index 0000000000..7a083bad76 --- /dev/null +++ b/tests/functional/a/access/access_attr_before_def_false_positive.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.13 diff --git a/tests/functional/a/arguments.py b/tests/functional/a/arguments.py index a312c45e6a..5807916aee 100644 --- a/tests/functional/a/arguments.py +++ b/tests/functional/a/arguments.py @@ -331,3 +331,5 @@ def func(string): func(42) a = func(42) + +isinstance(1) # [no-value-for-parameter] diff --git a/tests/functional/a/arguments.txt b/tests/functional/a/arguments.txt index 70a99e1a23..7f20d23d11 100644 --- a/tests/functional/a/arguments.txt +++ b/tests/functional/a/arguments.txt @@ -39,3 +39,4 @@ no-value-for-parameter:217:0:217:30::No value for argument 'second' in function unexpected-keyword-arg:218:0:218:43::Unexpected keyword argument 'fourth' in function call:UNDEFINED redundant-keyword-arg:308:0:308:79::Argument 'banana' passed by position and keyword in function call:UNDEFINED no-value-for-parameter:318:0:318:16::No value for argument 'param1' in function call:UNDEFINED +no-value-for-parameter:335:0:335:13::No value for argument '__class_or_tuple' in function call:HIGH diff --git a/tests/functional/a/arguments_differ.py b/tests/functional/a/arguments_differ.py index 1c56ecc083..effe96c9a5 100644 --- a/tests/functional/a/arguments_differ.py +++ b/tests/functional/a/arguments_differ.py @@ -280,7 +280,7 @@ class ChildT3(ParentT3): def func(self, user_input: FooT1) -> None: pass -# Keyword and positional overriddes +# Keyword and positional overrides class AbstractFoo: def kwonly_1(self, first, *, second, third): diff --git a/tests/functional/a/async_functions.py b/tests/functional/a/async_functions.py index 75fa684dff..5852dd2438 100644 --- a/tests/functional/a/async_functions.py +++ b/tests/functional/a/async_functions.py @@ -22,7 +22,8 @@ async def some_method(self): super(OtherClass, self).test() # [bad-super-call] -# +1: [too-many-arguments,too-many-return-statements, too-many-branches] +# +1: [line-too-long] +# +1: [too-many-arguments, too-many-positional-arguments, too-many-return-statements, too-many-branches] async def complex_function(this, function, has, more, arguments, than, one, _, should, have): if 1: diff --git a/tests/functional/a/async_functions.txt b/tests/functional/a/async_functions.txt index 985fddec2d..33052f8773 100644 --- a/tests/functional/a/async_functions.txt +++ b/tests/functional/a/async_functions.txt @@ -1,10 +1,12 @@ redefined-builtin:5:0:5:14:next:Redefining built-in 'next':UNDEFINED unused-argument:8:30:8:34:some_function:Unused argument 'arg2':HIGH bad-super-call:22:8:22:31:Class.some_method:Bad first argument 'OtherClass' given to super():UNDEFINED -too-many-arguments:26:0:26:26:complex_function:Too many arguments (10/5):UNDEFINED -too-many-branches:26:0:26:26:complex_function:Too many branches (13/12):UNDEFINED -too-many-return-statements:26:0:26:26:complex_function:Too many return statements (10/6):UNDEFINED -dangerous-default-value:59:0:59:14:func:Dangerous default value [] as argument:UNDEFINED -duplicate-argument-name:59:18:59:19:func:Duplicate argument name a in function definition:HIGH -disallowed-name:64:0:64:13:foo:"Disallowed name ""foo""":HIGH -empty-docstring:64:0:64:13:foo:Empty function docstring:HIGH +line-too-long:26:0:None:None::Line too long (104/100):UNDEFINED +too-many-arguments:27:0:27:26:complex_function:Too many arguments (10/5):UNDEFINED +too-many-branches:27:0:27:26:complex_function:Too many branches (13/12):UNDEFINED +too-many-positional-arguments:27:0:27:26:complex_function:Too many positional arguments (9/5):HIGH +too-many-return-statements:27:0:27:26:complex_function:Too many return statements (10/6):UNDEFINED +dangerous-default-value:60:0:60:14:func:Dangerous default value [] as argument:UNDEFINED +duplicate-argument-name:60:18:60:19:func:Duplicate argument name 'a' in function definition:HIGH +disallowed-name:65:0:65:13:foo:"Disallowed name ""foo""":HIGH +empty-docstring:65:0:65:13:foo:Empty function docstring:HIGH diff --git a/tests/functional/a/await_outside_async.py b/tests/functional/a/await_outside_async.py index 2bc1267615..1e18169a53 100644 --- a/tests/functional/a/await_outside_async.py +++ b/tests/functional/a/await_outside_async.py @@ -28,3 +28,7 @@ def inner_func(): def outer_func(): async def inner_func(): await asyncio.sleep(1) + +# pylint: disable=unnecessary-lambda-assignment +async def func3(): + f = lambda: await nested() # [await-outside-async] diff --git a/tests/functional/a/await_outside_async.txt b/tests/functional/a/await_outside_async.txt index 554eeccb24..8edd4e5cdc 100644 --- a/tests/functional/a/await_outside_async.txt +++ b/tests/functional/a/await_outside_async.txt @@ -1,2 +1,3 @@ await-outside-async:12:10:12:24:not_async:'await' should be used within an async function:UNDEFINED await-outside-async:25:8:25:30:func2.inner_func:'await' should be used within an async function:UNDEFINED +await-outside-async:34:16:34:30:func3.:'await' should be used within an async function:UNDEFINED diff --git a/tests/functional/b/broad_exception/broad_exception_caught.txt b/tests/functional/b/broad_exception/broad_exception_caught.txt index 817e620172..386423b63f 100644 --- a/tests/functional/b/broad_exception/broad_exception_caught.txt +++ b/tests/functional/b/broad_exception/broad_exception_caught.txt @@ -1,3 +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 +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/broad_exception_caught_trystar.txt b/tests/functional/b/broad_exception/broad_exception_caught_trystar.txt index da8725e57a..635a5c3fda 100644 --- a/tests/functional/b/broad_exception/broad_exception_caught_trystar.txt +++ b/tests/functional/b/broad_exception/broad_exception_caught_trystar.txt @@ -1,3 +1,3 @@ -broad-exception-caught:14:8:14:17::Catching too general exception Exception:INFERENCE -broad-exception-caught:20:8:20:21::Catching too general exception BaseException:INFERENCE -broad-exception-caught:32:7:32:27::Catching too general exception CustomBroadException:INFERENCE +broad-exception-caught:14:8:14:17::Catching too general exception Exception:INFERENCE +broad-exception-caught:20:8:20:21::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/broad_exception_raised.txt b/tests/functional/b/broad_exception/broad_exception_raised.txt index 705bf45cd8..1e27b23f98 100644 --- a/tests/functional/b/broad_exception/broad_exception_raised.txt +++ b/tests/functional/b/broad_exception/broad_exception_raised.txt @@ -1,8 +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 +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/b/broad_exception/broad_exception_raised_trystar.txt b/tests/functional/b/broad_exception/broad_exception_raised_trystar.txt index c88b26b4be..d009e255c8 100644 --- a/tests/functional/b/broad_exception/broad_exception_raised_trystar.txt +++ b/tests/functional/b/broad_exception/broad_exception_raised_trystar.txt @@ -1,8 +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_star:"Raising too general exception: Exception":INFERENCE -broad-exception-caught:21:12:21:21:raise_and_catch_star:Catching too general exception Exception:INFERENCE -broad-exception-raised:38:8:38:35:raise_catch_raise_star:"Raising too general exception: Exception":INFERENCE -broad-exception-raised:46:8:46:40:raise_catch_raise_using_alias_star:"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 +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_star:"Raising too general exception: Exception":INFERENCE +broad-exception-caught:21:12:21:21:raise_and_catch_star:Catching too general exception Exception:INFERENCE +broad-exception-raised:38:8:38:35:raise_catch_raise_star:"Raising too general exception: Exception":INFERENCE +broad-exception-raised:46:8:46:40:raise_catch_raise_using_alias_star:"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/consider/consider_merging_isinstance.py b/tests/functional/c/consider/consider_merging_isinstance.py index d3387bd5ce..ba0b4c77a8 100644 --- a/tests/functional/c/consider/consider_merging_isinstance.py +++ b/tests/functional/c/consider/consider_merging_isinstance.py @@ -23,8 +23,8 @@ def isinstances(): result = isinstance(var[10], str) or isinstance(var[10], int) and var[8] * 14 or isinstance(var[10], float) and var[5] * 14.4 or isinstance(var[10], list) # [consider-merging-isinstance] result = isinstance(var[11], int) or isinstance(var[11], int) or isinstance(var[11], float) # [consider-merging-isinstance] - result = isinstance(var[20]) - result = isinstance() + result = isinstance(var[20]) # [no-value-for-parameter] + result = isinstance() # [no-value-for-parameter, no-value-for-parameter] # Combination merged and not merged result = isinstance(var[12], (int, float)) or isinstance(var[12], list) # [consider-merging-isinstance] diff --git a/tests/functional/c/consider/consider_merging_isinstance.txt b/tests/functional/c/consider/consider_merging_isinstance.txt index 3d4297fb88..8bf618ad9d 100644 --- a/tests/functional/c/consider/consider_merging_isinstance.txt +++ b/tests/functional/c/consider/consider_merging_isinstance.txt @@ -4,4 +4,7 @@ consider-merging-isinstance:19:13:19:73:isinstances:Consider merging these isins consider-merging-isinstance:22:13:22:127:isinstances:Consider merging these isinstance calls to isinstance(var[6], (float, int)):UNDEFINED consider-merging-isinstance:23:13:23:158:isinstances:Consider merging these isinstance calls to isinstance(var[10], (list, str)):UNDEFINED consider-merging-isinstance:24:13:24:95:isinstances:Consider merging these isinstance calls to isinstance(var[11], (float, int)):UNDEFINED +no-value-for-parameter:26:13:26:32:isinstances:No value for argument '__class_or_tuple' in function call:HIGH +no-value-for-parameter:27:13:27:25:isinstances:No value for argument '__class_or_tuple' in function call:HIGH +no-value-for-parameter:27:13:27:25:isinstances:No value for argument '_obj' in function call:HIGH consider-merging-isinstance:30:13:30:75:isinstances:Consider merging these isinstance calls to isinstance(var[12], (float, int, list)):UNDEFINED diff --git a/tests/functional/c/consider/consider_using_get.py b/tests/functional/c/consider/consider_using_get.py index 728fccd505..8a75f99727 100644 --- a/tests/functional/c/consider/consider_using_get.py +++ b/tests/functional/c/consider/consider_using_get.py @@ -16,10 +16,10 @@ if 'key' in dictionary: # not accessing the dictionary in assignment variable = "string" -if 'key' in dictionary: # is a match, but not obvious and we ignore it for now +if 'key' in dictionary: # is a match, but not obvious, and we ignore it for now variable = dictionary[key] -if 'key1' in dictionary: # dictionary querried for wrong key +if 'key1' in dictionary: # dictionary queried for wrong key variable = dictionary['key2'] if 'key' in dictionary: # body is not pure diff --git a/tests/functional/d/deprecated/deprecated_methods_py3.py b/tests/functional/d/deprecated/deprecated_methods_py3.py deleted file mode 100644 index 80428c79f3..0000000000 --- a/tests/functional/d/deprecated/deprecated_methods_py3.py +++ /dev/null @@ -1,50 +0,0 @@ -""" Functional tests for method deprecation. """ -# pylint: disable=missing-docstring, super-init-not-called, not-callable -import base64 -import cgi -import inspect -import logging -import nntplib -import platform -import unittest -import xml.etree.ElementTree - - -class MyTest(unittest.TestCase): - def test(self): - self.assert_(True) # [deprecated-method] - -xml.etree.ElementTree.Element('tag').getchildren() # [deprecated-method] -xml.etree.ElementTree.Element('tag').getiterator() # [deprecated-method] -xml.etree.ElementTree.XMLParser('tag', None, None).doctype(None, None, None) # [deprecated-method] -nntplib.NNTP(None).xpath(None) # [deprecated-method] - - -inspect.getargspec(None) # [deprecated-method] -logging.warn("a") # [deprecated-method] -platform.popen([]) # [deprecated-method] -base64.encodestring("42") # [deprecated-method] -base64.decodestring("42") # [deprecated-method] -cgi.escape("a") # [deprecated-method] - - -class SuperCrash(unittest.TestCase): - - def __init__(self): - # should not crash. - super()() - -xml.etree.ElementTree.iterparse(None) - - -class Tests(unittest.TestCase): - - def test_foo(self): - self.assertEquals(2 + 2, 4) # [deprecated-method] - self.assertNotEquals(2 + 2, 4) # [deprecated-method] - self.assertAlmostEquals(2 + 2, 4) # [deprecated-method] - self.assertNotAlmostEquals(2 + 2, 4) # [deprecated-method] - self.assert_("abc" == "2") # [deprecated-method] - - self.assertRaisesRegex(ValueError, "exception") - self.assertRegex("something", r".+") diff --git a/tests/functional/d/deprecated/deprecated_methods_py3.rc b/tests/functional/d/deprecated/deprecated_methods_py3.rc deleted file mode 100644 index b56440977e..0000000000 --- a/tests/functional/d/deprecated/deprecated_methods_py3.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.7 diff --git a/tests/functional/d/deprecated/deprecated_methods_py3.txt b/tests/functional/d/deprecated/deprecated_methods_py3.txt deleted file mode 100644 index 9796114f66..0000000000 --- a/tests/functional/d/deprecated/deprecated_methods_py3.txt +++ /dev/null @@ -1,16 +0,0 @@ -deprecated-method:15::MyTest.test:Using deprecated method assert_() -deprecated-method:17:::Using deprecated method getchildren() -deprecated-method:18:::Using deprecated method getiterator() -deprecated-method:19:::Using deprecated method doctype() -deprecated-method:20:::Using deprecated method xpath() -deprecated-method:23:::Using deprecated method getargspec() -deprecated-method:24:::Using deprecated method warn() -deprecated-method:25:::Using deprecated method popen() -deprecated-method:26:::Using deprecated method encodestring() -deprecated-method:27:::Using deprecated method decodestring() -deprecated-method:28:::Using deprecated method escape() -deprecated-method:43::Tests.test_foo:Using deprecated method assertEquals() -deprecated-method:44::Tests.test_foo:Using deprecated method assertNotEquals() -deprecated-method:45::Tests.test_foo:Using deprecated method assertAlmostEquals() -deprecated-method:46::Tests.test_foo:Using deprecated method assertNotAlmostEquals() -deprecated-method:47::Tests.test_foo:Using deprecated method assert_() diff --git a/tests/functional/d/deprecated/deprecated_methods_py38.py b/tests/functional/d/deprecated/deprecated_methods_py38.py deleted file mode 100644 index 3a7dfe862b..0000000000 --- a/tests/functional/d/deprecated/deprecated_methods_py38.py +++ /dev/null @@ -1,57 +0,0 @@ -""" Functional tests for method deprecation. """ -# pylint: disable=missing-docstring, super-init-not-called, not-callable, comparison-of-constants -import base64 -import inspect -import logging -import nntplib -import time -import unittest -import xml.etree.ElementTree - -class MyTest(unittest.TestCase): - def test(self): - self.assert_(True) # [deprecated-method] - -xml.etree.ElementTree.Element('tag').getchildren() # [deprecated-method] -xml.etree.ElementTree.Element('tag').getiterator() # [deprecated-method] -nntplib.NNTP(None).xpath(None) # [deprecated-method] - - -inspect.getargspec(None) # [deprecated-method] -logging.warn("a") # [deprecated-method] -base64.encodestring("42") # [deprecated-method] -base64.decodestring("42") # [deprecated-method] - - -class SuperCrash(unittest.TestCase): - - def __init__(self): - # should not crash. - super()() - -xml.etree.ElementTree.iterparse(None) - - -class Tests(unittest.TestCase): - - def test_foo(self): - self.assertEquals(2 + 2, 4) # [deprecated-method] - self.assertNotEquals(2 + 2, 4) # [deprecated-method] - self.assertAlmostEquals(2 + 2, 4) # [deprecated-method] - self.assertNotAlmostEquals(2 + 2, 4) # [deprecated-method] - self.assert_("abc" == "2") # [deprecated-method] - - self.assertRaisesRegex(ValueError, "exception") - self.assertRegex("something", r".+") - - -class Deprecated: # pylint: disable=too-few-public-methods - deprecated_method = logging.warn - - -d = Deprecated() -d.deprecated_method() # [deprecated-method] - -def test(clock = time.time): - """time.clock is deprecated but time.time via an alias is not!""" - clock() diff --git a/tests/functional/d/deprecated/deprecated_methods_py38.rc b/tests/functional/d/deprecated/deprecated_methods_py38.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/d/deprecated/deprecated_methods_py38.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/d/deprecated/deprecated_methods_py38.txt b/tests/functional/d/deprecated/deprecated_methods_py38.txt deleted file mode 100644 index afed0c6b47..0000000000 --- a/tests/functional/d/deprecated/deprecated_methods_py38.txt +++ /dev/null @@ -1,14 +0,0 @@ -deprecated-method:13:8:13:26:MyTest.test:Using deprecated method assert_():UNDEFINED -deprecated-method:15:0:15:50::Using deprecated method getchildren():UNDEFINED -deprecated-method:16:0:16:50::Using deprecated method getiterator():UNDEFINED -deprecated-method:17:0:17:30::Using deprecated method xpath():UNDEFINED -deprecated-method:20:0:20:24::Using deprecated method getargspec():UNDEFINED -deprecated-method:21:0:21:17::Using deprecated method warn():UNDEFINED -deprecated-method:22:0:22:25::Using deprecated method encodestring():UNDEFINED -deprecated-method:23:0:23:25::Using deprecated method decodestring():UNDEFINED -deprecated-method:38:8:38:35:Tests.test_foo:Using deprecated method assertEquals():UNDEFINED -deprecated-method:39:8:39:38:Tests.test_foo:Using deprecated method assertNotEquals():UNDEFINED -deprecated-method:40:8:40:41:Tests.test_foo:Using deprecated method assertAlmostEquals():UNDEFINED -deprecated-method:41:8:41:44:Tests.test_foo:Using deprecated method assertNotAlmostEquals():UNDEFINED -deprecated-method:42:8:42:34:Tests.test_foo:Using deprecated method assert_():UNDEFINED -deprecated-method:53:0:53:21::Using deprecated method deprecated_method():UNDEFINED diff --git a/tests/functional/d/deprecated/deprecated_methods_py39.rc b/tests/functional/d/deprecated/deprecated_methods_py39.rc index 062f6df19c..4e2b748313 100644 --- a/tests/functional/d/deprecated/deprecated_methods_py39.rc +++ b/tests/functional/d/deprecated/deprecated_methods_py39.rc @@ -1,3 +1,2 @@ [testoptions] -min_pyver=3.9 max_pyver=3.10 diff --git a/tests/functional/d/deprecated/deprecated_module_py39.rc b/tests/functional/d/deprecated/deprecated_module_py39.rc index 062f6df19c..4e2b748313 100644 --- a/tests/functional/d/deprecated/deprecated_module_py39.rc +++ b/tests/functional/d/deprecated/deprecated_module_py39.rc @@ -1,3 +1,2 @@ [testoptions] -min_pyver=3.9 max_pyver=3.10 diff --git a/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc index 09ceaa5e54..5dc39b1a65 100644 --- a/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc +++ b/tests/functional/d/deprecated/deprecated_module_py39_earlier_pyversion.rc @@ -2,5 +2,4 @@ py-version=3.8 [testoptions] -min_pyver=3.9 max_pyver=3.10 diff --git a/tests/functional/d/duplicate/duplicate_argument_name.py b/tests/functional/d/duplicate/duplicate_argument_name.py index c0c68b43bb..a6654e6bb0 100644 --- a/tests/functional/d/duplicate/duplicate_argument_name.py +++ b/tests/functional/d/duplicate/duplicate_argument_name.py @@ -1,14 +1,28 @@ """Check for duplicate function arguments.""" +# pylint: disable=missing-docstring, line-too-long, unused-argument + def foo1(_, _): # [duplicate-argument-name] - """Function with duplicate argument name.""" + ... def foo2(_abc, *, _abc): # [duplicate-argument-name] - """Function with duplicate argument name.""" + ... def foo3(_, _=3): # [duplicate-argument-name] - """Function with duplicate argument name.""" + ... def foo4(_, *, _): # [duplicate-argument-name] - """Function with duplicate argument name.""" + ... + +def foo5(_, *_, _=3): # [duplicate-argument-name, duplicate-argument-name] + ... + +def foo6(a, *a): # [duplicate-argument-name] + ... + +def foo7(a, /, a): # [duplicate-argument-name] + ... + +def foo8(a, **a): # [duplicate-argument-name] + ... diff --git a/tests/functional/d/duplicate/duplicate_argument_name.txt b/tests/functional/d/duplicate/duplicate_argument_name.txt index 2925c5ac40..c565e88f40 100644 --- a/tests/functional/d/duplicate/duplicate_argument_name.txt +++ b/tests/functional/d/duplicate/duplicate_argument_name.txt @@ -1,4 +1,9 @@ -duplicate-argument-name:4:12:4:13:foo1:Duplicate argument name _ in function definition:HIGH -duplicate-argument-name:7:18:7:22:foo2:Duplicate argument name _abc in function definition:HIGH -duplicate-argument-name:10:12:10:13:foo3:Duplicate argument name _ in function definition:HIGH -duplicate-argument-name:13:15:13:16:foo4:Duplicate argument name _ in function definition:HIGH +duplicate-argument-name:6:12:6:13:foo1:Duplicate argument name '_' in function definition:HIGH +duplicate-argument-name:9:18:9:22:foo2:Duplicate argument name '_abc' in function definition:HIGH +duplicate-argument-name:12:12:12:13:foo3:Duplicate argument name '_' in function definition:HIGH +duplicate-argument-name:15:15:15:16:foo4:Duplicate argument name '_' in function definition:HIGH +duplicate-argument-name:18:13:18:14:foo5:Duplicate argument name '_' in function definition:HIGH +duplicate-argument-name:18:16:18:17:foo5:Duplicate argument name '_' in function definition:HIGH +duplicate-argument-name:21:13:21:14:foo6:Duplicate argument name 'a' in function definition:HIGH +duplicate-argument-name:24:15:24:16:foo7:Duplicate argument name 'a' in function definition:HIGH +duplicate-argument-name:27:14:27:15:foo8:Duplicate argument name 'a' in function definition:HIGH diff --git a/tests/functional/d/duplicate/duplicate_argument_name_py3.py b/tests/functional/d/duplicate/duplicate_argument_name_py3.py deleted file mode 100644 index 4751c6f2d3..0000000000 --- a/tests/functional/d/duplicate/duplicate_argument_name_py3.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Check for duplicate function keywordonly arguments.""" - - -def foo1(_, *_, _=3): # [duplicate-argument-name] - """Function with duplicate argument name.""" diff --git a/tests/functional/d/duplicate/duplicate_argument_name_py3.txt b/tests/functional/d/duplicate/duplicate_argument_name_py3.txt deleted file mode 100644 index 3d6f6f8d9d..0000000000 --- a/tests/functional/d/duplicate/duplicate_argument_name_py3.txt +++ /dev/null @@ -1 +0,0 @@ -duplicate-argument-name:4:16:4:17:foo1:Duplicate argument name _ in function definition:HIGH diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py index 0fd82307fb..05db0cd500 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py @@ -383,7 +383,7 @@ def test_finds_args_with_xref_type_google(named_arg, **kwargs): def test_ignores_optional_specifier_google( param1, param2, param3=(), param4=[], param5=[], param6=True -): +): # pylint: disable=too-many-positional-arguments """Do something. Args: @@ -411,7 +411,7 @@ def test_finds_multiple_complex_types_google( named_arg_eight, named_arg_nine, named_arg_ten, -): +): # pylint: disable=too-many-positional-arguments """The google docstring Args: diff --git a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py index 83fa9700d3..3d84fdef24 100644 --- a/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py +++ b/tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py @@ -344,7 +344,7 @@ def my_func( named_arg_six, named_arg_seven, named_arg_eight, -): +): # pylint: disable=too-many-positional-arguments """The docstring Args diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py index 22dbcadaa3..f4ccea3133 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.py @@ -17,33 +17,33 @@ def test_find_google_attr_raises_exact_exc(self): """This is a google docstring. Raises: - re.error: Sometimes + calendar.IllegalMonthError: Sometimes """ - import re + import calendar - raise re.error("hi") + raise calendar.IllegalMonthError(-1) def test_find_google_attr_raises_substr_exc(self): """This is a google docstring. Raises: - re.error: Sometimes + calendar.IllegalMonthError: Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) def test_find_valid_missing_google_attr_raises(self): # [missing-raises-doc] """This is a google docstring. Raises: - re.anothererror: Sometimes + calendar.anothererror: Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) def test_find_invalid_missing_google_attr_raises(self): diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt index f59d271769..f3bbcad7fb 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Google.txt @@ -1,6 +1,6 @@ missing-raises-doc:6:0:6:35:test_find_missing_google_raises:"""RuntimeError"" not documented as being raised":HIGH unreachable:13:4:13:25:test_find_missing_google_raises:Unreachable code:HIGH -missing-raises-doc:38:0:38:46:test_find_valid_missing_google_attr_raises:"""error"" not documented as being raised":HIGH +missing-raises-doc:38:0:38:46:test_find_valid_missing_google_attr_raises:"""IllegalMonthError"" not documented as being raised":HIGH unreachable:83:4:83:25:test_find_all_google_raises:Unreachable code:HIGH unreachable:94:4:94:25:test_find_multiple_google_raises:Unreachable code:HIGH unreachable:95:4:95:30:test_find_multiple_google_raises:Unreachable code:HIGH diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py index 8cf8e041f1..44e047ceb3 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.py @@ -87,12 +87,12 @@ def test_find_numpy_attr_raises_exact_exc(self): Raises ------ - re.error + calendar.IllegalMonthError Sometimes """ - import re + import calendar - raise re.error("hi") + raise calendar.IllegalMonthError(-1) def test_find_numpy_attr_raises_substr_exc(self): @@ -100,12 +100,12 @@ def test_find_numpy_attr_raises_substr_exc(self): Raises ------ - re.error + calendar.IllegalMonthError Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) def test_find_valid_missing_numpy_attr_raises(self): # [missing-raises-doc] @@ -113,12 +113,12 @@ def test_find_valid_missing_numpy_attr_raises(self): # [missing-raises-doc] Raises ------ - re.anothererror + calendar.anothererror Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) def test_find_invalid_missing_numpy_attr_raises(self): diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt index 43c6ba89b9..1cfdc062a4 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Numpy.txt @@ -3,7 +3,7 @@ unreachable:20:4:20:25:test_find_missing_numpy_raises:Unreachable code:HIGH unreachable:34:4:34:25:test_find_all_numpy_raises:Unreachable code:HIGH missing-raises-doc:37:0:37:35:test_find_rethrown_numpy_raises:"""RuntimeError"" not documented as being raised":HIGH missing-raises-doc:53:0:53:44:test_find_rethrown_numpy_multiple_raises:"""RuntimeError, ValueError"" not documented as being raised":HIGH -missing-raises-doc:111:0:111:45:test_find_valid_missing_numpy_attr_raises:"""error"" not documented as being raised":HIGH +missing-raises-doc:111:0:111:45:test_find_valid_missing_numpy_attr_raises:"""IllegalMonthError"" not documented as being raised":HIGH missing-raises-doc:146:4:146:11:Foo.foo:"""AttributeError"" not documented as being raised":HIGH unreachable:158:8:158:17:Foo.foo:Unreachable code:HIGH unreachable:182:8:182:17:Foo.foo:Unreachable code:HIGH diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.py b/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.py index 91a603b711..b9a301ed6f 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.py +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.py @@ -123,21 +123,21 @@ def test_find_sphinx_attr_raises_exact_exc(self): def test_find_sphinx_attr_raises_substr_exc(self): """This is a sphinx docstring. - :raises re.error: Sometimes + :raises calendar.IllegalMonthError: Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) def test_find_valid_missing_sphinx_attr_raises(self): # [missing-raises-doc] """This is a sphinx docstring. - :raises re.anothererror: Sometimes + :raises calendar.anothererror: Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) def test_find_invalid_missing_sphinx_attr_raises(self): @@ -145,11 +145,11 @@ def test_find_invalid_missing_sphinx_attr_raises(self): pylint allows this to pass since the comparison between Raises and raise are based on the class name, not the qualified name. - :raises bogusmodule.error: Sometimes + :raises bogusmodule.IllegalMonthError: Sometimes """ - from re import error + from calendar import IllegalMonthError - raise error("hi") + raise IllegalMonthError(-1) class Foo: diff --git a/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt b/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt index 599c8beda3..568de3c4b3 100644 --- a/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt +++ b/tests/functional/ext/docparams/raise/missing_raises_doc_Sphinx.txt @@ -10,4 +10,4 @@ missing-raises-doc:90:0:90:55:test_find_missing_sphinx_raises_infer_from_instanc unreachable:97:4:97:25:test_find_missing_sphinx_raises_infer_from_instance:Unreachable code:HIGH missing-raises-doc:100:0:100:55:test_find_missing_sphinx_raises_infer_from_function:"""RuntimeError"" not documented as being raised":HIGH unreachable:110:4:110:25:test_find_missing_sphinx_raises_infer_from_function:Unreachable code:HIGH -missing-raises-doc:133:0:133:46:test_find_valid_missing_sphinx_attr_raises:"""error"" not documented as being raised":HIGH +missing-raises-doc:133:0:133:46:test_find_valid_missing_sphinx_attr_raises:"""IllegalMonthError"" not documented as being raised":HIGH diff --git a/tests/functional/ext/typing/typing_consider_using_alias.py b/tests/functional/ext/typing/typing_consider_using_alias.py index 070451cf0a..783de1822e 100644 --- a/tests/functional/ext/typing/typing_consider_using_alias.py +++ b/tests/functional/ext/typing/typing_consider_using_alias.py @@ -28,7 +28,6 @@ var7: typing.Hashable # [consider-using-alias] var8: typing.ContextManager[str] # [consider-using-alias] var9: typing.Pattern[str] # [consider-using-alias] -var10: typing.re.Match[str] # [consider-using-alias] var11: list[int] var12: collections.abc var13: Awaitable[None] diff --git a/tests/functional/ext/typing/typing_consider_using_alias.txt b/tests/functional/ext/typing/typing_consider_using_alias.txt index 2cd299d904..2a55615978 100644 --- a/tests/functional/ext/typing/typing_consider_using_alias.txt +++ b/tests/functional/ext/typing/typing_consider_using_alias.txt @@ -6,15 +6,14 @@ consider-using-alias:27:6:27:21::'typing.Iterable' will be deprecated with PY39, consider-using-alias:28:6:28:21::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead:INFERENCE consider-using-alias:29:6:29:27::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead:INFERENCE consider-using-alias:30:6:30:20::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead:INFERENCE -consider-using-alias:31:7:31:22::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead:INFERENCE -consider-using-alias:40:9:40:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE -consider-using-alias:42:7:42:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead:INFERENCE -consider-using-alias:43:7:43:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:INFERENCE -consider-using-alias:44:7:44:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead:INFERENCE -consider-using-alias:50:74:50:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead:INFERENCE -consider-using-alias:50:16:50:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE -consider-using-alias:50:37:50:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE -consider-using-alias:50:93:50:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:INFERENCE -consider-using-alias:66:12:66:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE -consider-using-alias:71:12:71:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE -consider-using-alias:75:12:75:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:39:9:39:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:41:7:41:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead:INFERENCE +consider-using-alias:42:7:42:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:INFERENCE +consider-using-alias:43:7:43:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead:INFERENCE +consider-using-alias:49:74:49:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead:INFERENCE +consider-using-alias:49:16:49:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:49:37:49:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:49:93:49:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead:INFERENCE +consider-using-alias:65:12:65:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:70:12:70:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:74:12:74:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE diff --git a/tests/functional/ext/typing/typing_consider_using_alias_without_future.py b/tests/functional/ext/typing/typing_consider_using_alias_without_future.py index b597e955eb..b382b9ac9f 100644 --- a/tests/functional/ext/typing/typing_consider_using_alias_without_future.py +++ b/tests/functional/ext/typing/typing_consider_using_alias_without_future.py @@ -26,7 +26,6 @@ var7: typing.Hashable # [consider-using-alias] var8: typing.ContextManager[str] # [consider-using-alias] var9: typing.Pattern[str] # [consider-using-alias] -var10: typing.re.Match[str] # [consider-using-alias] var11: list[int] var12: collections.abc var13: Awaitable[None] diff --git a/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt b/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt index 7cf15a63c8..8ae56f810a 100644 --- a/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt +++ b/tests/functional/ext/typing/typing_consider_using_alias_without_future.txt @@ -6,15 +6,14 @@ consider-using-alias:25:6:25:21::'typing.Iterable' will be deprecated with PY39, consider-using-alias:26:6:26:21::'typing.Hashable' will be deprecated with PY39, consider using 'collections.abc.Hashable' instead:INFERENCE consider-using-alias:27:6:27:27::'typing.ContextManager' will be deprecated with PY39, consider using 'contextlib.AbstractContextManager' instead. Add 'from __future__ import annotations' as well:INFERENCE consider-using-alias:28:6:28:20::'typing.Pattern' will be deprecated with PY39, consider using 're.Pattern' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:29:7:29:22::'typing.Match' will be deprecated with PY39, consider using 're.Match' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:38:9:38:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE -consider-using-alias:40:7:40:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:41:7:41:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:42:7:42:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:48:74:48:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:48:16:48:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:48:37:48:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:48:93:48:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:64:12:64:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:69:12:69:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE -consider-using-alias:73:12:73:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:37:9:37:13::'typing.List' will be deprecated with PY39, consider using 'list' instead:INFERENCE +consider-using-alias:39:7:39:11::'typing.Type' will be deprecated with PY39, consider using 'type' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:40:7:40:12::'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:41:7:41:15::'typing.Callable' will be deprecated with PY39, consider using 'collections.abc.Callable' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:47:74:47:78:func1:'typing.Dict' will be deprecated with PY39, consider using 'dict' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:47:16:47:20:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:47:37:47:41:func1:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:47:93:47:105:func1:'typing.Tuple' will be deprecated with PY39, consider using 'tuple' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:63:12:63:16:CustomNamedTuple:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:68:12:68:16:CustomTypedDict2:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE +consider-using-alias:72:12:72:16:CustomDataClass:'typing.List' will be deprecated with PY39, consider using 'list' instead. Add 'from __future__ import annotations' as well:INFERENCE diff --git a/tests/functional/ext/typing/typing_deprecated_alias.py b/tests/functional/ext/typing/typing_deprecated_alias.py index 80c132ebd9..a84afc47a1 100644 --- a/tests/functional/ext/typing/typing_deprecated_alias.py +++ b/tests/functional/ext/typing/typing_deprecated_alias.py @@ -19,7 +19,6 @@ var7: typing.Hashable # [deprecated-typing-alias] var8: typing.ContextManager[str] # [deprecated-typing-alias] var9: typing.Pattern[str] # [deprecated-typing-alias] -var10: typing.re.Match[str] # [deprecated-typing-alias] var11: list[int] var12: collections.abc var13: Awaitable[None] diff --git a/tests/functional/ext/typing/typing_deprecated_alias.rc b/tests/functional/ext/typing/typing_deprecated_alias.rc index a4a4c9022c..d075a593ef 100644 --- a/tests/functional/ext/typing/typing_deprecated_alias.rc +++ b/tests/functional/ext/typing/typing_deprecated_alias.rc @@ -1,8 +1,3 @@ [main] py-version=3.9 load-plugins=pylint.extensions.typing - -[testoptions] -min_pyver=3.9 - -[typing] diff --git a/tests/functional/ext/typing/typing_deprecated_alias.txt b/tests/functional/ext/typing/typing_deprecated_alias.txt index 62cf9902ad..b08faa9570 100644 --- a/tests/functional/ext/typing/typing_deprecated_alias.txt +++ b/tests/functional/ext/typing/typing_deprecated_alias.txt @@ -6,23 +6,22 @@ deprecated-typing-alias:18:6:18:21::'typing.Iterable' is deprecated, use 'collec deprecated-typing-alias:19:6:19:21::'typing.Hashable' is deprecated, use 'collections.abc.Hashable' instead:INFERENCE deprecated-typing-alias:20:6:20:27::'typing.ContextManager' is deprecated, use 'contextlib.AbstractContextManager' instead:INFERENCE deprecated-typing-alias:21:6:21:20::'typing.Pattern' is deprecated, use 're.Pattern' instead:INFERENCE -deprecated-typing-alias:22:7:22:22::'typing.Match' is deprecated, use 're.Match' instead:INFERENCE -deprecated-typing-alias:28:9:28:12::'typing.Set' is deprecated, use 'set' instead:INFERENCE -deprecated-typing-alias:29:9:29:13::'typing.Dict' is deprecated, use 'dict' instead:INFERENCE -deprecated-typing-alias:29:19:29:23::'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:30:20:30:31::'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:31:9:31:13::'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:33:7:33:11::'typing.Type' is deprecated, use 'type' instead:INFERENCE -deprecated-typing-alias:34:7:34:12::'typing.Tuple' is deprecated, use 'tuple' instead:INFERENCE -deprecated-typing-alias:35:7:35:15::'typing.Callable' is deprecated, use 'collections.abc.Callable' instead:INFERENCE -deprecated-typing-alias:41:74:41:78:func1:'typing.Dict' is deprecated, use 'dict' instead:INFERENCE -deprecated-typing-alias:41:16:41:20:func1:'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:41:37:41:41:func1:'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:41:93:41:105:func1:'typing.Tuple' is deprecated, use 'tuple' instead:INFERENCE -deprecated-typing-alias:48:20:48:31:CustomIntList:'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:52:28:52:32::'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:54:14:54:18::'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:57:12:57:16:CustomNamedTuple:'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:59:56:59:60::'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:62:12:62:16:CustomTypedDict2:'typing.List' is deprecated, use 'list' instead:INFERENCE -deprecated-typing-alias:66:12:66:16:CustomDataClass:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:27:9:27:12::'typing.Set' is deprecated, use 'set' instead:INFERENCE +deprecated-typing-alias:28:9:28:13::'typing.Dict' is deprecated, use 'dict' instead:INFERENCE +deprecated-typing-alias:28:19:28:23::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:29:20:29:31::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:30:9:30:13::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:32:7:32:11::'typing.Type' is deprecated, use 'type' instead:INFERENCE +deprecated-typing-alias:33:7:33:12::'typing.Tuple' is deprecated, use 'tuple' instead:INFERENCE +deprecated-typing-alias:34:7:34:15::'typing.Callable' is deprecated, use 'collections.abc.Callable' instead:INFERENCE +deprecated-typing-alias:40:74:40:78:func1:'typing.Dict' is deprecated, use 'dict' instead:INFERENCE +deprecated-typing-alias:40:16:40:20:func1:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:40:37:40:41:func1:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:40:93:40:105:func1:'typing.Tuple' is deprecated, use 'tuple' instead:INFERENCE +deprecated-typing-alias:47:20:47:31:CustomIntList:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:51:28:51:32::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:53:14:53:18::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:56:12:56:16:CustomNamedTuple:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:58:56:58:60::'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:61:12:61:16:CustomTypedDict2:'typing.List' is deprecated, use 'list' instead:INFERENCE +deprecated-typing-alias:65:12:65:16:CustomDataClass:'typing.List' is deprecated, use 'list' instead:INFERENCE diff --git a/tests/functional/ext/typing/unnecessary_default_type_args.py b/tests/functional/ext/typing/unnecessary_default_type_args.py new file mode 100644 index 0000000000..e2d1d700de --- /dev/null +++ b/tests/functional/ext/typing/unnecessary_default_type_args.py @@ -0,0 +1,17 @@ +# pylint: disable=missing-docstring,deprecated-typing-alias +import collections.abc as ca +import typing as t + +a1: t.Generator[int, str, str] +a2: t.Generator[int, None, None] +a3: t.Generator[int] +b1: t.AsyncGenerator[int, str] +b2: t.AsyncGenerator[int, None] +b3: t.AsyncGenerator[int] + +c1: ca.Generator[int, str, str] +c2: ca.Generator[int, None, None] # [unnecessary-default-type-args] +c3: ca.Generator[int] +d1: ca.AsyncGenerator[int, str] +d2: ca.AsyncGenerator[int, None] # [unnecessary-default-type-args] +d3: ca.AsyncGenerator[int] diff --git a/tests/functional/ext/typing/unnecessary_default_type_args.rc b/tests/functional/ext/typing/unnecessary_default_type_args.rc new file mode 100644 index 0000000000..63e11a4e6b --- /dev/null +++ b/tests/functional/ext/typing/unnecessary_default_type_args.rc @@ -0,0 +1,3 @@ +[main] +py-version=3.10 +load-plugins=pylint.extensions.typing diff --git a/tests/functional/ext/typing/unnecessary_default_type_args.txt b/tests/functional/ext/typing/unnecessary_default_type_args.txt new file mode 100644 index 0000000000..2d36ba46a6 --- /dev/null +++ b/tests/functional/ext/typing/unnecessary_default_type_args.txt @@ -0,0 +1,2 @@ +unnecessary-default-type-args:13:4:13:33::Type `ca.Generator[int, None, None]` has unnecessary default type args. Change it to `ca.Generator[int]`.:HIGH +unnecessary-default-type-args:16:4:16:32::Type `ca.AsyncGenerator[int, None]` has unnecessary default type args. Change it to `ca.AsyncGenerator[int]`.:HIGH diff --git a/tests/functional/ext/typing/unnecessary_default_type_args_py313.py b/tests/functional/ext/typing/unnecessary_default_type_args_py313.py new file mode 100644 index 0000000000..9dec4c4075 --- /dev/null +++ b/tests/functional/ext/typing/unnecessary_default_type_args_py313.py @@ -0,0 +1,17 @@ +# pylint: disable=missing-docstring,deprecated-typing-alias +import collections.abc as ca +import typing as t + +a1: t.Generator[int, str, str] +a2: t.Generator[int, None, None] # [unnecessary-default-type-args] +a3: t.Generator[int] +b1: t.AsyncGenerator[int, str] +b2: t.AsyncGenerator[int, None] # [unnecessary-default-type-args] +b3: t.AsyncGenerator[int] + +c1: ca.Generator[int, str, str] +c2: ca.Generator[int, None, None] # [unnecessary-default-type-args] +c3: ca.Generator[int] +d1: ca.AsyncGenerator[int, str] +d2: ca.AsyncGenerator[int, None] # [unnecessary-default-type-args] +d3: ca.AsyncGenerator[int] diff --git a/tests/functional/ext/typing/unnecessary_default_type_args_py313.rc b/tests/functional/ext/typing/unnecessary_default_type_args_py313.rc new file mode 100644 index 0000000000..d2db5fe7ca --- /dev/null +++ b/tests/functional/ext/typing/unnecessary_default_type_args_py313.rc @@ -0,0 +1,3 @@ +[main] +py-version=3.13 +load-plugins=pylint.extensions.typing diff --git a/tests/functional/ext/typing/unnecessary_default_type_args_py313.txt b/tests/functional/ext/typing/unnecessary_default_type_args_py313.txt new file mode 100644 index 0000000000..228f499663 --- /dev/null +++ b/tests/functional/ext/typing/unnecessary_default_type_args_py313.txt @@ -0,0 +1,4 @@ +unnecessary-default-type-args:6:4:6:32::Type `t.Generator[int, None, None]` has unnecessary default type args. Change it to `t.Generator[int]`.:HIGH +unnecessary-default-type-args:9:4:9:31::Type `t.AsyncGenerator[int, None]` has unnecessary default type args. Change it to `t.AsyncGenerator[int]`.:HIGH +unnecessary-default-type-args:13:4:13:33::Type `ca.Generator[int, None, None]` has unnecessary default type args. Change it to `ca.Generator[int]`.:HIGH +unnecessary-default-type-args:16:4:16:32::Type `ca.AsyncGenerator[int, None]` has unnecessary default type args. Change it to `ca.AsyncGenerator[int]`.:HIGH diff --git a/tests/functional/g/generic_alias/generic_alias_collections.rc b/tests/functional/g/generic_alias/generic_alias_collections.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37.py b/tests/functional/g/generic_alias/generic_alias_collections_py37.py deleted file mode 100644 index eaa153cedb..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Test generic alias support for stdlib types (added in PY39). - -Raise [unsubscriptable-object] error for PY37 and PY38. -""" -# flake8: noqa -# pylint: disable=missing-docstring,pointless-statement -# pylint: disable=too-few-public-methods,multiple-statements,line-too-long -import abc -import collections -import collections.abc -import contextlib -import re - -# special -tuple[int, int] # [unsubscriptable-object] -type[int] # [unsubscriptable-object] -collections.abc.Callable[[int], str] # [unsubscriptable-object] - -# builtins -dict[int, str] # [unsubscriptable-object] -list[int] # [unsubscriptable-object] -set[int] # [unsubscriptable-object] -frozenset[int] # [unsubscriptable-object] - -# collections -collections.defaultdict[int, str] # [unsubscriptable-object] -collections.OrderedDict[int, str] # [unsubscriptable-object] -collections.ChainMap[int, str] # [unsubscriptable-object] -collections.Counter[int] # [unsubscriptable-object] -collections.deque[int] # [unsubscriptable-object] - -# collections.abc -collections.abc.Set[int] # [unsubscriptable-object] -collections.abc.Collection[int] # [unsubscriptable-object] -collections.abc.Container[int] # [unsubscriptable-object] -collections.abc.ItemsView[int, str] # [unsubscriptable-object] -collections.abc.KeysView[int] # [unsubscriptable-object] -collections.abc.Mapping[int, str] # [unsubscriptable-object] -collections.abc.MappingView[int] # [unsubscriptable-object] -collections.abc.MutableMapping[int, str] # [unsubscriptable-object] -collections.abc.MutableSequence[int] # [unsubscriptable-object] -collections.abc.MutableSet[int] # [unsubscriptable-object] -collections.abc.Sequence[int] # [unsubscriptable-object] -collections.abc.ValuesView[int] # [unsubscriptable-object] - -collections.abc.Iterable[int] # [unsubscriptable-object] -collections.abc.Iterator[int] # [unsubscriptable-object] -collections.abc.Generator[int, None, None] # [unsubscriptable-object] -collections.abc.Reversible[int] # [unsubscriptable-object] - -collections.abc.Coroutine[list[str], str, int] # [unsubscriptable-object,unsubscriptable-object] -collections.abc.AsyncGenerator[int, None] # [unsubscriptable-object] -collections.abc.AsyncIterable[int] # [unsubscriptable-object] -collections.abc.AsyncIterator[int] # [unsubscriptable-object] -collections.abc.Awaitable[int] # [unsubscriptable-object] - -# contextlib -contextlib.AbstractContextManager[int] # [unsubscriptable-object] -contextlib.AbstractAsyncContextManager[int] # [unsubscriptable-object] - -# re -re.Pattern[str] # [unsubscriptable-object] -re.Match[str] # [unsubscriptable-object] - - -# unsubscriptable types -collections.abc.Hashable -collections.abc.Sized -collections.abc.Hashable[int] # [unsubscriptable-object] -collections.abc.Sized[int] # [unsubscriptable-object] - -# subscriptable with Python 3.9 -collections.abc.ByteString[int] # [unsubscriptable-object] - - -# Missing implementation for 'collections.abc' derived classes -class DerivedHashable(collections.abc.Hashable): # [abstract-method] # __hash__ - pass - -class DerivedIterable(collections.abc.Iterable[int]): # [unsubscriptable-object] - pass - -class DerivedCollection(collections.abc.Collection[int]): # [unsubscriptable-object] - pass - - -# No implementation required for 'builtins' and 'collections' types -class DerivedList(list[int]): # [unsubscriptable-object] - pass - -class DerivedSet(set[int]): # [unsubscriptable-object] - pass - -class DerivedOrderedDict(collections.OrderedDict[int, str]): # [unsubscriptable-object] - pass - -class DerivedListIterable(list[collections.abc.Iterable[int]]): # [unsubscriptable-object,unsubscriptable-object] - pass - - -# Multiple generic base classes -class DerivedMultiple(collections.abc.Sized, collections.abc.Hashable): # [abstract-method,abstract-method] - pass - -class CustomAbstractCls1(abc.ABC): - pass -class CustomAbstractCls2(collections.abc.Sized, collections.abc.Iterable[CustomAbstractCls1]): # [abstract-method,unsubscriptable-object] # __len__ - pass -class CustomImplementation(CustomAbstractCls2): # [abstract-method] # __len__ - pass - - -# Type annotations -var_tuple: tuple[int, int] # [unsubscriptable-object] -var_dict: dict[int, str] # [unsubscriptable-object] -var_orderedDict: collections.OrderedDict[int, str] # [unsubscriptable-object] -var_container: collections.abc.Container[int] # [unsubscriptable-object] -var_sequence: collections.abc.Sequence[int] # [unsubscriptable-object] -var_iterable: collections.abc.Iterable[int] # [unsubscriptable-object] -var_awaitable: collections.abc.Awaitable[int] # [unsubscriptable-object] -var_contextmanager: contextlib.AbstractContextManager[int] # [unsubscriptable-object] -var_pattern: re.Pattern[int] # [unsubscriptable-object] -var_bytestring: collections.abc.ByteString -var_hashable: collections.abc.Hashable -var_sized: collections.abc.Sized - -# Type annotation with unsubscriptable type -var_int: int[int] # [unsubscriptable-object] -var_hashable2: collections.abc.Hashable[int] # [unsubscriptable-object] -var_sized2: collections.abc.Sized[int] # [unsubscriptable-object] - -# subscriptable with Python 3.9 -var_bytestring2: collections.abc.ByteString[int] # [unsubscriptable-object] diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37.rc b/tests/functional/g/generic_alias/generic_alias_collections_py37.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37.txt b/tests/functional/g/generic_alias/generic_alias_collections_py37.txt deleted file mode 100644 index 72104b4bed..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37.txt +++ /dev/null @@ -1,67 +0,0 @@ -unsubscriptable-object:15:0:15:5::Value 'tuple' is unsubscriptable:UNDEFINED -unsubscriptable-object:16:0:16:4::Value 'type' is unsubscriptable:UNDEFINED -unsubscriptable-object:17:0:17:24::Value 'collections.abc.Callable' is unsubscriptable:UNDEFINED -unsubscriptable-object:20:0:20:4::Value 'dict' is unsubscriptable:UNDEFINED -unsubscriptable-object:21:0:21:4::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:22:0:22:3::Value 'set' is unsubscriptable:UNDEFINED -unsubscriptable-object:23:0:23:9::Value 'frozenset' is unsubscriptable:UNDEFINED -unsubscriptable-object:26:0:26:23::Value 'collections.defaultdict' is unsubscriptable:UNDEFINED -unsubscriptable-object:27:0:27:23::Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED -unsubscriptable-object:28:0:28:20::Value 'collections.ChainMap' is unsubscriptable:UNDEFINED -unsubscriptable-object:29:0:29:19::Value 'collections.Counter' is unsubscriptable:UNDEFINED -unsubscriptable-object:30:0:30:17::Value 'collections.deque' is unsubscriptable:UNDEFINED -unsubscriptable-object:33:0:33:19::Value 'collections.abc.Set' is unsubscriptable:UNDEFINED -unsubscriptable-object:34:0:34:26::Value 'collections.abc.Collection' is unsubscriptable:UNDEFINED -unsubscriptable-object:35:0:35:25::Value 'collections.abc.Container' is unsubscriptable:UNDEFINED -unsubscriptable-object:36:0:36:25::Value 'collections.abc.ItemsView' is unsubscriptable:UNDEFINED -unsubscriptable-object:37:0:37:24::Value 'collections.abc.KeysView' is unsubscriptable:UNDEFINED -unsubscriptable-object:38:0:38:23::Value 'collections.abc.Mapping' is unsubscriptable:UNDEFINED -unsubscriptable-object:39:0:39:27::Value 'collections.abc.MappingView' is unsubscriptable:UNDEFINED -unsubscriptable-object:40:0:40:30::Value 'collections.abc.MutableMapping' is unsubscriptable:UNDEFINED -unsubscriptable-object:41:0:41:31::Value 'collections.abc.MutableSequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:42:0:42:26::Value 'collections.abc.MutableSet' is unsubscriptable:UNDEFINED -unsubscriptable-object:43:0:43:24::Value 'collections.abc.Sequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:44:0:44:26::Value 'collections.abc.ValuesView' is unsubscriptable:UNDEFINED -unsubscriptable-object:46:0:46:24::Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:47:0:47:24::Value 'collections.abc.Iterator' is unsubscriptable:UNDEFINED -unsubscriptable-object:48:0:48:25::Value 'collections.abc.Generator' is unsubscriptable:UNDEFINED -unsubscriptable-object:49:0:49:26::Value 'collections.abc.Reversible' is unsubscriptable:UNDEFINED -unsubscriptable-object:51:0:51:25::Value 'collections.abc.Coroutine' is unsubscriptable:UNDEFINED -unsubscriptable-object:51:26:51:30::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:52:0:52:30::Value 'collections.abc.AsyncGenerator' is unsubscriptable:UNDEFINED -unsubscriptable-object:53:0:53:29::Value 'collections.abc.AsyncIterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:54:0:54:29::Value 'collections.abc.AsyncIterator' is unsubscriptable:UNDEFINED -unsubscriptable-object:55:0:55:25::Value 'collections.abc.Awaitable' is unsubscriptable:UNDEFINED -unsubscriptable-object:58:0:58:33::Value 'contextlib.AbstractContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:59:0:59:38::Value 'contextlib.AbstractAsyncContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:62:0:62:10::Value 're.Pattern' is unsubscriptable:UNDEFINED -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 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 -unsubscriptable-object:91:17:91:20:DerivedSet:Value 'set' is unsubscriptable:UNDEFINED -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 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 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 -unsubscriptable-object:117:15:117:40::Value 'collections.abc.Container' is unsubscriptable:UNDEFINED -unsubscriptable-object:118:14:118:38::Value 'collections.abc.Sequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:119:14:119:38::Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:120:15:120:40::Value 'collections.abc.Awaitable' is unsubscriptable:UNDEFINED -unsubscriptable-object:121:20:121:53::Value 'contextlib.AbstractContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:122:13:122:23::Value 're.Pattern' is unsubscriptable:UNDEFINED -unsubscriptable-object:128:9:128:12::Value 'int' is unsubscriptable:UNDEFINED -unsubscriptable-object:129:15:129:39::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED -unsubscriptable-object:130:12:130:33::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED -unsubscriptable-object:133:17:133:43::Value 'collections.abc.ByteString' is unsubscriptable:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.py b/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.py deleted file mode 100644 index 5319f13b62..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Test generic alias support for stdlib types (added in PY39). - -Raise [unsubscriptable-object] error for PY37 and PY38. -Make sure `import typing` doesn't change anything. -""" -# flake8: noqa -# pylint: disable=missing-docstring,pointless-statement,unused-import -# pylint: disable=too-few-public-methods,multiple-statements,line-too-long -import abc -import collections -import collections.abc -import contextlib -import re -import typing - -# special -tuple[int, int] # [unsubscriptable-object] -type[int] # [unsubscriptable-object] -collections.abc.Callable[[int], str] # [unsubscriptable-object] - -# builtins -dict[int, str] # [unsubscriptable-object] -list[int] # [unsubscriptable-object] -set[int] # [unsubscriptable-object] -frozenset[int] # [unsubscriptable-object] - -# collections -collections.defaultdict[int, str] # [unsubscriptable-object] -collections.OrderedDict[int, str] # [unsubscriptable-object] -collections.ChainMap[int, str] # [unsubscriptable-object] -collections.Counter[int] # [unsubscriptable-object] -collections.deque[int] # [unsubscriptable-object] - -# collections.abc -collections.abc.Set[int] # [unsubscriptable-object] -collections.abc.Collection[int] # [unsubscriptable-object] -collections.abc.Container[int] # [unsubscriptable-object] -collections.abc.ItemsView[int, str] # [unsubscriptable-object] -collections.abc.KeysView[int] # [unsubscriptable-object] -collections.abc.Mapping[int, str] # [unsubscriptable-object] -collections.abc.MappingView[int] # [unsubscriptable-object] -collections.abc.MutableMapping[int, str] # [unsubscriptable-object] -collections.abc.MutableSequence[int] # [unsubscriptable-object] -collections.abc.MutableSet[int] # [unsubscriptable-object] -collections.abc.Sequence[int] # [unsubscriptable-object] -collections.abc.ValuesView[int] # [unsubscriptable-object] - -collections.abc.Iterable[int] # [unsubscriptable-object] -collections.abc.Iterator[int] # [unsubscriptable-object] -collections.abc.Generator[int, None, None] # [unsubscriptable-object] -collections.abc.Reversible[int] # [unsubscriptable-object] - -collections.abc.Coroutine[list[str], str, int] # [unsubscriptable-object,unsubscriptable-object] -collections.abc.AsyncGenerator[int, None] # [unsubscriptable-object] -collections.abc.AsyncIterable[int] # [unsubscriptable-object] -collections.abc.AsyncIterator[int] # [unsubscriptable-object] -collections.abc.Awaitable[int] # [unsubscriptable-object] - -# contextlib -contextlib.AbstractContextManager[int] # [unsubscriptable-object] -contextlib.AbstractAsyncContextManager[int] # [unsubscriptable-object] - -# re -re.Pattern[str] # [unsubscriptable-object] -re.Match[str] # [unsubscriptable-object] - - -# unsubscriptable types -collections.abc.Hashable -collections.abc.Sized -collections.abc.Hashable[int] # [unsubscriptable-object] -collections.abc.Sized[int] # [unsubscriptable-object] - -# subscriptable with Python 3.9 -collections.abc.ByteString[int] # [unsubscriptable-object] - - -# Missing implementation for 'collections.abc' derived classes -class DerivedHashable(collections.abc.Hashable): # [abstract-method] # __hash__ - pass - -class DerivedIterable(collections.abc.Iterable[int]): # [unsubscriptable-object] - pass - -class DerivedCollection(collections.abc.Collection[int]): # [unsubscriptable-object] - pass - - -# No implementation required for 'builtins' and 'collections' types -class DerivedList(list[int]): # [unsubscriptable-object] - pass - -class DerivedSet(set[int]): # [unsubscriptable-object] - pass - -class DerivedOrderedDict(collections.OrderedDict[int, str]): # [unsubscriptable-object] - pass - -class DerivedListIterable(list[collections.abc.Iterable[int]]): # [unsubscriptable-object,unsubscriptable-object] - pass - - -# Multiple generic base classes -class DerivedMultiple(collections.abc.Sized, collections.abc.Hashable): # [abstract-method,abstract-method] - pass - -class CustomAbstractCls1(abc.ABC): - pass -class CustomAbstractCls2(collections.abc.Sized, collections.abc.Iterable[CustomAbstractCls1]): # [abstract-method,unsubscriptable-object] # __len__ - pass -class CustomImplementation(CustomAbstractCls2): # [abstract-method] # __len__ - pass - - -# Type annotations -var_tuple: tuple[int, int] # [unsubscriptable-object] -var_dict: dict[int, str] # [unsubscriptable-object] -var_orderedDict: collections.OrderedDict[int, str] # [unsubscriptable-object] -var_container: collections.abc.Container[int] # [unsubscriptable-object] -var_sequence: collections.abc.Sequence[int] # [unsubscriptable-object] -var_iterable: collections.abc.Iterable[int] # [unsubscriptable-object] -var_awaitable: collections.abc.Awaitable[int] # [unsubscriptable-object] -var_contextmanager: contextlib.AbstractContextManager[int] # [unsubscriptable-object] -var_pattern: re.Pattern[int] # [unsubscriptable-object] -var_bytestring: collections.abc.ByteString -var_hashable: collections.abc.Hashable -var_sized: collections.abc.Sized - -# Type annotation with unsubscriptable type -var_int: int[int] # [unsubscriptable-object] -var_hashable2: collections.abc.Hashable[int] # [unsubscriptable-object] -var_sized2: collections.abc.Sized[int] # [unsubscriptable-object] - -# subscriptable with Python 3.9 -var_bytestring2: collections.abc.ByteString[int] # [unsubscriptable-object] diff --git a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.rc b/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 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 deleted file mode 100644 index 0dd989f2e8..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_collections_py37_with_typing.txt +++ /dev/null @@ -1,67 +0,0 @@ -unsubscriptable-object:17:0:17:5::Value 'tuple' is unsubscriptable:UNDEFINED -unsubscriptable-object:18:0:18:4::Value 'type' is unsubscriptable:UNDEFINED -unsubscriptable-object:19:0:19:24::Value 'collections.abc.Callable' is unsubscriptable:UNDEFINED -unsubscriptable-object:22:0:22:4::Value 'dict' is unsubscriptable:UNDEFINED -unsubscriptable-object:23:0:23:4::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:24:0:24:3::Value 'set' is unsubscriptable:UNDEFINED -unsubscriptable-object:25:0:25:9::Value 'frozenset' is unsubscriptable:UNDEFINED -unsubscriptable-object:28:0:28:23::Value 'collections.defaultdict' is unsubscriptable:UNDEFINED -unsubscriptable-object:29:0:29:23::Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED -unsubscriptable-object:30:0:30:20::Value 'collections.ChainMap' is unsubscriptable:UNDEFINED -unsubscriptable-object:31:0:31:19::Value 'collections.Counter' is unsubscriptable:UNDEFINED -unsubscriptable-object:32:0:32:17::Value 'collections.deque' is unsubscriptable:UNDEFINED -unsubscriptable-object:35:0:35:19::Value 'collections.abc.Set' is unsubscriptable:UNDEFINED -unsubscriptable-object:36:0:36:26::Value 'collections.abc.Collection' is unsubscriptable:UNDEFINED -unsubscriptable-object:37:0:37:25::Value 'collections.abc.Container' is unsubscriptable:UNDEFINED -unsubscriptable-object:38:0:38:25::Value 'collections.abc.ItemsView' is unsubscriptable:UNDEFINED -unsubscriptable-object:39:0:39:24::Value 'collections.abc.KeysView' is unsubscriptable:UNDEFINED -unsubscriptable-object:40:0:40:23::Value 'collections.abc.Mapping' is unsubscriptable:UNDEFINED -unsubscriptable-object:41:0:41:27::Value 'collections.abc.MappingView' is unsubscriptable:UNDEFINED -unsubscriptable-object:42:0:42:30::Value 'collections.abc.MutableMapping' is unsubscriptable:UNDEFINED -unsubscriptable-object:43:0:43:31::Value 'collections.abc.MutableSequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:44:0:44:26::Value 'collections.abc.MutableSet' is unsubscriptable:UNDEFINED -unsubscriptable-object:45:0:45:24::Value 'collections.abc.Sequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:46:0:46:26::Value 'collections.abc.ValuesView' is unsubscriptable:UNDEFINED -unsubscriptable-object:48:0:48:24::Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:49:0:49:24::Value 'collections.abc.Iterator' is unsubscriptable:UNDEFINED -unsubscriptable-object:50:0:50:25::Value 'collections.abc.Generator' is unsubscriptable:UNDEFINED -unsubscriptable-object:51:0:51:26::Value 'collections.abc.Reversible' is unsubscriptable:UNDEFINED -unsubscriptable-object:53:0:53:25::Value 'collections.abc.Coroutine' is unsubscriptable:UNDEFINED -unsubscriptable-object:53:26:53:30::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:54:0:54:30::Value 'collections.abc.AsyncGenerator' is unsubscriptable:UNDEFINED -unsubscriptable-object:55:0:55:29::Value 'collections.abc.AsyncIterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:56:0:56:29::Value 'collections.abc.AsyncIterator' is unsubscriptable:UNDEFINED -unsubscriptable-object:57:0:57:25::Value 'collections.abc.Awaitable' is unsubscriptable:UNDEFINED -unsubscriptable-object:60:0:60:33::Value 'contextlib.AbstractContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:61:0:61:38::Value 'contextlib.AbstractAsyncContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:64:0:64:10::Value 're.Pattern' is unsubscriptable:UNDEFINED -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 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 -unsubscriptable-object:93:17:93:20:DerivedSet:Value 'set' is unsubscriptable:UNDEFINED -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 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 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 -unsubscriptable-object:119:15:119:40::Value 'collections.abc.Container' is unsubscriptable:UNDEFINED -unsubscriptable-object:120:14:120:38::Value 'collections.abc.Sequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:121:14:121:38::Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:122:15:122:40::Value 'collections.abc.Awaitable' is unsubscriptable:UNDEFINED -unsubscriptable-object:123:20:123:53::Value 'contextlib.AbstractContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:124:13:124:23::Value 're.Pattern' is unsubscriptable:UNDEFINED -unsubscriptable-object:130:9:130:12::Value 'int' is unsubscriptable:UNDEFINED -unsubscriptable-object:131:15:131:39::Value 'collections.abc.Hashable' is unsubscriptable:UNDEFINED -unsubscriptable-object:132:12:132:33::Value 'collections.abc.Sized' is unsubscriptable:UNDEFINED -unsubscriptable-object:135:17:135:43::Value 'collections.abc.ByteString' is unsubscriptable:UNDEFINED diff --git a/tests/functional/g/generic_alias/generic_alias_mixed_py37.py b/tests/functional/g/generic_alias/generic_alias_mixed_py37.py deleted file mode 100644 index cb7a4d0f43..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_mixed_py37.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Test generic alias support with mix of typing.py and stdlib types. - -Possible with postponed evaluation enabled, starting with PY37. -""" -# flake8: noqa -# pylint: disable=missing-docstring,pointless-statement -# pylint: disable=too-few-public-methods,multiple-statements,line-too-long -from __future__ import annotations - -import collections -import collections.abc -import contextlib -import re -import typing - -# Type annotations -var_orderedDict: collections.OrderedDict[int, str] -var_container: collections.abc.Container[int] -var_sequence: collections.abc.Sequence[int] -var_iterable: collections.abc.Iterable[int] -var_awaitable: collections.abc.Awaitable[int] -var_pattern: re.Pattern[int] -var_bytestring: collections.abc.ByteString -var_hashable: collections.abc.Hashable -var_ContextManager: contextlib.AbstractContextManager[int] - - -# No implementation required for 'builtins' -class DerivedListIterable(typing.List[typing.Iterable[int]]): - pass - - -# Missing implementation for 'collections.abc' derived classes -class DerivedHashable(typing.Hashable): # [abstract-method] # __hash__ - pass - -class DerivedIterable(typing.Iterable[int]): # [abstract-method] # __iter__ - pass - -class DerivedCollection(typing.Collection[int]): # [abstract-method,abstract-method,abstract-method] # __contains__, __iter__, __len__ - pass diff --git a/tests/functional/g/generic_alias/generic_alias_mixed_py37.rc b/tests/functional/g/generic_alias/generic_alias_mixed_py37.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_mixed_py37.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt b/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt deleted file mode 100644 index 188039c60d..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_mixed_py37.txt +++ /dev/null @@ -1,5 +0,0 @@ -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.rc b/tests/functional/g/generic_alias/generic_alias_mixed_py39.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_mixed_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.py b/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.py deleted file mode 100644 index 02cbcf081c..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Test generic alias support for stdlib types (added in PY39). - -In type annotation context, they can be used with postponed evaluation enabled, -starting with PY37. -""" -# flake8: noqa -# pylint: disable=missing-docstring,pointless-statement,invalid-name -# pylint: disable=too-few-public-methods,multiple-statements,line-too-long -from __future__ import annotations - -import abc -import collections -import collections.abc -import contextlib -import re - - -# ----- unsubscriptable (even with postponed evaluation) ----- -# special -tuple[int, int] # [unsubscriptable-object] -type[int] # [unsubscriptable-object] -collections.abc.Callable[[int], str] # [unsubscriptable-object] - -# builtins -dict[int, str] # [unsubscriptable-object] -list[int] # [unsubscriptable-object] -set[int] # [unsubscriptable-object] -frozenset[int] # [unsubscriptable-object] - -# collections -collections.defaultdict[int, str] # [unsubscriptable-object] -collections.OrderedDict[int, str] # [unsubscriptable-object] -collections.ChainMap[int, str] # [unsubscriptable-object] -collections.Counter[int] # [unsubscriptable-object] -collections.deque[int] # [unsubscriptable-object] - -# collections.abc -collections.abc.Set[int] # [unsubscriptable-object] -collections.abc.Collection[int] # [unsubscriptable-object] -collections.abc.Container[int] # [unsubscriptable-object] -collections.abc.ItemsView[int, str] # [unsubscriptable-object] -collections.abc.KeysView[int] # [unsubscriptable-object] -collections.abc.Mapping[int, str] # [unsubscriptable-object] -collections.abc.MappingView[int] # [unsubscriptable-object] -collections.abc.MutableMapping[int, str] # [unsubscriptable-object] -collections.abc.MutableSequence[int] # [unsubscriptable-object] -collections.abc.MutableSet[int] # [unsubscriptable-object] -collections.abc.Sequence[int] # [unsubscriptable-object] -collections.abc.ValuesView[int] # [unsubscriptable-object] - -collections.abc.Iterable[int] # [unsubscriptable-object] -collections.abc.Iterator[int] # [unsubscriptable-object] -collections.abc.Generator[int, None, None] # [unsubscriptable-object] -collections.abc.Reversible[int] # [unsubscriptable-object] - -collections.abc.Coroutine[list[str], str, int] # [unsubscriptable-object,unsubscriptable-object] -collections.abc.AsyncGenerator[int, None] # [unsubscriptable-object] -collections.abc.AsyncIterable[int] # [unsubscriptable-object] -collections.abc.AsyncIterator[int] # [unsubscriptable-object] -collections.abc.Awaitable[int] # [unsubscriptable-object] - -# contextlib -contextlib.AbstractContextManager[int] # [unsubscriptable-object] -contextlib.AbstractAsyncContextManager[int] # [unsubscriptable-object] - -# re -re.Pattern[str] # [unsubscriptable-object] -re.Match[str] # [unsubscriptable-object] - - -# unsubscriptable types -collections.abc.Hashable -collections.abc.Sized -collections.abc.Hashable[int] # [unsubscriptable-object] -collections.abc.Sized[int] # [unsubscriptable-object] - -# subscriptable with Python 3.9 -collections.abc.ByteString[int] # [unsubscriptable-object] - - -# Missing implementation for 'collections.abc' derived classes -class DerivedHashable(collections.abc.Hashable): # [abstract-method] # __hash__ - pass - -class DerivedIterable(collections.abc.Iterable[int]): # [unsubscriptable-object] - pass - -class DerivedCollection(collections.abc.Collection[int]): # [unsubscriptable-object] - pass - - -# No implementation required for 'builtins' and 'collections' types -class DerivedList(list[int]): # [unsubscriptable-object] - pass - -class DerivedSet(set[int]): # [unsubscriptable-object] - pass - -class DerivedOrderedDict(collections.OrderedDict[int, str]): # [unsubscriptable-object] - pass - -class DerivedListIterable(list[collections.abc.Iterable[int]]): # [unsubscriptable-object,unsubscriptable-object] - pass - - -# Multiple generic base classes -class DerivedMultiple(collections.abc.Sized, collections.abc.Hashable): # [abstract-method,abstract-method] - pass - -class CustomAbstractCls1(abc.ABC): - pass -class CustomAbstractCls2(collections.abc.Sized, collections.abc.Iterable[CustomAbstractCls1]): # [abstract-method,unsubscriptable-object] # __len__ - pass -class CustomImplementation(CustomAbstractCls2): # [abstract-method] # __len__ - pass - - - -# ----- subscriptable (with postponed evaluation) ----- -# special -var_tuple: tuple[int, int] -var_type: type[int] -var_callable: collections.abc.Callable[[int], str] - -# builtins -var_dict: dict[int, str] -var_list: list[int] -var_set: set[int] -var_frozenset: frozenset[int] - -# collections -var_defaultdict: collections.defaultdict[int, str] -var_OrderedDict: collections.OrderedDict[int, str] -var_ChainMap: collections.ChainMap[int, str] -var_Counter: collections.Counter[int] -var_deque: collections.deque[int] - -# collections.abc -var_abc_set: collections.abc.Set[int] -var_abc_collection: collections.abc.Collection[int] -var_abc_container: collections.abc.Container[int] -var_abc_ItemsView: collections.abc.ItemsView[int, str] -var_abc_KeysView: collections.abc.KeysView[int] -var_abc_Mapping: collections.abc.Mapping[int, str] -var_abc_MappingView: collections.abc.MappingView[int] -var_abc_MutableMapping: collections.abc.MutableMapping[int, str] -var_abc_MutableSequence: collections.abc.MutableSequence[int] -var_abc_MutableSet: collections.abc.MutableSet[int] -var_abc_Sequence: collections.abc.Sequence[int] -var_abc_ValuesView: collections.abc.ValuesView[int] - -var_abc_Iterable: collections.abc.Iterable[int] -var_abc_Iterator: collections.abc.Iterator[int] -var_abc_Generator: collections.abc.Generator[int, None, None] -var_abc_Reversible: collections.abc.Reversible[int] - -var_abc_Coroutine: collections.abc.Coroutine[list[str], str, int] -var_abc_AsyncGenerator: collections.abc.AsyncGenerator[int, None] -var_abc_AsyncIterable: collections.abc.AsyncIterable[int] -var_abc_AsyncIterator: collections.abc.AsyncIterator[int] -var_abc_Awaitable: collections.abc.Awaitable[int] - -# contextlib -var_ContextManager: contextlib.AbstractContextManager[int] -var_AsyncContextManager: contextlib.AbstractAsyncContextManager[int] - -# re -var_re_Pattern: re.Pattern[str] -var_re_Match: re.Match[str] - - -# unsubscriptable types -var_abc_Hashable: collections.abc.Hashable -var_abc_Sized: collections.abc.Sized -var_abc_Hashable2: collections.abc.Hashable[int] # string annotations aren't checked -var_abc_Sized2: collections.abc.Sized[int] # string annotations aren't checked - -# subscriptable with Python 3.9 -var_abc_ByteString: collections.abc.ByteString[int] - - -# Generic in type stubs only -> string annotations aren't checked -class A: - ... - -var_a1: A[str] # string annotations aren't checked -var_a2: "A[str]" # string annotations aren't checked -class B(A[str]): # [unsubscriptable-object] - ... diff --git a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.rc b/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 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 deleted file mode 100644 index cbf46bfef4..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_postponed_evaluation_py37.txt +++ /dev/null @@ -1,55 +0,0 @@ -unsubscriptable-object:20:0:20:5::Value 'tuple' is unsubscriptable:UNDEFINED -unsubscriptable-object:21:0:21:4::Value 'type' is unsubscriptable:UNDEFINED -unsubscriptable-object:22:0:22:24::Value 'collections.abc.Callable' is unsubscriptable:UNDEFINED -unsubscriptable-object:25:0:25:4::Value 'dict' is unsubscriptable:UNDEFINED -unsubscriptable-object:26:0:26:4::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:27:0:27:3::Value 'set' is unsubscriptable:UNDEFINED -unsubscriptable-object:28:0:28:9::Value 'frozenset' is unsubscriptable:UNDEFINED -unsubscriptable-object:31:0:31:23::Value 'collections.defaultdict' is unsubscriptable:UNDEFINED -unsubscriptable-object:32:0:32:23::Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED -unsubscriptable-object:33:0:33:20::Value 'collections.ChainMap' is unsubscriptable:UNDEFINED -unsubscriptable-object:34:0:34:19::Value 'collections.Counter' is unsubscriptable:UNDEFINED -unsubscriptable-object:35:0:35:17::Value 'collections.deque' is unsubscriptable:UNDEFINED -unsubscriptable-object:38:0:38:19::Value 'collections.abc.Set' is unsubscriptable:UNDEFINED -unsubscriptable-object:39:0:39:26::Value 'collections.abc.Collection' is unsubscriptable:UNDEFINED -unsubscriptable-object:40:0:40:25::Value 'collections.abc.Container' is unsubscriptable:UNDEFINED -unsubscriptable-object:41:0:41:25::Value 'collections.abc.ItemsView' is unsubscriptable:UNDEFINED -unsubscriptable-object:42:0:42:24::Value 'collections.abc.KeysView' is unsubscriptable:UNDEFINED -unsubscriptable-object:43:0:43:23::Value 'collections.abc.Mapping' is unsubscriptable:UNDEFINED -unsubscriptable-object:44:0:44:27::Value 'collections.abc.MappingView' is unsubscriptable:UNDEFINED -unsubscriptable-object:45:0:45:30::Value 'collections.abc.MutableMapping' is unsubscriptable:UNDEFINED -unsubscriptable-object:46:0:46:31::Value 'collections.abc.MutableSequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:47:0:47:26::Value 'collections.abc.MutableSet' is unsubscriptable:UNDEFINED -unsubscriptable-object:48:0:48:24::Value 'collections.abc.Sequence' is unsubscriptable:UNDEFINED -unsubscriptable-object:49:0:49:26::Value 'collections.abc.ValuesView' is unsubscriptable:UNDEFINED -unsubscriptable-object:51:0:51:24::Value 'collections.abc.Iterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:52:0:52:24::Value 'collections.abc.Iterator' is unsubscriptable:UNDEFINED -unsubscriptable-object:53:0:53:25::Value 'collections.abc.Generator' is unsubscriptable:UNDEFINED -unsubscriptable-object:54:0:54:26::Value 'collections.abc.Reversible' is unsubscriptable:UNDEFINED -unsubscriptable-object:56:0:56:25::Value 'collections.abc.Coroutine' is unsubscriptable:UNDEFINED -unsubscriptable-object:56:26:56:30::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:57:0:57:30::Value 'collections.abc.AsyncGenerator' is unsubscriptable:UNDEFINED -unsubscriptable-object:58:0:58:29::Value 'collections.abc.AsyncIterable' is unsubscriptable:UNDEFINED -unsubscriptable-object:59:0:59:29::Value 'collections.abc.AsyncIterator' is unsubscriptable:UNDEFINED -unsubscriptable-object:60:0:60:25::Value 'collections.abc.Awaitable' is unsubscriptable:UNDEFINED -unsubscriptable-object:63:0:63:33::Value 'contextlib.AbstractContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:64:0:64:38::Value 'contextlib.AbstractAsyncContextManager' is unsubscriptable:UNDEFINED -unsubscriptable-object:67:0:67:10::Value 're.Pattern' is unsubscriptable:UNDEFINED -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 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 -unsubscriptable-object:96:17:96:20:DerivedSet:Value 'set' is unsubscriptable:UNDEFINED -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 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 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_py39.rc b/tests/functional/g/generic_alias/generic_alias_related_py39.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/g/generic_alias/generic_alias_related_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/g/generic_class_syntax_py312.py b/tests/functional/g/generic_class_syntax_py312.py index bbfff1c6ad..bd5d1ba838 100644 --- a/tests/functional/g/generic_class_syntax_py312.py +++ b/tests/functional/g/generic_class_syntax_py312.py @@ -2,7 +2,7 @@ class Entity[_T: float]: last_update: int | None = None - def __init__(self, data: _T) -> None: # [undefined-variable] # false-positive + def __init__(self, data: _T) -> None: self.data = data @@ -28,6 +28,6 @@ def __init__(self): self.update_interval = 0 -class Child[_T](Parent[_T]): # [undefined-variable] # false-positive +class Child[_T](Parent[_T]): def func(self): self.update_interval = None diff --git a/tests/functional/g/generic_class_syntax_py312.txt b/tests/functional/g/generic_class_syntax_py312.txt deleted file mode 100644 index bd5fbbe7ee..0000000000 --- a/tests/functional/g/generic_class_syntax_py312.txt +++ /dev/null @@ -1,2 +0,0 @@ -undefined-variable:5:29:5:31:Entity.__init__:Undefined variable '_T':UNDEFINED -undefined-variable:31:23:31:25:Child:Undefined variable '_T':UNDEFINED diff --git a/tests/functional/i/import_error.rc b/tests/functional/i/import_error.rc index 7278ff8f17..efd9369c9e 100644 --- a/tests/functional/i/import_error.rc +++ b/tests/functional/i/import_error.rc @@ -4,7 +4,3 @@ enable=multiple-imports [TYPECHECK] ignored-modules=external_module,fake_module.submodule,foo,bar,*_ignored - -[testoptions] -# TODO: PY3.9: This does pass on PyPy 3.9 -except_implementations=PyPy diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py b/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py index dd6f9fbe12..e6e285341b 100644 --- a/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring.py @@ -1,4 +1,4 @@ # pylint: disable=missing-module-docstring dictionary = {'0': 0} -f_string = f'{dictionary["0"]}' +F_STRING = f'{dictionary["0"]}' diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py index 5e952af196..323bc6a07b 100644 --- a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312.py @@ -1,5 +1,5 @@ # pylint: disable=missing-module-docstring dictionary = {'0': 0} -# quotes are inconsistent when targetting Python 3.12 (use single quotes) -f_string = f'{dictionary["0"]}' # [inconsistent-quotes] +# quotes are inconsistent when targeting Python 3.12 (use single quotes) +F_STRING = f'{dictionary["0"]}' # [inconsistent-quotes] diff --git a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py index 5b53a19bb5..c7099e4710 100644 --- a/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py +++ b/tests/functional/i/inconsistent/inconsistent_quotes_fstring_py312_311.py @@ -1,5 +1,5 @@ # pylint: disable=missing-module-docstring dictionary = {'0': 0} -# quotes are consistent when targetting 3.11 and earlier (cannot use single quotes here) -f_string = f'{dictionary["0"]}' +# quotes are consistent when targeting 3.11 and earlier (cannot use single quotes here) +F_STRING = f'{dictionary["0"]}' diff --git a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.py b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.py index 13878aad69..ea5154752a 100644 --- a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.py +++ b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.py @@ -4,6 +4,14 @@ import sys import typing +from pylint.constants import PY311_PLUS + +if PY311_PLUS: + from typing import assert_never # pylint: disable=no-name-in-module +else: + from typing_extensions import assert_never + + def parser_error(msg) -> typing.NoReturn: # pylint: disable=unused-argument sys.exit(1) @@ -11,7 +19,7 @@ def parser_error_nortype(msg): # pylint: disable=unused-argument sys.exit(2) -from typing import NoReturn # pylint: disable=wrong-import-position +from typing import NoReturn # pylint: disable=wrong-import-position,wrong-import-order def parser_error_name(msg) -> NoReturn: # pylint: disable=unused-argument sys.exit(3) @@ -95,3 +103,34 @@ def bug_pylint_8747_incorrect_annotation(self, s: str) -> int: return n except ValueError: self._falsely_no_return_method() + +# https://github.com/pylint-dev/pylint/issues/7565 +def never_is_handled_like_noreturn(arg: typing.Union[int, str]) -> int: + if isinstance(arg, int): + return 1 + if isinstance(arg, str): + return 2 + assert_never(arg) + + +def declared_to_not_return() -> None: + return + + +def config_takes_precedence_over_inference(arg: typing.Union[int, str]) -> int: + if isinstance(arg, int): + return 1 + if isinstance(arg, str): + return 2 + declared_to_not_return() + + +def unrelated() -> None: + return + + +# +1: [inconsistent-return-statements] +def ensure_message(arg: typing.Union[int, str]) -> int: + if isinstance(arg, int): + return 1 + unrelated() diff --git a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.rc b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.rc index dd6cbb5989..fdeed8ed4d 100644 --- a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.rc +++ b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.rc @@ -1,2 +1,2 @@ [REFACTORING] -never-returning-functions=sys.exit,sys.getdefaultencoding +never-returning-functions=sys.exit,sys.getdefaultencoding,inconsistent_returns_noreturn.declared_to_not_return diff --git a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt index e2ad258f72..da3cfcc7f5 100644 --- a/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt +++ b/tests/functional/i/inconsistent/inconsistent_returns_noreturn.txt @@ -1,2 +1,3 @@ -inconsistent-return-statements:32:0:32:25:bug_pylint_4122_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED -inconsistent-return-statements:77:4:77:29:ClassUnderTest.bug_pylint_8747_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:40:0:40:25:bug_pylint_4122_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:85:4:85:29:ClassUnderTest.bug_pylint_8747_wrong:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED +inconsistent-return-statements:133:0:133:18:ensure_message:Either all return statements in a function should return an expression, or none of them should.:UNDEFINED diff --git a/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.py b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.py index aa37a45306..3189fb8f44 100644 --- a/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.py +++ b/tests/functional/i/invalid/invalid_exceptions/invalid_exceptions_raised.py @@ -1,17 +1,13 @@ # pylint:disable=too-few-public-methods,import-error,missing-docstring, not-callable, import-outside-toplevel -"""test pb with exceptions and old/new style classes""" +"""test pb with exceptions and classes""" class ValidException(Exception): """Valid Exception.""" -class OldStyleClass: - """Not an exception.""" - class NewStyleClass: """Not an exception.""" - def good_case(): """raise""" raise ValidException('hop') @@ -31,22 +27,10 @@ def good_case3(): import io raise io.BlockingIOError -def bad_case0(): - """raise""" - # +2:<3.0:[nonstandard-exception] - # +1:>=3.0:[raising-non-exception] - raise OldStyleClass('hop') - def bad_case1(): """raise""" raise NewStyleClass() # [raising-non-exception] -def bad_case2(): - """raise""" - # +2:<3.0:[nonstandard-exception] - # +1:>=3.0:[raising-non-exception] - raise OldStyleClass('hop') - def bad_case3(): """raise""" raise NewStyleClass # [raising-non-exception] 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 f2ccd8a052..863e5baf37 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,9 @@ -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 +raising-non-exception:32:4:32:25:bad_case1:Raising a class which doesn't inherit from BaseException:INFERENCE +raising-non-exception:36:4:36:23:bad_case3:Raising a class which doesn't inherit from BaseException:INFERENCE +notimplemented-raised:40:4:40:31:bad_case4:NotImplemented raised - should raise NotImplementedError:HIGH +raising-bad-type:44:4:44:11:bad_case5:Raising int while only classes or instances are allowed:INFERENCE +raising-bad-type:48:4:48:14:bad_case6:Raising NoneType while only classes or instances are allowed:INFERENCE +raising-non-exception:52:4:52:14:bad_case7:Raising a class which doesn't inherit from BaseException:INFERENCE +raising-non-exception:56:4:56:15:bad_case8:Raising a class which doesn't inherit from BaseException:INFERENCE +raising-non-exception:60:4:60:14:bad_case9:Raising a class which doesn't inherit from BaseException:INFERENCE +raising-bad-type:94:4:94:18:bad_case10:Raising str while only classes or instances are allowed:INFERENCE diff --git a/tests/functional/i/invalid/invalid_field_call.py b/tests/functional/i/invalid/invalid_field_call.py index 43ff41c429..62d62733ae 100644 --- a/tests/functional/i/invalid/invalid_field_call.py +++ b/tests/functional/i/invalid/invalid_field_call.py @@ -34,7 +34,6 @@ class DC: mc.field() a: float = field(init=False) b: float = dc.field(init=False) - # TODO(remove py3.9 min) pylint: disable-next=unsubscriptable-object c: list[float] = [field(), field()] # [invalid-field-call, invalid-field-call] @dc.dataclass @@ -42,7 +41,6 @@ class IsAlsoDC: field() # [invalid-field-call] a: float = field(init=False) b: float = dc.field(init=False) - # TODO(remove py3.9 min) pylint: disable-next=unsubscriptable-object c: list[float] = [field(), field()] # [invalid-field-call, invalid-field-call] @dc.dataclass(frozen=True) diff --git a/tests/functional/i/invalid/invalid_field_call.txt b/tests/functional/i/invalid/invalid_field_call.txt index c672a2bfea..5e4f0fe244 100644 --- a/tests/functional/i/invalid/invalid_field_call.txt +++ b/tests/functional/i/invalid/invalid_field_call.txt @@ -6,9 +6,9 @@ invalid-field-call:27:4:27:14:NotADataClass:Invalid usage of field(), it should invalid-field-call:28:15:28:35:NotADataClass:Invalid usage of field(), it should be used within a dataclass or the make_dataclass() function.:INFERENCE invalid-field-call:32:4:32:11:DC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE invalid-field-call:33:4:33:14:DC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE -invalid-field-call:38:22:38:29:DC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE -invalid-field-call:38:31:38:38:DC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE -invalid-field-call:42:4:42:11:IsAlsoDC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE -invalid-field-call:46:22:46:29:IsAlsoDC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE -invalid-field-call:46:31:46:38:IsAlsoDC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE -invalid-field-call:61:15:61:32:AlsoNotADataClass:Invalid usage of field(), it should be used within a dataclass or the make_dataclass() function.:INFERENCE +invalid-field-call:37:22:37:29:DC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE +invalid-field-call:37:31:37:38:DC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE +invalid-field-call:41:4:41:11:IsAlsoDC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE +invalid-field-call:44:22:44:29:IsAlsoDC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE +invalid-field-call:44:31:44:38:IsAlsoDC:Invalid usage of field(), it should be the value of an assignment within a dataclass.:INFERENCE +invalid-field-call:59:15:59:32:AlsoNotADataClass:Invalid usage of field(), it should be used within a dataclass or the make_dataclass() function.:INFERENCE diff --git a/tests/functional/l/logging/logging_too_many_args.py b/tests/functional/l/logging/logging_too_many_args.py deleted file mode 100644 index 3aff250f71..0000000000 --- a/tests/functional/l/logging/logging_too_many_args.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Tests for logging-too-many-args""" -import logging - -logging.error("constant string", 1, 2) # [logging-too-many-args] -logging.error("{}", 1, 2) # [logging-too-many-args] -logging.error("{0}", 1, 2) # [logging-too-many-args] -logging.error("{}, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] -logging.error("{0}, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] diff --git a/tests/functional/l/logging/logging_too_many_args.txt b/tests/functional/l/logging/logging_too_many_args.txt deleted file mode 100644 index 91aa7ce3a2..0000000000 --- a/tests/functional/l/logging/logging_too_many_args.txt +++ /dev/null @@ -1,5 +0,0 @@ -logging-too-many-args:4:0:4:38::Too many arguments for logging format string:UNDEFINED -logging-too-many-args:5:0:5:25::Too many arguments for logging format string:UNDEFINED -logging-too-many-args:6:0:6:26::Too many arguments for logging format string:UNDEFINED -logging-too-many-args:7:0:7:48::Too many arguments for logging format string:UNDEFINED -logging-too-many-args:8:0:8:49::Too many arguments for logging format string:UNDEFINED diff --git a/tests/functional/l/logging/logging_too_many_args_new_style.py b/tests/functional/l/logging/logging_too_many_args_new_style.py new file mode 100644 index 0000000000..7eff4bc621 --- /dev/null +++ b/tests/functional/l/logging/logging_too_many_args_new_style.py @@ -0,0 +1,20 @@ +"""Tests for logging-too-many-args with logging-format-style=new +for both new style and old style.""" +import logging + +logging.error("constant string", 1, 2) # [logging-too-many-args] +logging.error("{}", 1, 2) # [logging-too-many-args] +logging.error("{0}", 1, 2) # [logging-too-many-args] +logging.error("{}, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +logging.error("{0}, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +# Regression test for https://github.com/pylint-dev/pylint/issues/9118 +logging.warning( "The frequency is: {:.2f} MHz", 2.3 ) + +logging.error("constant string", 1, 2) # [logging-too-many-args] +logging.error("%s", 1, 2) # [logging-too-many-args] +logging.error("%s", 1, 2) # [logging-too-many-args] +logging.error("%s, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +logging.error("%s, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +# Regression test for https://github.com/pylint-dev/pylint/issues/9118 +# This is a false positive currently +logging.warning( "The frequency is: %f MHz", 2.3 ) # [logging-too-many-args] diff --git a/tests/functional/l/logging/logging_too_many_args.rc b/tests/functional/l/logging/logging_too_many_args_new_style.rc similarity index 100% rename from tests/functional/l/logging/logging_too_many_args.rc rename to tests/functional/l/logging/logging_too_many_args_new_style.rc diff --git a/tests/functional/l/logging/logging_too_many_args_new_style.txt b/tests/functional/l/logging/logging_too_many_args_new_style.txt new file mode 100644 index 0000000000..1a1ffc7cf1 --- /dev/null +++ b/tests/functional/l/logging/logging_too_many_args_new_style.txt @@ -0,0 +1,11 @@ +logging-too-many-args:5:0:5:38::Too many arguments for logging format string:HIGH +logging-too-many-args:6:0:6:25::Too many arguments for logging format string:HIGH +logging-too-many-args:7:0:7:26::Too many arguments for logging format string:HIGH +logging-too-many-args:8:0:8:48::Too many arguments for logging format string:HIGH +logging-too-many-args:9:0:9:49::Too many arguments for logging format string:HIGH +logging-too-many-args:13:0:13:38::Too many arguments for logging format string:HIGH +logging-too-many-args:14:0:14:25::Too many arguments for logging format string:HIGH +logging-too-many-args:15:0:15:25::Too many arguments for logging format string:HIGH +logging-too-many-args:16:0:16:48::Too many arguments for logging format string:HIGH +logging-too-many-args:17:0:17:48::Too many arguments for logging format string:HIGH +logging-too-many-args:20:0:20:50::Too many arguments for logging format string:HIGH diff --git a/tests/functional/l/logging/logging_too_many_args_old_style.py b/tests/functional/l/logging/logging_too_many_args_old_style.py new file mode 100644 index 0000000000..1d5808004b --- /dev/null +++ b/tests/functional/l/logging/logging_too_many_args_old_style.py @@ -0,0 +1,20 @@ +"""Tests for logging-too-many-args using logging-format-style=old +for both new style and old style.""" +import logging + +logging.error("constant string", 1, 2) # [logging-too-many-args] +logging.error("{}", 1, 2) # [logging-too-many-args] +logging.error("{0}", 1, 2) # [logging-too-many-args] +logging.error("{}, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +logging.error("{0}, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +# Regression test for https://github.com/pylint-dev/pylint/issues/9118 +# This is a false positive currently +logging.warning( "The frequency is: {:.2f} MHz", 2.3 ) # [logging-too-many-args] + +logging.error("constant string", 1, 2) # [logging-too-many-args] +logging.error("%s", 1, 2) # [logging-too-many-args] +logging.error("%s", 1, 2) # [logging-too-many-args] +logging.error("%s, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +logging.error("%s, {named}", 1, 2, {"named": 1}) # [logging-too-many-args] +# Regression test for https://github.com/pylint-dev/pylint/issues/9118 +logging.warning( "The frequency is: %f MHz", 2.3 ) diff --git a/tests/functional/l/logging/logging_too_many_args_old_style.rc b/tests/functional/l/logging/logging_too_many_args_old_style.rc new file mode 100644 index 0000000000..7df00b851a --- /dev/null +++ b/tests/functional/l/logging/logging_too_many_args_old_style.rc @@ -0,0 +1,2 @@ +[LOGGING] +logging-format-style=old diff --git a/tests/functional/l/logging/logging_too_many_args_old_style.txt b/tests/functional/l/logging/logging_too_many_args_old_style.txt new file mode 100644 index 0000000000..16fd5520fd --- /dev/null +++ b/tests/functional/l/logging/logging_too_many_args_old_style.txt @@ -0,0 +1,11 @@ +logging-too-many-args:5:0:5:38::Too many arguments for logging format string:HIGH +logging-too-many-args:6:0:6:25::Too many arguments for logging format string:HIGH +logging-too-many-args:7:0:7:26::Too many arguments for logging format string:HIGH +logging-too-many-args:8:0:8:48::Too many arguments for logging format string:HIGH +logging-too-many-args:9:0:9:49::Too many arguments for logging format string:HIGH +logging-too-many-args:12:0:12:54::Too many arguments for logging format string:HIGH +logging-too-many-args:14:0:14:38::Too many arguments for logging format string:HIGH +logging-too-many-args:15:0:15:25::Too many arguments for logging format string:HIGH +logging-too-many-args:16:0:16:25::Too many arguments for logging format string:HIGH +logging-too-many-args:17:0:17:48::Too many arguments for logging format string:HIGH +logging-too-many-args:18:0:18:48::Too many arguments for logging format string:HIGH diff --git a/tests/functional/m/method_cache_max_size_none_py39.py b/tests/functional/m/method_cache_max_size_none_py39.py deleted file mode 100644 index d0bd65755e..0000000000 --- a/tests/functional/m/method_cache_max_size_none_py39.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Tests for method-cache-max-size-none""" -# pylint: disable=missing-function-docstring, reimported, too-few-public-methods -# pylint: disable=missing-class-docstring, function-redefined - -import functools -import functools as aliased_functools -from functools import cache -from functools import cache as aliased_cache - - -@cache -def my_func(param): - return param + 1 - - -class MyClassWithMethods: - @cache - @staticmethod - def my_func(param): - return param + 1 - - @cache - @classmethod - def my_func(cls, param): - return param + 1 - - @cache # [method-cache-max-size-none] - def my_func(self, param): - return param + 1 - - @functools.cache # [method-cache-max-size-none] - def my_func(self, param): - return param + 1 - - @aliased_functools.cache # [method-cache-max-size-none] - def my_func(self, param): - return param + 1 - - @aliased_cache # [method-cache-max-size-none] - def my_func(self, param): - return param + 1 - - # Check double decorating to check robustness of checker itself - @functools.lru_cache(maxsize=1) - @aliased_cache # [method-cache-max-size-none] - def my_func(self, param): - return param + 1 diff --git a/tests/functional/m/method_cache_max_size_none_py39.rc b/tests/functional/m/method_cache_max_size_none_py39.rc deleted file mode 100644 index 15ad50f5ab..0000000000 --- a/tests/functional/m/method_cache_max_size_none_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver = 3.9 diff --git a/tests/functional/m/method_cache_max_size_none_py39.txt b/tests/functional/m/method_cache_max_size_none_py39.txt deleted file mode 100644 index e364e50ef5..0000000000 --- a/tests/functional/m/method_cache_max_size_none_py39.txt +++ /dev/null @@ -1,5 +0,0 @@ -method-cache-max-size-none:27:5:27:10:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE -method-cache-max-size-none:31:5:31:20:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE -method-cache-max-size-none:35:5:35:28:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE -method-cache-max-size-none:39:5:39:18:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE -method-cache-max-size-none:45:5:45:18:MyClassWithMethods.my_func:'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self':INFERENCE diff --git a/tests/functional/m/method_hidden.py b/tests/functional/m/method_hidden.py index 19fd60c722..31bba74bf5 100644 --- a/tests/functional/m/method_hidden.py +++ b/tests/functional/m/method_hidden.py @@ -134,3 +134,9 @@ def __init__(self): class ChildTwo(ParentTwo): def __private(self): pass + + +class ChildHidingAncestorAttribute(Parent): + @functools().cached_property + def _protected(self): + pass diff --git a/tests/functional/m/method_hidden_py39.py b/tests/functional/m/method_hidden_py39.py deleted file mode 100644 index ac087d0d6c..0000000000 --- a/tests/functional/m/method_hidden_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -# pylint: disable=too-few-public-methods,missing-docstring -"""check method hiding ancestor attribute -""" -import something_else as functools # pylint: disable=import-error - - -class Parent: - def __init__(self): - self._protected = None - - -class Child(Parent): - @functools().cached_property - def _protected(self): - # This test case is only valid for python3.9 and above - pass diff --git a/tests/functional/m/method_hidden_py39.rc b/tests/functional/m/method_hidden_py39.rc deleted file mode 100644 index 15ad50f5ab..0000000000 --- a/tests/functional/m/method_hidden_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver = 3.9 diff --git a/tests/functional/m/misplaced_format_function.py b/tests/functional/m/misplaced_format_function.py index 679f598582..a137210241 100644 --- a/tests/functional/m/misplaced_format_function.py +++ b/tests/functional/m/misplaced_format_function.py @@ -6,7 +6,7 @@ print("value: {}").format(123) # [misplaced-format-function] print('value: {}'.format(123)) print('{} Come Forth!'.format('Lazarus')) -print('Der Hem ist mein Licht und mein Heil, vor wem sollte ich mich furchten? => {}'.format('Psalm 27, 1')) +print('He is my light and my salvation, of whom should I be afraid? => {}'.format('Psalm 27, 1')) print('123') print() s = 'value: {}'.format(123) diff --git a/tests/functional/m/multiple_statements.py b/tests/functional/m/multiple_statements.py index cc0a53e111..6279462cbf 100644 --- a/tests/functional/m/multiple_statements.py +++ b/tests/functional/m/multiple_statements.py @@ -43,3 +43,23 @@ class MyError(Exception): a='a'; b='b' # [multiple-statements] def concat2(arg1: str) -> str: ... def concat2(arg1: str) -> str: ... + +# Test for multiple statements on finally line +try: + pass +finally: pass # [multiple-statements] + +# Test for multiple statements on else line +try: + pass +except: + pass +else: pass # [multiple-statements] + +# Test for multiple statements on else and finally lines +try: + pass +except: + pass +else: pass # [multiple-statements] +finally: pass # [multiple-statements] diff --git a/tests/functional/m/multiple_statements.txt b/tests/functional/m/multiple_statements.txt index 6b5be1adf6..343298e25f 100644 --- a/tests/functional/m/multiple_statements.txt +++ b/tests/functional/m/multiple_statements.txt @@ -8,3 +8,7 @@ multiple-statements:26:30:26:59:MyException:More than one statement on a single multiple-statements:27:26:27:30:MyError:More than one statement on a single line:HIGH multiple-statements:30:26:30:31:MyError:More than one statement on a single line:HIGH multiple-statements:32:26:32:31:MyError:More than one statement on a single line:HIGH +multiple-statements:50:9:50:13::More than one statement on a single line:HIGH +multiple-statements:57:6:57:10::More than one statement on a single line:HIGH +multiple-statements:64:6:64:10::More than one statement on a single line:HIGH +multiple-statements:65:9:65:13::More than one statement on a single line:HIGH diff --git a/tests/functional/n/names_in__all__.py b/tests/functional/n/names_in__all__.py index 38ed18a2e7..76fbd833ae 100644 --- a/tests/functional/n/names_in__all__.py +++ b/tests/functional/n/names_in__all__.py @@ -1,7 +1,7 @@ # pylint: disable=too-few-public-methods, import-error, unnecessary-pass """Test Pylint's use of __all__. -* NonExistant is not defined in this module, and it is listed in +* NonExistent is not defined in this module, and it is listed in __all__. An error is expected. * This module imports path and republished it in __all__. No errors @@ -16,7 +16,7 @@ '', # [undefined-all-variable] Missing, SomeUndefined, # [undefined-variable] - 'NonExistant', # [undefined-all-variable] + 'NonExistent', # [undefined-all-variable] 'path', 'func', # [undefined-all-variable] 'inner', # [undefined-all-variable] diff --git a/tests/functional/n/names_in__all__.txt b/tests/functional/n/names_in__all__.txt index 720942df3d..29c0e11391 100644 --- a/tests/functional/n/names_in__all__.txt +++ b/tests/functional/n/names_in__all__.txt @@ -1,6 +1,6 @@ undefined-all-variable:16:4:16:6::Undefined variable name '' in __all__:UNDEFINED undefined-variable:18:4:18:17::Undefined variable 'SomeUndefined':UNDEFINED -undefined-all-variable:19:4:19:17::Undefined variable name 'NonExistant' in __all__:UNDEFINED +undefined-all-variable:19:4:19:17::Undefined variable name 'NonExistent' in __all__:UNDEFINED undefined-all-variable:21:4:21:10::Undefined variable name 'func' in __all__:UNDEFINED undefined-all-variable:22:4:22:11::Undefined variable name 'inner' in __all__:UNDEFINED undefined-all-variable:23:4:23:16::Undefined variable name 'InnerKlass' in __all__:UNDEFINED diff --git a/tests/functional/n/nested_min_max_py39.rc b/tests/functional/n/nested_min_max_py39.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/n/nested_min_max_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/n/no/no_member_imports.py b/tests/functional/n/no/no_member_imports.py index ead6d93352..225a215f2f 100644 --- a/tests/functional/n/no/no_member_imports.py +++ b/tests/functional/n/no/no_member_imports.py @@ -33,9 +33,9 @@ def test_ignored_modules_root_one_applies_as_well() -> None: def test_ignored_modules_patterns() -> None: - import collections + import importlib - collections.abc.THIS_does_not_EXIST + importlib.metadata.THIS_does_not_EXIST def test_ignored_classes_no_recursive_pattern() -> None: diff --git a/tests/functional/n/no/no_member_imports.rc b/tests/functional/n/no/no_member_imports.rc index 59db427c27..55b91f9fda 100644 --- a/tests/functional/n/no/no_member_imports.rc +++ b/tests/functional/n/no/no_member_imports.rc @@ -1,3 +1,3 @@ [TYPECHECK] -ignored-modules=argparse,xml.etree.,collections.abc* +ignored-modules=argparse,xml.etree.,importlib.metadata* ignored-classes=sys*,optparse.Values,Option diff --git a/tests/functional/n/no/no_name_in_module.py b/tests/functional/n/no/no_name_in_module.py index ef9fb03d17..6d34245c84 100644 --- a/tests/functional/n/no/no_name_in_module.py +++ b/tests/functional/n/no/no_name_in_module.py @@ -7,8 +7,8 @@ toto.yo() from xml.etree import ElementTree -ElementTree.nonexistant_function() # [no-member] -ElementTree.another.nonexistant.function() # [no-member] +ElementTree.nonexistent_function() # [no-member] +ElementTree.another.nonexistent.function() # [no-member] import sys diff --git a/tests/functional/n/no/no_name_in_module.txt b/tests/functional/n/no/no_name_in_module.txt index 878793bc73..ed8628108d 100644 --- a/tests/functional/n/no/no_name_in_module.txt +++ b/tests/functional/n/no/no_name_in_module.txt @@ -1,6 +1,6 @@ no-name-in-module:5:0:5:23::No name 'tutu' in module 'collections':UNDEFINED no-name-in-module:6:0:6:28::No name 'toto' in module 'collections':UNDEFINED -no-member:10:0:10:32::Module 'xml.etree.ElementTree' has no 'nonexistant_function' member:INFERENCE +no-member:10:0:10:32::Module 'xml.etree.ElementTree' has no 'nonexistent_function' member:INFERENCE no-member:11:0:11:19::Module 'xml.etree.ElementTree' has no 'another' member:INFERENCE no-member:16:6:16:17::Module 'sys' has no 'stdoout' member; maybe 'stdout'?:INFERENCE no-name-in-module:23:0:23:34::No name 'compiile' in module 're':UNDEFINED diff --git a/tests/functional/n/non/non_init_parent_called.py b/tests/functional/n/non/non_init_parent_called.py index 7a6c94eadd..5c27a45aa1 100644 --- a/tests/functional/n/non/non_init_parent_called.py +++ b/tests/functional/n/non/non_init_parent_called.py @@ -3,7 +3,7 @@ """test for call to __init__ from a non ancestor class """ from . import non_init_parent_called -import nonexistant # [import-error] +import nonexistent # [import-error] class AAAA: @@ -19,13 +19,13 @@ class BBBBMixin: def __init__(self): print('init', self) -class CCC(BBBBMixin, non_init_parent_called.AAAA, non_init_parent_called.BBBB, nonexistant.AClass): # [no-member] +class CCC(BBBBMixin, non_init_parent_called.AAAA, non_init_parent_called.BBBB, nonexistent.AClass): # [no-member] """mix different things, some inferable some not""" def __init__(self): BBBBMixin.__init__(self) non_init_parent_called.AAAA.__init__(self) non_init_parent_called.BBBB.__init__(self) # [no-member] - nonexistant.AClass.__init__(self) + nonexistent.AClass.__init__(self) class DDDD(AAAA): """call superclass constructor in disjunct branches""" diff --git a/tests/functional/n/non/non_init_parent_called.txt b/tests/functional/n/non/non_init_parent_called.txt index d6ef10e9f8..85aad5f6e4 100644 --- a/tests/functional/n/non/non_init_parent_called.txt +++ b/tests/functional/n/non/non_init_parent_called.txt @@ -1,4 +1,4 @@ -import-error:6:0:6:18::Unable to import 'nonexistant':UNDEFINED +import-error:6:0:6:18::Unable to import 'nonexistent':UNDEFINED non-parent-init-called:14:8:14:26:AAAA.__init__:__init__ method from a non direct base class 'BBBBMixin' is called:UNDEFINED no-member:22:50:22:77:CCC:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE no-member:27:8:27:35:CCC.__init__:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py index ac51b9c4dc..a0fa108f7f 100644 --- a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py +++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py @@ -1,4 +1,4 @@ -"""Assigment Expression as defined in https://www.python.org/dev/peps/pep-0572/""" +"""Assignment Expression as defined in https://www.python.org/dev/peps/pep-0572/""" if (loł := __name__) == "__main__": # [non-ascii-name] print(loł) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument.py similarity index 89% rename from tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py rename to tests/functional/n/non_ascii_name/non_ascii_name_function_argument.py index a75e1c6b78..377bf865e6 100644 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument.py @@ -1,7 +1,5 @@ """ non ascii variable defined in a function - -This test is only for 3.8 as the starting column is incorrect """ diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument.txt new file mode 100644 index 0000000000..8a26db2e12 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument.txt @@ -0,0 +1,2 @@ +non-ascii-name:9:4:9:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH +non-ascii-name:21:4:21:12::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt deleted file mode 100644 index 7813364747..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt +++ /dev/null @@ -1,2 +0,0 @@ -non-ascii-name:11:4:11:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH -non-ascii-name:23:0:None:None::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py deleted file mode 100644 index a2d87ac4d7..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -non ascii variable defined in a function - -This test is 3.9+ and not using 'min_pyver_end_position' -as the starting column is also incorrect on < 3.9 -""" - - -def okay( - just_some_thing_long_again: str, - lol_very_long_argument: str, - łol: str, # [non-ascii-name] -) -> bool: - """Be okay, yeah?""" - # Usage should not raise a second error - print(just_some_thing_long_again, lol_very_long_argument, łol) - return True - - -# Usage should raise a second error -okay( - "A VVVVVVVEEEERRRRRRRRRRYYYYYYYYYY LONG TIME ", - lol_very_long_argument="a", - łol="b", # [non-ascii-name] -) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt deleted file mode 100644 index 0222308af6..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt +++ /dev/null @@ -1,2 +0,0 @@ -non-ascii-name:12:4:12:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH -non-ascii-name:24:4:24:12::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs.py similarity index 79% rename from tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py rename to tests/functional/n/non_ascii_name/non_ascii_name_kwargs.py index 3c7b8c6835..fbd382f41d 100644 --- a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs.py @@ -1,7 +1,5 @@ """ Defining non ASCII variables in a function call - -This test is only for 3.8 as the starting column is incorrect """ diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs.txt new file mode 100644 index 0000000000..b5ab4d5bc8 --- /dev/null +++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs.txt @@ -0,0 +1 @@ +non-ascii-name:14:4:14:10::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt deleted file mode 100644 index b43189658f..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt +++ /dev/null @@ -1 +0,0 @@ -non-ascii-name:16:0:None:None::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py deleted file mode 100644 index 0dfceb38f4..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Defining non ASCII variables in a function call - -This test is 3.9+ and not using 'min_pyver_end_position' -as the starting column is also incorrect on < 3.9 -""" - - -def okay(**kwargs): - """Print kwargs""" - print(kwargs) - - -okay( - a_long_attribute_that_is_very_okay=1, - b_belongs_to_yet_another_okay_attributed=2, - łol=3, # [non-ascii-name] -) diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt deleted file mode 100644 index 7595a9d082..0000000000 --- a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt +++ /dev/null @@ -1 +0,0 @@ -non-ascii-name:17:4:17:10::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py index edcdae6435..8de521a7f5 100644 --- a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py +++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py @@ -9,7 +9,7 @@ class НoldIt: # [non-ascii-name] - """nice classs""" + """Nice class.""" def public(self): """do something""" diff --git a/tests/functional/n/not_async_context_manager_py37.py b/tests/functional/n/not_async_context_manager_py37.py index c1ca26976d..9bf1cf046e 100644 --- a/tests/functional/n/not_async_context_manager_py37.py +++ b/tests/functional/n/not_async_context_manager_py37.py @@ -8,8 +8,8 @@ async def context_manager(value): yield value -async with context_manager(42) as ans: - assert ans == 42 +async with context_manager(42) as answer: + assert answer == 42 def async_context_manager(): diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585.py b/tests/functional/p/postponed/postponed_evaluation_pep585.py index 2317228e55..5354e081fd 100644 --- a/tests/functional/p/postponed/postponed_evaluation_pep585.py +++ b/tests/functional/p/postponed/postponed_evaluation_pep585.py @@ -1,18 +1,5 @@ -"""Test PEP 585 in combination with postponed evaluation PEP 563. - -This check requires Python 3.7 or 3.8! -Testing with 3.8 only, to support TypedDict. -""" - -# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name -# pylint: disable=inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports -# pylint: disable=unused-variable,unnecessary-direct-lambda-call - -# Disabled because of a bug with pypy 3.8 see -# https://github.com/pylint-dev/pylint/pull/7918#issuecomment-1352737369 -# pylint: disable=multiple-statements - -from __future__ import annotations +"""Test PEP 585 works as expected, starting with Python 3.9""" +# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports,unused-variable,unnecessary-direct-lambda-call import collections import dataclasses import typing @@ -20,25 +7,25 @@ from typing import Any, Dict, NamedTuple, TypedDict, Union, Tuple -AliasInvalid = list[int] # [unsubscriptable-object] +AliasValid = list[int] class CustomIntList(typing.List[int]): pass -class CustomIntListError(list[int]): # [unsubscriptable-object] +class CustomIntListError(list[int]): pass cast_variable = [1, 2, 3] -cast_variable = typing.cast(list[int], cast_variable) # [unsubscriptable-object] +cast_variable = typing.cast(list[int], cast_variable) -T = typing.TypeVar("T", list[int], str) # [unsubscriptable-object] +T = typing.TypeVar("T", list[int], str) -(lambda x: 2)(list[int]) # [unsubscriptable-object] +(lambda x: 2)(list[int]) # Check typing.NamedTuple CustomNamedTuple = typing.NamedTuple( - "CustomNamedTuple", [("my_var", list[int])]) # [unsubscriptable-object] + "CustomNamedTuple", [("my_var", list[int])]) class CustomNamedTuple2(NamedTuple): my_var: list[int] @@ -48,9 +35,9 @@ class CustomNamedTuple3(typing.NamedTuple): # Check typing.TypedDict -CustomTypedDict = TypedDict("CustomTypedDict", my_var=list[int]) # [unsubscriptable-object] +CustomTypedDict = TypedDict("CustomTypedDict", my_var=list[int]) -CustomTypedDict2 = TypedDict("CustomTypedDict2", {"my_var": list[int]}) # [unsubscriptable-object] +CustomTypedDict2 = TypedDict("CustomTypedDict2", {"my_var": list[int]}) class CustomTypedDict3(TypedDict): my_var: list[int] @@ -101,12 +88,12 @@ def func(arg: list[int]): def func2() -> list[int]: pass -Alias2 = Union[list[str], None] # [unsubscriptable-object] -Alias3 = Union[Union[list[int], int]] # [unsubscriptable-object] -Alias4 = Tuple[list[int]] # [unsubscriptable-object] -Alias5 = Dict[str, list[int]] # [unsubscriptable-object] -Alias6 = int | list[int] # [unsubscriptable-object] -Alias7 = list[list[int]] # [unsubscriptable-object,unsubscriptable-object] +Alias2 = Union[list[str], None] +Alias3 = Union[Union[list[int], int]] +Alias4 = Tuple[list[int]] +Alias5 = Dict[str, list[int]] +Alias6 = int | list[int] +Alias7 = list[list[int]] import collections.abc @@ -116,7 +103,7 @@ def func2() -> list[int]: class OrderedDict: pass -var12: OrderedDict[str, int] # string annotations aren't checked +var12: OrderedDict[str, int] # [unsubscriptable-object] var13: list[int] var14: collections.OrderedDict[str, int] var15: collections.Counter[int] @@ -126,15 +113,15 @@ class OrderedDict: def func3(): - AliasInvalid2 = list[int] # [unsubscriptable-object] + AliasInvalid2 = list[int] cast_variable2 = [1, 2, 3] - cast_variable2 = typing.cast(list[int], cast_variable2) # [unsubscriptable-object] + cast_variable2 = typing.cast(list[int], cast_variable2) var19: list[int] -def func4(arg=list[int]): # [unsubscriptable-object] +def func4(var=list[int]): pass -def func5(arg1: list[int], arg2=set[int]): # [unsubscriptable-object] +def func5(arg1: list[int], arg2=set[int]): pass def func6(arg1: list[int], /, *args: tuple[str], arg2: set[int], **kwargs: dict[str, Any]): diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585.rc b/tests/functional/p/postponed/postponed_evaluation_pep585.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585.txt b/tests/functional/p/postponed/postponed_evaluation_pep585.txt index 899dc59774..127e252a53 100644 --- a/tests/functional/p/postponed/postponed_evaluation_pep585.txt +++ b/tests/functional/p/postponed/postponed_evaluation_pep585.txt @@ -1,19 +1 @@ -unsubscriptable-object:23:15:23:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:28:25:28:29:CustomIntListError:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:32:28:32:32::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:34:24:34:28::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:36:14:36:18::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:41:36:41:40::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:51:54:51:58::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:53:60:53:64::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:104:15:104:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:105:21:105:25::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:106:15:106:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:107:19:107:23::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:108:15:108:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:109:9:109:13::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:109:14:109:18::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:129:20:129:24:func3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:131:33:131:37:func3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:134:14:134:18:func4:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:137:32:137:35:func5:Value 'set' is unsubscriptable:UNDEFINED +unsubscriptable-object:106:7:106:18::Value 'OrderedDict' is unsubscriptable:UNDEFINED diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585_error.py b/tests/functional/p/postponed/postponed_evaluation_pep585_error.py deleted file mode 100644 index 9810c0c14b..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585_error.py +++ /dev/null @@ -1,124 +0,0 @@ -"""Test PEP 585 without postponed evaluation. Everything should fail. - -This check requires Python 3.7 or Python 3.8! -Testing with 3.8 only, to support TypedDict. -""" - -# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods -# pylint: disable=invalid-name,inherit-non-class,unsupported-binary-operation -# pylint: disable=unused-variable,line-too-long,unnecessary-direct-lambda-call - -# Disabled because of a bug with pypy 3.8 see -# https://github.com/pylint-dev/pylint/pull/7918#issuecomment-1352737369 -# pylint: disable=multiple-statements - -import collections -import dataclasses -import typing -from dataclasses import dataclass -from typing import Any, Dict, NamedTuple, TypedDict, Union - - -AliasInvalid = list[int] # [unsubscriptable-object] - -class CustomIntList(typing.List[int]): - pass - -class CustomIntListError(list[int]): # [unsubscriptable-object] - pass - -cast_variable = [1, 2, 3] -cast_variable = typing.cast(list[int], cast_variable) # [unsubscriptable-object] - -T = typing.TypeVar("T", list[int], str) # [unsubscriptable-object] - -(lambda x: 2)(list[int]) # [unsubscriptable-object] - - -# Check typing.NamedTuple -CustomNamedTuple = typing.NamedTuple( - "CustomNamedTuple", [("my_var", list[int])]) # [unsubscriptable-object] - -class CustomNamedTuple2(NamedTuple): - my_var: list[int] # [unsubscriptable-object] - -class CustomNamedTuple3(typing.NamedTuple): - my_var: list[int] # [unsubscriptable-object] - - -# Check typing.TypedDict -CustomTypedDict = TypedDict("CustomTypedDict", my_var=list[int]) # [unsubscriptable-object] - -CustomTypedDict2 = TypedDict("CustomTypedDict2", {"my_var": list[int]}) # [unsubscriptable-object] - -class CustomTypedDict3(TypedDict): - my_var: list[int] # [unsubscriptable-object] - -class CustomTypedDict4(typing.TypedDict): - my_var: list[int] # [unsubscriptable-object] - - -# Check dataclasses -def my_decorator(*args, **kwargs): - def wraps(*args, **kwargs): - pass - return wraps - -@dataclass -class CustomDataClass: - my_var: list[int] # [unsubscriptable-object] - -@dataclasses.dataclass -class CustomDataClass2: - my_var: list[int] # [unsubscriptable-object] - -@dataclass() -class CustomDataClass3: - my_var: list[int] # [unsubscriptable-object] - -@my_decorator -@dataclasses.dataclass -class CustomDataClass4: - my_var: list[int] # [unsubscriptable-object] - - -var1: set[int] # [unsubscriptable-object] -var2: collections.OrderedDict[str, int] # [unsubscriptable-object] -var3: dict[str, list[int]] # [unsubscriptable-object,unsubscriptable-object] -var4: Dict[str, list[int]] # [unsubscriptable-object] -var5: dict[tuple[int, int], str] # [unsubscriptable-object,unsubscriptable-object] -var6: Dict[tuple[int, int], str] # [unsubscriptable-object] -var7: list[list[int]] # [unsubscriptable-object,unsubscriptable-object] -var8: tuple[list[int]] # [unsubscriptable-object,unsubscriptable-object] -var9: int | list[str | int] # [unsubscriptable-object] -var10: Union[list[str], None] # [unsubscriptable-object] -var11: Union[Union[list[int], int]] # [unsubscriptable-object] - -def func(arg: list[int]): # [unsubscriptable-object] - pass - -def func2() -> list[int]: # [unsubscriptable-object] - pass - -Alias2 = Union[list[str], None] # [unsubscriptable-object] -Alias3 = Union[Union[list[int], int]] # [unsubscriptable-object] -Alias5 = Dict[str, list[int]] # [unsubscriptable-object] -Alias6 = int | list[int] # [unsubscriptable-object] -Alias7 = list[list[int]] # [unsubscriptable-object,unsubscriptable-object] - - -def func3(): - AliasInvalid2 = list[int] # [unsubscriptable-object] - cast_variable2 = [1, 2, 3] - cast_variable2 = typing.cast(list[int], cast_variable2) # [unsubscriptable-object] - var12: list[int] # [unsubscriptable-object] - -def func4(var=list[int]): # [unsubscriptable-object] - pass - -def func5(arg1: list[int], arg2=set[int]): # [unsubscriptable-object,unsubscriptable-object] - pass - -def func6(arg1: list[int], /, *args: tuple[str], arg2: set[int], **kwargs: dict[str, Any]): - # -1:[unsubscriptable-object,unsubscriptable-object,unsubscriptable-object,unsubscriptable-object] - pass diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585_error.rc b/tests/functional/p/postponed/postponed_evaluation_pep585_error.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585_error.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585_error.txt b/tests/functional/p/postponed/postponed_evaluation_pep585_error.txt deleted file mode 100644 index 406081dfae..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585_error.txt +++ /dev/null @@ -1,49 +0,0 @@ -unsubscriptable-object:22:15:22:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:27:25:27:29:CustomIntListError:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:31:28:31:32::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:33:24:33:28::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:35:14:35:18::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:40:36:40:40::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:43:12:43:16:CustomNamedTuple2:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:46:12:46:16:CustomNamedTuple3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:50:54:50:58::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:52:60:52:64::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:55:12:55:16:CustomTypedDict3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:58:12:58:16:CustomTypedDict4:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:69:12:69:16:CustomDataClass:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:73:12:73:16:CustomDataClass2:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:77:12:77:16:CustomDataClass3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:82:12:82:16:CustomDataClass4:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:85:6:85:9::Value 'set' is unsubscriptable:UNDEFINED -unsubscriptable-object:86:6:86:29::Value 'collections.OrderedDict' is unsubscriptable:UNDEFINED -unsubscriptable-object:87:6:87:10::Value 'dict' is unsubscriptable:UNDEFINED -unsubscriptable-object:87:16:87:20::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:88:16:88:20::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:89:6:89:10::Value 'dict' is unsubscriptable:UNDEFINED -unsubscriptable-object:89:11:89:16::Value 'tuple' is unsubscriptable:UNDEFINED -unsubscriptable-object:90:11:90:16::Value 'tuple' is unsubscriptable:UNDEFINED -unsubscriptable-object:91:6:91:10::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:91:11:91:15::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:92:12:92:16::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:92:6:92:11::Value 'tuple' is unsubscriptable:UNDEFINED -unsubscriptable-object:93:12:93:16::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:94:13:94:17::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:95:19:95:23::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:97:14:97:18:func:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:100:15:100:19:func2:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:103:15:103:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:104:21:104:25::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:105:19:105:23::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:106:15:106:19::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:107:9:107:13::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:107:14:107:18::Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:111:20:111:24:func3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:113:33:113:37:func3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:114:11:114:15:func3:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:116:14:116:18:func4:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:119:16:119:20:func5:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:119:32:119:35:func5:Value 'set' is unsubscriptable:UNDEFINED -unsubscriptable-object:122:75:122:79:func6:Value 'dict' is unsubscriptable:UNDEFINED -unsubscriptable-object:122:16:122:20:func6:Value 'list' is unsubscriptable:UNDEFINED -unsubscriptable-object:122:55:122:58:func6:Value 'set' is unsubscriptable:UNDEFINED -unsubscriptable-object:122:37:122:42:func6:Value 'tuple' is unsubscriptable:UNDEFINED diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585_py39.py b/tests/functional/p/postponed/postponed_evaluation_pep585_py39.py deleted file mode 100644 index 5354e081fd..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585_py39.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Test PEP 585 works as expected, starting with Python 3.9""" -# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports,unused-variable,unnecessary-direct-lambda-call -import collections -import dataclasses -import typing -from dataclasses import dataclass -from typing import Any, Dict, NamedTuple, TypedDict, Union, Tuple - - -AliasValid = list[int] - -class CustomIntList(typing.List[int]): - pass - -class CustomIntListError(list[int]): - pass - -cast_variable = [1, 2, 3] -cast_variable = typing.cast(list[int], cast_variable) - -T = typing.TypeVar("T", list[int], str) - -(lambda x: 2)(list[int]) - - -# Check typing.NamedTuple -CustomNamedTuple = typing.NamedTuple( - "CustomNamedTuple", [("my_var", list[int])]) - -class CustomNamedTuple2(NamedTuple): - my_var: list[int] - -class CustomNamedTuple3(typing.NamedTuple): - my_var: list[int] - - -# Check typing.TypedDict -CustomTypedDict = TypedDict("CustomTypedDict", my_var=list[int]) - -CustomTypedDict2 = TypedDict("CustomTypedDict2", {"my_var": list[int]}) - -class CustomTypedDict3(TypedDict): - my_var: list[int] - -class CustomTypedDict4(typing.TypedDict): - my_var: list[int] - - -# Check dataclasses -def my_decorator(*args, **kwargs): - def wraps(*args, **kwargs): - pass - return wraps - -@dataclass -class CustomDataClass: - my_var: list[int] - -@dataclasses.dataclass -class CustomDataClass2: - my_var: list[int] - -@dataclass() -class CustomDataClass3: - my_var: list[int] - -@my_decorator -@dataclasses.dataclass -class CustomDataClass4: - my_var: list[int] - - -var1: set[int] -var2: collections.OrderedDict[str, int] -var3: dict[str, list[int]] -var4: Dict[str, list[int]] -var5: dict[tuple[int, int], str] -var6: Dict[tuple[int, int], str] -var7: list[list[int]] -var8: tuple[list[int]] -var9: int | list[str | int] -var10: Union[list[str], None] -var11: Union[Union[list[int], int]] - -def func(arg: list[int]): - pass - -def func2() -> list[int]: - pass - -Alias2 = Union[list[str], None] -Alias3 = Union[Union[list[int], int]] -Alias4 = Tuple[list[int]] -Alias5 = Dict[str, list[int]] -Alias6 = int | list[int] -Alias7 = list[list[int]] - - -import collections.abc -import contextlib -import re - -class OrderedDict: - pass - -var12: OrderedDict[str, int] # [unsubscriptable-object] -var13: list[int] -var14: collections.OrderedDict[str, int] -var15: collections.Counter[int] -var16: collections.abc.Iterable[int] -var17: contextlib.AbstractContextManager[int] -var18: re.Pattern[str] - - -def func3(): - AliasInvalid2 = list[int] - cast_variable2 = [1, 2, 3] - cast_variable2 = typing.cast(list[int], cast_variable2) - var19: list[int] - -def func4(var=list[int]): - pass - -def func5(arg1: list[int], arg2=set[int]): - pass - -def func6(arg1: list[int], /, *args: tuple[str], arg2: set[int], **kwargs: dict[str, Any]): - pass diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585_py39.rc b/tests/functional/p/postponed/postponed_evaluation_pep585_py39.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/p/postponed/postponed_evaluation_pep585_py39.txt b/tests/functional/p/postponed/postponed_evaluation_pep585_py39.txt deleted file mode 100644 index 127e252a53..0000000000 --- a/tests/functional/p/postponed/postponed_evaluation_pep585_py39.txt +++ /dev/null @@ -1 +0,0 @@ -unsubscriptable-object:106:7:106:18::Value 'OrderedDict' is unsubscriptable:UNDEFINED diff --git a/tests/functional/p/potential_index_error.py b/tests/functional/p/potential_index_error.py index 4d3c48d75d..71c025a767 100644 --- a/tests/functional/p/potential_index_error.py +++ b/tests/functional/p/potential_index_error.py @@ -23,3 +23,26 @@ def my_func(): # Test that we don't crash on more complicated indices/slices # We do not raise here (currently) print([1, 2, 3][2:3]) + + +# Test for cases with unpacking operation +my_list = ["foo", "bar"] +my_set = {"foo", "bar"} +my_tuple = ("foo", "bar") +my_iterable = (*my_list, *my_set, *my_tuple, *("foo", "bar")) +my_non_iterable = None + +print([*my_list][1]) +print([*my_list][2]) # [potential-index-error] + +print([*my_set][1]) +print([*my_set][2]) # [potential-index-error] + +print((*my_tuple,)[1]) +print((*my_tuple,)[2]) # [potential-index-error] + +print((*my_iterable,)[7]) +print((*my_iterable,)[8]) # [potential-index-error] + +print((*my_non_iterable,)[0]) +print((*my_non_iterable,)[1]) # [potential-index-error] diff --git a/tests/functional/p/potential_index_error.txt b/tests/functional/p/potential_index_error.txt index 2340f81737..fc99227ec9 100644 --- a/tests/functional/p/potential_index_error.txt +++ b/tests/functional/p/potential_index_error.txt @@ -1,3 +1,8 @@ potential-index-error:6:6:6:18::Invalid index for iterable length:INFERENCE potential-index-error:7:6:7:18::Invalid index for iterable length:INFERENCE potential-index-error:8:6:8:22::Invalid index for iterable length:INFERENCE +potential-index-error:36:6:36:19::Invalid index for iterable length:INFERENCE +potential-index-error:39:6:39:18::Invalid index for iterable length:INFERENCE +potential-index-error:42:6:42:21::Invalid index for iterable length:INFERENCE +potential-index-error:45:6:45:24::Invalid index for iterable length:INFERENCE +potential-index-error:48:6:48:28::Invalid index for iterable length:INFERENCE diff --git a/tests/functional/r/raising/raising_non_exception.txt b/tests/functional/r/raising/raising_non_exception.txt index 5cab168463..86c4f70b1a 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:INFERENCE +raising-non-exception:13:0:13:22::Raising a class which doesn't inherit from BaseException:INFERENCE diff --git a/tests/functional/r/regression/regression_9074_refactor_loop_with_unary_variable.txt b/tests/functional/r/regression/regression_9074_refactor_loop_with_unary_variable.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/functional/r/regression_02/regression_9875_enumerate.py b/tests/functional/r/regression/regression_9875_enumerate.py similarity index 100% rename from tests/functional/r/regression_02/regression_9875_enumerate.py rename to tests/functional/r/regression/regression_9875_enumerate.py diff --git a/tests/functional/r/regression_02/regression_9875_enumerate.txt b/tests/functional/r/regression/regression_9875_enumerate.txt similarity index 100% rename from tests/functional/r/regression_02/regression_9875_enumerate.txt rename to tests/functional/r/regression/regression_9875_enumerate.txt diff --git a/tests/functional/r/regression_02/regression_4126.py b/tests/functional/r/regression_02/regression_4126.py new file mode 100644 index 0000000000..728f4355ab --- /dev/null +++ b/tests/functional/r/regression_02/regression_4126.py @@ -0,0 +1,2 @@ +"""Regression test for https://github.com/pylint-dev/pylint/issues/4126.""" +import array # pylint: disable=unused-import diff --git a/tests/functional/r/regression_02/regression_4126.rc b/tests/functional/r/regression_02/regression_4126.rc new file mode 100644 index 0000000000..bf9a2d27ab --- /dev/null +++ b/tests/functional/r/regression_02/regression_4126.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=CPython,IronPython diff --git a/tests/functional/r/regression_02/regression_5408.rc b/tests/functional/r/regression_02/regression_5408.rc index 7b414202d3..b47a745259 100644 --- a/tests/functional/r/regression_02/regression_5408.rc +++ b/tests/functional/r/regression_02/regression_5408.rc @@ -1,3 +1,2 @@ [testoptions] -min_pyver=3.9 except_implementations=PyPy diff --git a/tests/functional/r/regression_02/regression_5479.py b/tests/functional/r/regression_02/regression_5479.py index 9955ea6804..9b493f1929 100644 --- a/tests/functional/r/regression_02/regression_5479.py +++ b/tests/functional/r/regression_02/regression_5479.py @@ -1,7 +1,7 @@ """Test for a regression on slots and annotated assignments. Reported in https://github.com/pylint-dev/pylint/issues/5479 """ -# pylint: disable=too-few-public-methods, unused-private-member, missing-class-docstring, missing-function-docstring +# pylint: disable=too-few-public-methods, unused-private-member, missing-class-docstring, missing-function-docstring, declare-non-slot from __future__ import annotations diff --git a/tests/functional/s/slots_checks.py b/tests/functional/s/slots_checks.py index ddef0fb94c..e0c76dbe45 100644 --- a/tests/functional/s/slots_checks.py +++ b/tests/functional/s/slots_checks.py @@ -130,6 +130,65 @@ class ChildNotAffectedByValueInSlot(Parent): __slots__ = ('first', ) +class ClassTypeHintNotInSlotsWithoutDict: + __slots__ = ("a", "b") + + a: int + b: str + c: bool # [declare-non-slot] + + +class ClassTypeHintNotInSlotsWithDict: + __slots__ = ("a", "b", "__dict__") + + a: int + b: str + c: bool + + +class BaseNoSlots: + pass + + +class DerivedWithSlots(BaseNoSlots): + __slots__ = ("age",) + + price: int + + +class BaseWithSlots: + __slots__ = ("a", "b",) + + +class DerivedWithMoreSlots(BaseWithSlots): + __slots__ = ("c",) + + # Is in base __slots__ + a: int + + # Not in any base __slots__ + d: int # [declare-non-slot] + e: str= "AnnAssign.value is not None" + + +class BaseWithSlotsDict: + __slots__ = ("__dict__", ) + +class DerivedTypeHintNotInSlots(BaseWithSlotsDict): + __slots__ = ("other", ) + + a: int + def __init__(self) -> None: + super().__init__() + self.a = 42 + + +class ClassWithEmptySlotsAndAnnotation: + __slots__ = () + + a: int + + # https://github.com/pylint-dev/pylint/issues/9814 class SlotsManipulationTest: __slots__ = ["a", "b", "c"] diff --git a/tests/functional/s/slots_checks.txt b/tests/functional/s/slots_checks.txt index d63ad25173..127cb465fc 100644 --- a/tests/functional/s/slots_checks.txt +++ b/tests/functional/s/slots_checks.txt @@ -14,3 +14,5 @@ invalid-slots:81:0:81:16:TwelfthBad:Invalid __slots__ object:UNDEFINED class-variable-slots-conflict:114:17:114:24:ValueInSlotConflict:Value 'first' in slots conflicts with class variable:UNDEFINED class-variable-slots-conflict:114:45:114:53:ValueInSlotConflict:Value 'fourth' in slots conflicts with class variable:UNDEFINED class-variable-slots-conflict:114:36:114:43:ValueInSlotConflict:Value 'third' in slots conflicts with class variable:UNDEFINED +declare-non-slot:138:4:138:5:ClassTypeHintNotInSlotsWithoutDict:No such name 'c' in __slots__:INFERENCE +declare-non-slot:170:4:170:5:DerivedWithMoreSlots:No such name 'd' in __slots__:INFERENCE diff --git a/tests/functional/s/star/star_needs_assignment_target_py38.py b/tests/functional/s/star/star_needs_assignment_target_py38.py deleted file mode 100644 index fb5eea86a7..0000000000 --- a/tests/functional/s/star/star_needs_assignment_target_py38.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Test PEP 0448 -- Additional Unpacking Generalizations -https://www.python.org/dev/peps/pep-0448/ -""" - -# pylint: disable=superfluous-parens, unnecessary-comprehension - -UNPACK_TUPLE = (*range(4), 4) -UNPACK_LIST = [*range(4), 4] -UNPACK_SET = {*range(4), 4} -UNPACK_DICT = {'a': 1, **{'b': '2'}} -UNPACK_DICT2 = {**UNPACK_DICT, "x": 1, "y": 2} -UNPACK_DICT3 = {**{'a': 1}, 'a': 2, **{'a': 3}} - -UNPACK_IN_COMP = {elem for elem in (*range(10))} # [star-needs-assignment-target] diff --git a/tests/functional/s/star/star_needs_assignment_target_py38.rc b/tests/functional/s/star/star_needs_assignment_target_py38.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/s/star/star_needs_assignment_target_py38.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/s/star/star_needs_assignment_target_py38.txt b/tests/functional/s/star/star_needs_assignment_target_py38.txt deleted file mode 100644 index fb5a5faa6b..0000000000 --- a/tests/functional/s/star/star_needs_assignment_target_py38.txt +++ /dev/null @@ -1 +0,0 @@ -star-needs-assignment-target:15:36:15:46::Can use starred expression only in assignment target:UNDEFINED diff --git a/tests/functional/s/string/string_formatting.txt b/tests/functional/s/string/string_formatting.txt index 80aa4847fb..ccb1893af8 100644 --- a/tests/functional/s/string/string_formatting.txt +++ b/tests/functional/s/string/string_formatting.txt @@ -30,7 +30,7 @@ too-few-format-args:96:4:96:25:pprint_bad:Not enough arguments for format string too-few-format-args:97:4:97:26:pprint_bad:Not enough arguments for format string:UNDEFINED too-many-format-args:98:4:98:32:pprint_bad:Too many arguments for format string:UNDEFINED logging-too-few-args:99:4:99:30:pprint_bad:Not enough arguments for logging format string:UNDEFINED -logging-too-many-args:100:4:100:31:pprint_bad:Too many arguments for logging format string:UNDEFINED +logging-too-many-args:100:4:100:31:pprint_bad:Too many arguments for logging format string:HIGH format-string-without-interpolation:101:4:101:22:pprint_bad:Using formatting for a string that does not have any interpolated variables:UNDEFINED format-string-without-interpolation:102:4:102:23:pprint_bad:Using formatting for a string that does not have any interpolated variables:UNDEFINED format-string-without-interpolation:103:4:103:23:pprint_bad:Using formatting for a string that does not have any interpolated variables:UNDEFINED diff --git a/tests/functional/s/string/string_log_formatting.txt b/tests/functional/s/string/string_log_formatting.txt index 5903c75bf6..21eb4255a8 100644 --- a/tests/functional/s/string/string_log_formatting.txt +++ b/tests/functional/s/string/string_log_formatting.txt @@ -1,6 +1,6 @@ -logging-too-many-args:14:4:14:23:pprint:Too many arguments for logging format string:UNDEFINED -logging-too-many-args:15:4:15:24:pprint:Too many arguments for logging format string:UNDEFINED +logging-too-many-args:14:4:14:23:pprint:Too many arguments for logging format string:HIGH +logging-too-many-args:15:4:15:24:pprint:Too many arguments for logging format string:HIGH logging-format-truncated:16:4:16:27:pprint:Logging format string ends in middle of conversion specifier:UNDEFINED logging-too-few-args:17:4:17:28:pprint:Not enough arguments for logging format string:UNDEFINED logging-unsupported-format:18:4:18:32:pprint:Unsupported logging format character 'y' (0x79) at index 3:UNDEFINED -logging-too-many-args:19:4:19:36:pprint:Too many arguments for logging format string:UNDEFINED +logging-too-many-args:19:4:19:36:pprint:Too many arguments for logging format string:HIGH diff --git a/tests/functional/s/super/super_init_not_called_extensions.py b/tests/functional/s/super/super_init_not_called_extensions.py deleted file mode 100644 index 241e0008a1..0000000000 --- a/tests/functional/s/super/super_init_not_called_extensions.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Tests for super-init-not-called.""" -# pylint: disable=too-few-public-methods - -from typing_extensions import Protocol as ExtensionProtocol - - -class TestProto(ExtensionProtocol): - """A protocol without __init__ using Protocol from typing_extensions.""" - - -class TestParent(TestProto): - """An implementation.""" - - def __init__(self): - ... - - -class TestChild(TestParent): - """An implementation which should call the init of TestParent.""" - - def __init__(self): # [super-init-not-called] - ... diff --git a/tests/functional/s/super/super_init_not_called_extensions.rc b/tests/functional/s/super/super_init_not_called_extensions.rc deleted file mode 100644 index d584aa9595..0000000000 --- a/tests/functional/s/super/super_init_not_called_extensions.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -max_pyver=3.9 diff --git a/tests/functional/s/super/super_init_not_called_extensions.txt b/tests/functional/s/super/super_init_not_called_extensions.txt deleted file mode 100644 index b80cb80be4..0000000000 --- a/tests/functional/s/super/super_init_not_called_extensions.txt +++ /dev/null @@ -1 +0,0 @@ -super-init-not-called:21:4:21:16:TestChild.__init__:__init__ method from base class 'TestParent' is not called:INFERENCE diff --git a/tests/functional/t/too/too_many_arguments.py b/tests/functional/t/too/too_many_arguments.py index 9c7f3ab087..0d427c7e49 100644 --- a/tests/functional/t/too/too_many_arguments.py +++ b/tests/functional/t/too/too_many_arguments.py @@ -1,6 +1,7 @@ # pylint: disable=missing-docstring,wrong-import-position,unnecessary-dunder-call -def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9): # [too-many-arguments] +# +1: [too-many-arguments, too-many-positional-arguments] +def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9): return arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 diff --git a/tests/functional/t/too/too_many_arguments.txt b/tests/functional/t/too/too_many_arguments.txt index 6d727813ea..b996058196 100644 --- a/tests/functional/t/too/too_many_arguments.txt +++ b/tests/functional/t/too/too_many_arguments.txt @@ -1,2 +1,3 @@ -too-many-arguments:3:0:3:19:stupid_function:Too many arguments (9/5):UNDEFINED -too-many-arguments:36:0:36:9:name1:Too many arguments (6/5):UNDEFINED +too-many-arguments:4:0:4:19:stupid_function:Too many arguments (9/5):UNDEFINED +too-many-positional-arguments:4:0:4:19:stupid_function:Too many positional arguments (9/5):HIGH +too-many-arguments:37:0:37:9:name1:Too many arguments (6/5):UNDEFINED diff --git a/tests/functional/t/too/too_many_function_args.py b/tests/functional/t/too/too_many_function_args.py index 9ba49565eb..848cbd1a0c 100644 --- a/tests/functional/t/too/too_many_function_args.py +++ b/tests/functional/t/too/too_many_function_args.py @@ -17,3 +17,8 @@ def main(param): if param == 0: tmp = add return tmp(1, 1.01) + + +# Negative case, see `_check_isinstance_args` in `./pylint/checkers/typecheck.py` +isinstance(1, int, int) # [too-many-function-args] +isinstance(1, 1, int) # [too-many-function-args, isinstance-second-argument-not-valid-type] diff --git a/tests/functional/t/too/too_many_function_args.txt b/tests/functional/t/too/too_many_function_args.txt new file mode 100644 index 0000000000..fbc0c97814 --- /dev/null +++ b/tests/functional/t/too/too_many_function_args.txt @@ -0,0 +1,3 @@ +too-many-function-args:23:0:23:23::Too many positional arguments for function call:HIGH +isinstance-second-argument-not-valid-type:24:0:24:21::Second argument of isinstance is not a type:INFERENCE +too-many-function-args:24:0:24:21::Too many positional arguments for function call:HIGH diff --git a/tests/functional/t/too/too_many_locals.py b/tests/functional/t/too/too_many_locals.py index 34395871d7..8b141039f8 100644 --- a/tests/functional/t/too/too_many_locals.py +++ b/tests/functional/t/too/too_many_locals.py @@ -29,7 +29,8 @@ def too_many_locals_function(): # [too-many-locals] args15 = args14 * 15 return args15 -def too_many_arguments_function(arga, argu, argi, arge, argt, args): # [too-many-arguments] +# +1: [too-many-arguments, too-many-positional-arguments] +def too_many_arguments_function(arga, argu, argi, arge, argt, args): """pylint will complain about too many arguments.""" arga = argu arga += argi diff --git a/tests/functional/t/too/too_many_locals.txt b/tests/functional/t/too/too_many_locals.txt index 19272626f1..6617661e3c 100644 --- a/tests/functional/t/too/too_many_locals.txt +++ b/tests/functional/t/too/too_many_locals.txt @@ -1,3 +1,4 @@ too-many-locals:4:0:4:12:function:Too many local variables (16/15):UNDEFINED too-many-locals:12:0:12:28:too_many_locals_function:Too many local variables (16/15):UNDEFINED -too-many-arguments:32:0:32:31:too_many_arguments_function:Too many arguments (6/5):UNDEFINED +too-many-arguments:33:0:33:31:too_many_arguments_function:Too many arguments (6/5):UNDEFINED +too-many-positional-arguments:33:0:33:31:too_many_arguments_function:Too many positional arguments (6/5):HIGH diff --git a/tests/functional/t/too/too_many_positional_arguments.py b/tests/functional/t/too/too_many_positional_arguments.py new file mode 100644 index 0000000000..e373324818 --- /dev/null +++ b/tests/functional/t/too/too_many_positional_arguments.py @@ -0,0 +1,9 @@ +# pylint: disable=missing-function-docstring, missing-module-docstring +class FiveArgumentMethods: + """The max positional arguments default is 5.""" + def fail1(self, a, b, c, d, e): # [too-many-arguments, too-many-positional-arguments] + pass + def fail2(self, a, b, c, d, /, e): # [too-many-arguments, too-many-positional-arguments] + pass + def okay1(self, a, b, c, d, *, e=True): # [too-many-arguments] + pass diff --git a/tests/functional/t/too/too_many_positional_arguments.txt b/tests/functional/t/too/too_many_positional_arguments.txt new file mode 100644 index 0000000000..80591cd371 --- /dev/null +++ b/tests/functional/t/too/too_many_positional_arguments.txt @@ -0,0 +1,5 @@ +too-many-arguments:4:4:4:13:FiveArgumentMethods.fail1:Too many arguments (6/5):UNDEFINED +too-many-positional-arguments:4:4:4:13:FiveArgumentMethods.fail1:Too many positional arguments (6/5):HIGH +too-many-arguments:6:4:6:13:FiveArgumentMethods.fail2:Too many arguments (6/5):UNDEFINED +too-many-positional-arguments:6:4:6:13:FiveArgumentMethods.fail2:Too many positional arguments (6/5):HIGH +too-many-arguments:8:4:8:13:FiveArgumentMethods.okay1:Too many arguments (6/5):UNDEFINED diff --git a/tests/functional/u/undefined/undefined_variable_py312.py b/tests/functional/u/undefined/undefined_variable_py312.py index 0ca2475eb9..88a5426539 100644 --- a/tests/functional/u/undefined/undefined_variable_py312.py +++ b/tests/functional/u/undefined/undefined_variable_py312.py @@ -4,4 +4,5 @@ def f[T](a: T) -> T: print(a) class ChildClass[T, *Ts, **P]: - ... + def __init__(self, value: T): + self.value = value diff --git a/tests/functional/u/unexpected_special_method_signature.py b/tests/functional/u/unexpected_special_method_signature.py index 146308b035..31ea161faf 100644 --- a/tests/functional/u/unexpected_special_method_signature.py +++ b/tests/functional/u/unexpected_special_method_signature.py @@ -74,6 +74,7 @@ class Valid: def __new__(cls, test, multiple, args): pass + # pylint: disable-next=too-many-positional-arguments def __init__(self, this, can, have, multiple, args, as_well): pass diff --git a/tests/functional/u/uninferable_all_object.py b/tests/functional/u/uninferable_all_object.py index 3e565f9ebf..9c49c2ab6e 100644 --- a/tests/functional/u/uninferable_all_object.py +++ b/tests/functional/u/uninferable_all_object.py @@ -2,7 +2,7 @@ __all__ = sorted([ 'Dummy', - 'NonExistant', + 'NonExistent', 'path', 'func', 'inner', diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38.py b/tests/functional/u/unnecessary/unnecessary_dunder_call.py similarity index 100% rename from tests/functional/u/unnecessary/unnecessary_dunder_call_py38.py rename to tests/functional/u/unnecessary/unnecessary_dunder_call.py diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38.txt b/tests/functional/u/unnecessary/unnecessary_dunder_call.txt similarity index 100% rename from tests/functional/u/unnecessary/unnecessary_dunder_call_py38.txt rename to tests/functional/u/unnecessary/unnecessary_dunder_call.txt diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38.rc b/tests/functional/u/unnecessary/unnecessary_dunder_call_py38.rc deleted file mode 100644 index afbcb7d432..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38.rc +++ /dev/null @@ -1,3 +0,0 @@ -[testoptions] -max_pyver=3.8 -except_implementations=PyPy diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.py b/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.py deleted file mode 100644 index f86e14651d..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Checks for unnecessary-dunder-call.""" -# pylint: disable=too-few-public-methods, undefined-variable -# pylint: disable=missing-class-docstring, missing-function-docstring -# pylint: disable=protected-access, unnecessary-lambda-assignment, unnecessary-lambda -from collections import OrderedDict -from typing import Any - -# Test includelisted dunder methods raise lint when manually called. -num_str = some_num.__str__() # [unnecessary-dunder-call] -num_repr = some_num.__add__(2) # [unnecessary-dunder-call] -my_repr = my_module.my_object.__repr__() # [unnecessary-dunder-call] - -MY_CONTAINS_BAD = {1, 2, 3}.__contains__(1) # [unnecessary-dunder-call] -MY_CONTAINS_GOOD = 1 in {1, 2, 3} - -# Just instantiate like a normal person please -my_list_bad = [] -my_list_bad.__init__({1, 2, 3}) # [unnecessary-dunder-call] -my_list_good = list({1, 2, 3}) - -# Test unknown/user-defined dunder methods don't raise lint. -my_woohoo = my_object.__woohoo__() - -# Test lint raised within function. -def is_bigger_than_two(val): - return val.__gt__(2) # [unnecessary-dunder-call] - -# Test dunder methods don't raise lint -# if within a dunder method definition. -class Foo1: - def __init__(self): - object.__init__(self) - -class Foo2: - def __init__(self): - super().__init__(self) - -class Bar1: - def __new__(cls): - object.__new__(cls) - -class Bar2: - def __new__(cls): - super().__new__(cls) - -class CustomRegistry(dict): - def __init__(self) -> None: - super().__init__() - self._entry_ids = {} - - def __setitem__(self, key, entry) -> None: - super().__setitem__(key, entry) - self._entry_ids.__setitem__(entry.id, entry) - self._entry_ids.__delitem__(entry.id) - - def __delitem__(self, key: str) -> None: - entry = self[key] - self._entry_ids.__delitem__(entry.id) - super().__delitem__(key) - -class CustomState: - def __init__(self, state): - self._state = state - - def __eq__(self, other: Any) -> bool: - return self._state.__eq__(other) - -class CustomDict(OrderedDict): - def __init__(self, *args, **kwds): - OrderedDict.__init__(self, *args, **kwds) - - def __setitem__(self, key, value): - OrderedDict.__setitem__(self, key, value) - - -class MyClass(list): - def __contains__(self, item): - print("do some special checks") - return super().__contains__(item) - -class PluginBase: - subclasses = [] - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - cls.subclasses.append(cls) - -# Validate that dunder call is allowed -# at any depth within dunder definition -class SomeClass: - def __init__(self): - self.my_attr = object() - - def __setattr__(self, name, value): - def nested_function(): - self.my_attr.__setattr__(name, value) - - nested_function() - -# Allow use of dunder methods that don't -# have an alternate method of being called -class Base: - @classmethod - def get_first_subclass(cls): - for subklass in cls.__subclasses__(): - return subklass - return object - -# Test no lint raised for attributes. -my_instance_name = x.__class__.__name__ -my_pkg_version = pkg.__version__ - -# Allow use of dunder methods on non instantiated classes -MANUAL_SELF = int.__add__(1, 1) -MY_DICT = {"a": 1, "b": 2} -dict.__setitem__(MY_DICT, "key", "value") - -# Still flag instantiated classes -INSTANTIATED_SELF = int("1").__add__(1) # [unnecessary-dunder-call] -{"a": 1, "b": 2}.__setitem__("key", "value") # [unnecessary-dunder-call] - -# We also exclude dunder methods called on super -# since we can't apply alternate operators/functions here. -a = [1, 2, 3] -assert super(type(a), a).__str__() == "[1, 2, 3]" - -class MyString(str): - """Custom str implementation""" - def rjust(self, width, fillchar= ' '): - """Acceptable call to __index__""" - width = width.__index__() - -# Test no lint raised for these dunders within lambdas -lambda1 = lambda x: x.__setitem__(1,2) -lambda2 = lambda x: x.__del__(1) -lambda3 = lambda x,y: x.__ipow__(y) -lambda4 = lambda u,v: u.__setitem__(v()) - -# Test lint raised for these dunders within lambdas -lambda5 = lambda x: x.__gt__(3) # [unnecessary-dunder-call] -lambda6 = lambda x,y: x.__or__(y) # [unnecessary-dunder-call] -lambda7 = lambda x: x.__iter__() # [unnecessary-dunder-call] -lambda8 = lambda z: z.__hash__() # [unnecessary-dunder-call] -lambda9 = lambda n: (4).__rmul__(n) # [unnecessary-dunder-call] diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.rc b/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.rc deleted file mode 100644 index 413b991518..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.rc +++ /dev/null @@ -1,3 +0,0 @@ -[testoptions] -max_pyver=3.8 -except_implementations=CPython diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.txt b/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.txt deleted file mode 100644 index 2152103f8c..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py38_pypy.txt +++ /dev/null @@ -1,13 +0,0 @@ -unnecessary-dunder-call:9:10:None:None::Unnecessarily calls dunder method __str__. Use str built-in function.:HIGH -unnecessary-dunder-call:10:11:None:None::Unnecessarily calls dunder method __add__. Use + operator.:HIGH -unnecessary-dunder-call:11:10:None:None::Unnecessarily calls dunder method __repr__. Use repr built-in function.:HIGH -unnecessary-dunder-call:13:18:None:None::Unnecessarily calls dunder method __contains__. Use in keyword.:HIGH -unnecessary-dunder-call:18:0:None:None::Unnecessarily calls dunder method __init__. Instantiate class directly.:HIGH -unnecessary-dunder-call:26:11:None:None:is_bigger_than_two:Unnecessarily calls dunder method __gt__. Use > operator.:HIGH -unnecessary-dunder-call:119:20:None:None::Unnecessarily calls dunder method __add__. Use + operator.:HIGH -unnecessary-dunder-call:120:0:None:None::Unnecessarily calls dunder method __setitem__. Set item via subscript.:HIGH -unnecessary-dunder-call:140:20:None:None::Unnecessarily calls dunder method __gt__. Use > operator.:HIGH -unnecessary-dunder-call:141:22:None:None::Unnecessarily calls dunder method __or__. Use | operator.:HIGH -unnecessary-dunder-call:142:20:None:None::Unnecessarily calls dunder method __iter__. Use iter built-in function.:HIGH -unnecessary-dunder-call:143:20:None:None::Unnecessarily calls dunder method __hash__. Use hash built-in function.:HIGH -unnecessary-dunder-call:144:21:None:None::Unnecessarily calls dunder method __rmul__. Use * operator.:HIGH diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.py b/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.py deleted file mode 100644 index f86e14651d..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Checks for unnecessary-dunder-call.""" -# pylint: disable=too-few-public-methods, undefined-variable -# pylint: disable=missing-class-docstring, missing-function-docstring -# pylint: disable=protected-access, unnecessary-lambda-assignment, unnecessary-lambda -from collections import OrderedDict -from typing import Any - -# Test includelisted dunder methods raise lint when manually called. -num_str = some_num.__str__() # [unnecessary-dunder-call] -num_repr = some_num.__add__(2) # [unnecessary-dunder-call] -my_repr = my_module.my_object.__repr__() # [unnecessary-dunder-call] - -MY_CONTAINS_BAD = {1, 2, 3}.__contains__(1) # [unnecessary-dunder-call] -MY_CONTAINS_GOOD = 1 in {1, 2, 3} - -# Just instantiate like a normal person please -my_list_bad = [] -my_list_bad.__init__({1, 2, 3}) # [unnecessary-dunder-call] -my_list_good = list({1, 2, 3}) - -# Test unknown/user-defined dunder methods don't raise lint. -my_woohoo = my_object.__woohoo__() - -# Test lint raised within function. -def is_bigger_than_two(val): - return val.__gt__(2) # [unnecessary-dunder-call] - -# Test dunder methods don't raise lint -# if within a dunder method definition. -class Foo1: - def __init__(self): - object.__init__(self) - -class Foo2: - def __init__(self): - super().__init__(self) - -class Bar1: - def __new__(cls): - object.__new__(cls) - -class Bar2: - def __new__(cls): - super().__new__(cls) - -class CustomRegistry(dict): - def __init__(self) -> None: - super().__init__() - self._entry_ids = {} - - def __setitem__(self, key, entry) -> None: - super().__setitem__(key, entry) - self._entry_ids.__setitem__(entry.id, entry) - self._entry_ids.__delitem__(entry.id) - - def __delitem__(self, key: str) -> None: - entry = self[key] - self._entry_ids.__delitem__(entry.id) - super().__delitem__(key) - -class CustomState: - def __init__(self, state): - self._state = state - - def __eq__(self, other: Any) -> bool: - return self._state.__eq__(other) - -class CustomDict(OrderedDict): - def __init__(self, *args, **kwds): - OrderedDict.__init__(self, *args, **kwds) - - def __setitem__(self, key, value): - OrderedDict.__setitem__(self, key, value) - - -class MyClass(list): - def __contains__(self, item): - print("do some special checks") - return super().__contains__(item) - -class PluginBase: - subclasses = [] - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - cls.subclasses.append(cls) - -# Validate that dunder call is allowed -# at any depth within dunder definition -class SomeClass: - def __init__(self): - self.my_attr = object() - - def __setattr__(self, name, value): - def nested_function(): - self.my_attr.__setattr__(name, value) - - nested_function() - -# Allow use of dunder methods that don't -# have an alternate method of being called -class Base: - @classmethod - def get_first_subclass(cls): - for subklass in cls.__subclasses__(): - return subklass - return object - -# Test no lint raised for attributes. -my_instance_name = x.__class__.__name__ -my_pkg_version = pkg.__version__ - -# Allow use of dunder methods on non instantiated classes -MANUAL_SELF = int.__add__(1, 1) -MY_DICT = {"a": 1, "b": 2} -dict.__setitem__(MY_DICT, "key", "value") - -# Still flag instantiated classes -INSTANTIATED_SELF = int("1").__add__(1) # [unnecessary-dunder-call] -{"a": 1, "b": 2}.__setitem__("key", "value") # [unnecessary-dunder-call] - -# We also exclude dunder methods called on super -# since we can't apply alternate operators/functions here. -a = [1, 2, 3] -assert super(type(a), a).__str__() == "[1, 2, 3]" - -class MyString(str): - """Custom str implementation""" - def rjust(self, width, fillchar= ' '): - """Acceptable call to __index__""" - width = width.__index__() - -# Test no lint raised for these dunders within lambdas -lambda1 = lambda x: x.__setitem__(1,2) -lambda2 = lambda x: x.__del__(1) -lambda3 = lambda x,y: x.__ipow__(y) -lambda4 = lambda u,v: u.__setitem__(v()) - -# Test lint raised for these dunders within lambdas -lambda5 = lambda x: x.__gt__(3) # [unnecessary-dunder-call] -lambda6 = lambda x,y: x.__or__(y) # [unnecessary-dunder-call] -lambda7 = lambda x: x.__iter__() # [unnecessary-dunder-call] -lambda8 = lambda z: z.__hash__() # [unnecessary-dunder-call] -lambda9 = lambda n: (4).__rmul__(n) # [unnecessary-dunder-call] diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.rc b/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.txt b/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.txt deleted file mode 100644 index 65ab199829..0000000000 --- a/tests/functional/u/unnecessary/unnecessary_dunder_call_py39.txt +++ /dev/null @@ -1,13 +0,0 @@ -unnecessary-dunder-call:9:10:9:28::Unnecessarily calls dunder method __str__. Use str built-in function.:HIGH -unnecessary-dunder-call:10:11:10:30::Unnecessarily calls dunder method __add__. Use + operator.:HIGH -unnecessary-dunder-call:11:10:11:40::Unnecessarily calls dunder method __repr__. Use repr built-in function.:HIGH -unnecessary-dunder-call:13:18:13:43::Unnecessarily calls dunder method __contains__. Use in keyword.:HIGH -unnecessary-dunder-call:18:0:18:31::Unnecessarily calls dunder method __init__. Instantiate class directly.:HIGH -unnecessary-dunder-call:26:11:26:24:is_bigger_than_two:Unnecessarily calls dunder method __gt__. Use > operator.:HIGH -unnecessary-dunder-call:119:20:119:39::Unnecessarily calls dunder method __add__. Use + operator.:HIGH -unnecessary-dunder-call:120:0:120:44::Unnecessarily calls dunder method __setitem__. Set item via subscript.:HIGH -unnecessary-dunder-call:140:20:140:31::Unnecessarily calls dunder method __gt__. Use > operator.:HIGH -unnecessary-dunder-call:141:22:141:33::Unnecessarily calls dunder method __or__. Use | operator.:HIGH -unnecessary-dunder-call:142:20:142:32::Unnecessarily calls dunder method __iter__. Use iter built-in function.:HIGH -unnecessary-dunder-call:143:20:143:32::Unnecessarily calls dunder method __hash__. Use hash built-in function.:HIGH -unnecessary-dunder-call:144:20:144:35::Unnecessarily calls dunder method __rmul__. Use * operator.:HIGH diff --git a/tests/functional/u/unreachable.rc b/tests/functional/u/unreachable.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/u/unreachable.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.py b/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.py new file mode 100644 index 0000000000..56cce47fb2 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-function-docstring, missing-module-docstring +import random +if zero_or_one := random.randint(0, 1): # [using-assignment-expression-in-unsupported-version] + assert zero_or_one == 1 diff --git a/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.rc b/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.rc new file mode 100644 index 0000000000..77eb3be645 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.rc @@ -0,0 +1,2 @@ +[main] +py-version=3.7 diff --git a/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.txt b/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.txt new file mode 100644 index 0000000000..54e68c2d5c --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_assignment_expression.txt @@ -0,0 +1 @@ +using-assignment-expression-in-unsupported-version:3:3:3:38::Assignment expression is not supported by all versions included in the py-version setting:HIGH diff --git a/tests/functional/u/unsupported/unsupported_version_for_exception_group.py b/tests/functional/u/unsupported/unsupported_version_for_exception_group.py new file mode 100644 index 0000000000..6327e82f1d --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_exception_group.py @@ -0,0 +1,21 @@ +# pylint: disable=missing-function-docstring, missing-module-docstring +def f(): + excs = [OSError("error 1"), SystemError("error 2")] + # +1: [using-exception-groups-in-unsupported-version] + raise ExceptionGroup("there were problems", excs) + + +try: # [using-exception-groups-in-unsupported-version] + f() +except* OSError as e: + print("There were OSErrors") +except* SystemError as e: + print("There were SystemErrors") + + +try: + f() +except ExceptionGroup as group: # [using-exception-groups-in-unsupported-version] + # https://github.com/pylint-dev/pylint/issues/8985 + for exc in group.exceptions: # pylint: disable=not-an-iterable + print("ERROR: ", exc) diff --git a/tests/functional/u/unsupported/unsupported_version_for_exception_group.rc b/tests/functional/u/unsupported/unsupported_version_for_exception_group.rc new file mode 100644 index 0000000000..4885accdeb --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_exception_group.rc @@ -0,0 +1,5 @@ +[main] +py-version=3.10 + +[testoptions] +min_pyver=3.11 diff --git a/tests/functional/u/unsupported/unsupported_version_for_exception_group.txt b/tests/functional/u/unsupported/unsupported_version_for_exception_group.txt new file mode 100644 index 0000000000..0e48220e98 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_exception_group.txt @@ -0,0 +1,3 @@ +using-exception-groups-in-unsupported-version:5:4:5:53:f:Exception groups are not supported by all versions included in the py-version setting:HIGH +using-exception-groups-in-unsupported-version:8:0:13:36::Exception groups are not supported by all versions included in the py-version setting:HIGH +using-exception-groups-in-unsupported-version:18:0:21:29::Exception groups are not supported by all versions included in the py-version setting:HIGH diff --git a/tests/functional/u/unsupported/unsupported_version_for_f_string.txt b/tests/functional/u/unsupported/unsupported_version_for_f_string.txt index 3c05a79c0a..634abd3dc3 100644 --- a/tests/functional/u/unsupported/unsupported_version_for_f_string.txt +++ b/tests/functional/u/unsupported/unsupported_version_for_f_string.txt @@ -1,2 +1,2 @@ -using-f-string-in-unsupported-version:4:6:4:26::F-strings are not supported by all versions included in the py-version setting:UNDEFINED -using-f-string-in-unsupported-version:5:10:5:53::F-strings are not supported by all versions included in the py-version setting:UNDEFINED +using-f-string-in-unsupported-version:4:6:4:26::F-strings are not supported by all versions included in the py-version setting:HIGH +using-f-string-in-unsupported-version:5:10:5:53::F-strings are not supported by all versions included in the py-version setting:HIGH diff --git a/tests/functional/u/unsupported/unsupported_version_for_final.txt b/tests/functional/u/unsupported/unsupported_version_for_final.txt index 51c55b9206..326bffd4c2 100644 --- a/tests/functional/u/unsupported/unsupported_version_for_final.txt +++ b/tests/functional/u/unsupported/unsupported_version_for_final.txt @@ -1,9 +1,9 @@ -using-final-decorator-in-unsupported-version:10:1:10:6:MyClass1:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:12:5:12:10:MyClass1.my_method:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:13:5:13:10:MyClass1.my_method:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:18:1:18:8:MyClass2:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:20:5:20:12:MyClass2.my_method:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:25:1:25:13:MyClass3:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:27:5:27:17:MyClass3.my_method:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:32:1:32:15:MyClass4:typing.final is not supported by all versions included in the py-version setting:UNDEFINED -using-final-decorator-in-unsupported-version:34:5:34:19:MyClass4.my_method:typing.final is not supported by all versions included in the py-version setting:UNDEFINED +using-final-decorator-in-unsupported-version:10:1:10:6:MyClass1:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:12:5:12:10:MyClass1.my_method:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:13:5:13:10:MyClass1.my_method:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:18:1:18:8:MyClass2:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:20:5:20:12:MyClass2.my_method:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:25:1:25:13:MyClass3:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:27:5:27:17:MyClass3.my_method:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:32:1:32:15:MyClass4:typing.final is not supported by all versions included in the py-version setting:HIGH +using-final-decorator-in-unsupported-version:34:5:34:19:MyClass4.my_method:typing.final is not supported by all versions included in the py-version setting:HIGH diff --git a/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.py b/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.py new file mode 100644 index 0000000000..66d56bd4c7 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.py @@ -0,0 +1,5 @@ +# pylint: disable=missing-function-docstring, missing-module-docstring, line-too-long +# +1: [using-generic-type-syntax-in-unsupported-version, using-generic-type-syntax-in-unsupported-version] +type Point[T] = tuple[float, float] +# +1: [using-generic-type-syntax-in-unsupported-version, using-generic-type-syntax-in-unsupported-version] +type Alias[*Ts] = tuple[*Ts] diff --git a/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.rc b/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.rc new file mode 100644 index 0000000000..884bba53e2 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.rc @@ -0,0 +1,5 @@ +[main] +py-version=3.11 + +[testoptions] +min_pyver=3.12 diff --git a/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.txt b/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.txt new file mode 100644 index 0000000000..7b7deed82c --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_generic_type_syntax.txt @@ -0,0 +1,4 @@ +using-generic-type-syntax-in-unsupported-version:3:0:3:35::Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting:HIGH +using-generic-type-syntax-in-unsupported-version:3:11:3:12::Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting:HIGH +using-generic-type-syntax-in-unsupported-version:5:0:5:28::Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting:HIGH +using-generic-type-syntax-in-unsupported-version:5:11:5:14::Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting:HIGH diff --git a/tests/functional/u/unsupported/unsupported_version_for_posonly_args.py b/tests/functional/u/unsupported/unsupported_version_for_posonly_args.py new file mode 100644 index 0000000000..27c507d2d0 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_posonly_args.py @@ -0,0 +1,3 @@ +# pylint: disable=missing-function-docstring, missing-module-docstring +def add(x, y, /): # [using-positional-only-args-in-unsupported-version] + return x + y diff --git a/tests/functional/u/unsupported/unsupported_version_for_posonly_args.rc b/tests/functional/u/unsupported/unsupported_version_for_posonly_args.rc new file mode 100644 index 0000000000..77eb3be645 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_posonly_args.rc @@ -0,0 +1,2 @@ +[main] +py-version=3.7 diff --git a/tests/functional/u/unsupported/unsupported_version_for_posonly_args.txt b/tests/functional/u/unsupported/unsupported_version_for_posonly_args.txt new file mode 100644 index 0000000000..4ae1b27568 --- /dev/null +++ b/tests/functional/u/unsupported/unsupported_version_for_posonly_args.txt @@ -0,0 +1 @@ +using-positional-only-args-in-unsupported-version:2:0:None:None:add:Positional-only arguments are not supported by all versions included in the py-version setting:HIGH diff --git a/tests/functional/u/unused/unused_import.py b/tests/functional/u/unused/unused_import.py index c8e7c5640b..a2e3ceca3f 100644 --- a/tests/functional/u/unused/unused_import.py +++ b/tests/functional/u/unused/unused_import.py @@ -39,6 +39,8 @@ class SomeClass: import xml +example: t.Annotated[str, "Path"] = "/foo/bar" + def get_ordered_dict() -> "collections.OrderedDict": return [] diff --git a/tests/functional/u/unused/unused_import.txt b/tests/functional/u/unused/unused_import.txt index f356843fa9..b8f1b2f8fe 100644 --- a/tests/functional/u/unused/unused_import.txt +++ b/tests/functional/u/unused/unused_import.txt @@ -7,8 +7,8 @@ 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:17:0:17:40::Unused SomeOtherName imported from fake:UNDEFINED -unused-import:54:0:54:9::Unused import os:UNDEFINED -unused-import:89:4:89:19::Unused import unittest:UNDEFINED -unused-import:91:4:91:15::Unused import uuid:UNDEFINED -unused-import:93:4:93:19::Unused import warnings:UNDEFINED -unused-import:95:4:95:21::Unused import compileall:UNDEFINED +unused-import:56:0:56:9::Unused import os:UNDEFINED +unused-import:91:4:91:19::Unused import unittest:UNDEFINED +unused-import:93:4:93:15::Unused import uuid:UNDEFINED +unused-import:95:4:95:19::Unused import warnings:UNDEFINED +unused-import:97:4:97: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 deleted file mode 100644 index 2a897b1741..0000000000 --- a/tests/functional/u/unused/unused_import_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -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 deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/u/unused/unused_import_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[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 deleted file mode 100644 index 50e5ad5a96..0000000000 --- a/tests/functional/u/unused/unused_import_py39.txt +++ /dev/null @@ -1 +0,0 @@ -unused-import:6:0:6:24::Unused Path imported from pathlib:UNDEFINED diff --git a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py39.rc b/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py39.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/u/unused/unused_name_in_string_literal_type_annotation_py39.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_string.txt b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_string.txt index 5f07a683c6..191de3a386 100644 --- a/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_string.txt +++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_string.txt @@ -1,6 +1,6 @@ -use-implicit-booleaness-not-comparison-to-string:6:3:6:10::"""X is ''"" can be simplified to ""not X"", if it is striclty a string, as an empty string is falsey":HIGH -use-implicit-booleaness-not-comparison-to-string:9:3:9:14::"""Y is not ''"" can be simplified to ""Y"", if it is striclty a string, as an empty string is falsey":HIGH -use-implicit-booleaness-not-comparison-to-string:12:3:12:10::"""X == ''"" can be simplified to ""not X"", if it is striclty a string, as an empty string is falsey":HIGH -use-implicit-booleaness-not-comparison-to-string:15:3:15:10::"""Y != ''"" can be simplified to ""Y"", if it is striclty a string, as an empty string is falsey":HIGH -use-implicit-booleaness-not-comparison-to-string:18:3:18:10::"""'' == Y"" can be simplified to ""not Y"", if it is striclty a string, as an empty string is falsey":HIGH -use-implicit-booleaness-not-comparison-to-string:21:3:21:10::"""'' != X"" can be simplified to ""X"", if it is striclty a string, as an empty string is falsey":HIGH +use-implicit-booleaness-not-comparison-to-string:6:3:6:10::"""X is ''"" can be simplified to ""not X"", if it is strictly a string, as an empty string is falsey":HIGH +use-implicit-booleaness-not-comparison-to-string:9:3:9:14::"""Y is not ''"" can be simplified to ""Y"", if it is strictly a string, as an empty string is falsey":HIGH +use-implicit-booleaness-not-comparison-to-string:12:3:12:10::"""X == ''"" can be simplified to ""not X"", if it is strictly a string, as an empty string is falsey":HIGH +use-implicit-booleaness-not-comparison-to-string:15:3:15:10::"""Y != ''"" can be simplified to ""Y"", if it is strictly a string, as an empty string is falsey":HIGH +use-implicit-booleaness-not-comparison-to-string:18:3:18:10::"""'' == Y"" can be simplified to ""not Y"", if it is strictly a string, as an empty string is falsey":HIGH +use-implicit-booleaness-not-comparison-to-string:21:3:21:10::"""'' != X"" can be simplified to ""X"", if it is strictly a string, as an empty string is falsey":HIGH diff --git a/tests/functional/u/use/use_implicit_booleaness_not_len.py b/tests/functional/u/use/use_implicit_booleaness_not_len.py index 1261aa3014..4a1063e757 100644 --- a/tests/functional/u/use/use_implicit_booleaness_not_len.py +++ b/tests/functional/u/use/use_implicit_booleaness_not_len.py @@ -125,7 +125,7 @@ class ChildClassWithoutBool(ClassWithoutBool): assert len(ChildClassWithoutBool()) # [use-implicit-booleaness-not-len] assert len(range(0)) # [use-implicit-booleaness-not-len] assert len([t + 1 for t in []]) # [use-implicit-booleaness-not-len] - assert len(u + 1 for u in []) # [use-implicit-booleaness-not-len] + assert len(u + 1 for u in []) # Should be fine assert len({"1":(v + 1) for v in {}}) # [use-implicit-booleaness-not-len] assert len(set((w + 1) for w in set())) # [use-implicit-booleaness-not-len] @@ -189,3 +189,7 @@ def github_issue_4215(): if len('TEST'): pass + +def github_issue_10100(): + if len((x for x in [1, 2, 3])): # Should be fine + print("yay!") 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 85917de828..868e746bfb 100644 --- a/tests/functional/u/use/use_implicit_booleaness_not_len.txt +++ b/tests/functional/u/use/use_implicit_booleaness_not_len.txt @@ -18,7 +18,6 @@ use-implicit-booleaness-not-len:124:11:124:34:github_issue_1879:Do not use `len( 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 diff --git a/tests/functional/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py index 5e589e81bb..b398afa370 100644 --- a/tests/functional/u/used/used_before_assignment.py +++ b/tests/functional/u/used/used_before_assignment.py @@ -2,7 +2,7 @@ # pylint: disable=consider-using-f-string, missing-function-docstring import datetime import sys -# from typing import NoReturn # uncomment when we reunite with used_before_assignment_py38.py +from typing import NoReturn MSG = "hello %s" % MSG # [used-before-assignment] @@ -206,3 +206,19 @@ def inner_if_continues_outer_if_has_no_other_statements(): else: order = None print(order) + + +class PlatformChecks: # pylint: disable=missing-docstring + """https://github.com/pylint-dev/pylint/issues/9674""" + def skip(self, msg) -> NoReturn: + raise Exception(msg) # pylint: disable=broad-exception-raised + + def print_platform_specific_command(self): + if sys.platform == "linux": + cmd = "ls" + elif sys.platform == "win32": + cmd = "dir" + else: + self.skip("only runs on Linux/Windows") + + print(cmd) diff --git a/tests/functional/u/used/used_before_assignment_py38.py b/tests/functional/u/used/used_before_assignment_py38.py deleted file mode 100644 index 81d69268e2..0000000000 --- a/tests/functional/u/used/used_before_assignment_py38.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Temporary file until we drop python 3.8 -See https://github.com/pylint-dev/pylint/issues/9751 -Please reunite with used_before_assignment.py at this point -""" - -# pylint: disable=missing-docstring - -import sys -from typing import NoReturn - - -class PlatformChecks: - """https://github.com/pylint-dev/pylint/issues/9674""" - def skip(self, msg) -> NoReturn: - raise Exception(msg) # pylint: disable=broad-exception-raised - - def print_platform_specific_command(self): - if sys.platform == "linux": - cmd = "ls" - elif sys.platform == "win32": - cmd = "dir" - else: - self.skip("only runs on Linux/Windows") - - print(cmd) diff --git a/tests/functional/u/used/used_before_assignment_py38.rc b/tests/functional/u/used/used_before_assignment_py38.rc deleted file mode 100644 index 16b75eea75..0000000000 --- a/tests/functional/u/used/used_before_assignment_py38.rc +++ /dev/null @@ -1,2 +0,0 @@ -[testoptions] -min_pyver=3.9 diff --git a/tests/functional/w/wrong_import_order.txt b/tests/functional/w/wrong_import_order.txt index 068d2140d8..9f143c292d 100644 --- a/tests/functional/w/wrong_import_order.txt +++ b/tests/functional/w/wrong_import_order.txt @@ -1,16 +1,16 @@ -wrong-import-order:12:0:12:14::"standard import ""os.path"" should be placed before third party import ""six""":UNDEFINED -wrong-import-order:14:0:14:10::"standard import ""sys"" should be placed before third party imports ""six"", ""astroid.are_exclusive""":UNDEFINED -wrong-import-order:15:0:15:15::"standard import ""datetime"" should be placed before third party imports ""six"", ""astroid.are_exclusive""":UNDEFINED -wrong-import-order:18:0:18:22::"third party import ""totally_missing"" should be placed before local import ""package.Class""":UNDEFINED -wrong-import-order:20:0:20:14::"third party import ""astroid"" should be placed before local imports ""package.Class"", "".package""":UNDEFINED -wrong-import-order:22:0:22:22::"first party import ""pylint.checkers"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED -wrong-import-order:23:0:23:25::"first party import ""pylint.config"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED -wrong-import-order:24:0:24:17::"first party import ""pylint.sys"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED -wrong-import-order:25:0:25:28::"first party import ""pylint.pyreverse"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED -wrong-import-order:30:0:30:40::"third party import ""six.moves.urllib.parse.quote"" should be placed before first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"", ""pylint.pyreverse"" and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED -wrong-import-order:31:0:31:23::"first party import ""pylint.constants"" should be placed before local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED -wrong-import-order:32:0:32:19::"standard import ""re"" should be placed before third party imports ""six"", ""astroid.are_exclusive"", ""unused_import"", ""totally_missing"", ""astroid"", ""six.moves.urllib.parse.quote"", first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"", ""pylint.pyreverse"", ""pylint.constants"", and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED -wrong-import-order:32:0:32:19::"third party import ""requests"" should be placed before first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"", ""pylint.pyreverse"", ""pylint.constants"" and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED -wrong-import-order:33:0:33:24::"first party import ""pylint.exceptions"" should be placed before local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED -wrong-import-order:34:0:34:21::"first party import ""pylint.message"" should be placed before local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED -wrong-import-order:35:0:35:11::"standard import ""time"" should be placed before third party imports ""six"", ""astroid.are_exclusive"", ""unused_import"" (...) ""astroid"", ""six.moves.urllib.parse.quote"", ""requests"", first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"" (...) ""pylint.constants"", ""pylint.exceptions"", ""pylint.message"", and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:12:0:12:14::"standard import ""os.path"" should be placed before third party import ""six""":UNDEFINED +wrong-import-order:14:0:14:10::"standard import ""sys"" should be placed before third party imports ""six"", ""astroid.are_exclusive""":UNDEFINED +wrong-import-order:15:0:15:15::"standard import ""datetime"" should be placed before third party imports ""six"", ""astroid.are_exclusive""":UNDEFINED +wrong-import-order:18:0:18:22::"third party import ""totally_missing"" should be placed before local import ""package.Class""":UNDEFINED +wrong-import-order:20:0:20:14::"third party import ""astroid"" should be placed before local imports ""package.Class"", "".package""":UNDEFINED +wrong-import-order:22:0:22:22::"first party import ""pylint.checkers"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED +wrong-import-order:23:0:23:25::"first party import ""pylint.config"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED +wrong-import-order:24:0:24:17::"first party import ""pylint.sys"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED +wrong-import-order:25:0:25:28::"first party import ""pylint.pyreverse"" should be placed before local imports ""package.Class"", "".package"", "".package2""":UNDEFINED +wrong-import-order:30:0:30:40::"third party import ""six.moves.urllib.parse.quote"" should be placed before first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"", ""pylint.pyreverse"" and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:31:0:31:23::"first party import ""pylint.constants"" should be placed before local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:32:0:32:19::"standard import ""re"" should be placed before third party imports ""six"", ""astroid.are_exclusive"", ""unused_import"", ""totally_missing"", ""astroid"", ""six.moves.urllib.parse.quote"", first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"", ""pylint.pyreverse"", ""pylint.constants"", and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:32:0:32:19::"third party import ""requests"" should be placed before first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"", ""pylint.pyreverse"", ""pylint.constants"" and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:33:0:33:24::"first party import ""pylint.exceptions"" should be placed before local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:34:0:34:21::"first party import ""pylint.message"" should be placed before local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED +wrong-import-order:35:0:35:11::"standard import ""time"" should be placed before third party imports ""six"", ""astroid.are_exclusive"", ""unused_import"" (...) ""astroid"", ""six.moves.urllib.parse.quote"", ""requests"", first party imports ""pylint.checkers"", ""pylint.config"", ""pylint.sys"" (...) ""pylint.constants"", ""pylint.exceptions"", ""pylint.message"", and local imports ""package.Class"", "".package"", "".package2"" (...) ""package3.Class3"", "".package4"", ""package4.Class4""":UNDEFINED diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 34133d759b..e8d38e6a57 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -114,6 +114,25 @@ def test__is_in_ignore_list_re_match() -> None: "path": INIT_PATH, } +# A directory that is not a python package. +REPORTERS_PATH = Path(__file__).parent.parent / "reporters" +test_reporters = { # pylint: disable=consider-using-namedtuple-or-dataclass + str(REPORTERS_PATH / "unittest_json_reporter.py"): { + "path": str(REPORTERS_PATH / "unittest_json_reporter.py"), + "name": "reporters.unittest_json_reporter", + "isarg": False, + "basepath": str(REPORTERS_PATH / "__init__.py"), + "basename": "reporters", + }, + str(REPORTERS_PATH / "unittest_reporting.py"): { + "path": str(REPORTERS_PATH / "unittest_reporting.py"), + "name": "reporters.unittest_reporting", + "isarg": False, + "basepath": str(REPORTERS_PATH / "__init__.py"), + "basename": "reporters", + }, +} + def _list_expected_package_modules( deduplicating: bool = False, @@ -174,6 +193,7 @@ class Checker(BaseChecker): for module in _list_expected_package_modules() }, ), + ([str(Path(__file__).parent.parent / "reporters")], test_reporters), ], ) @set_config(ignore_paths="") diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 18305b73a8..7ba8879e9a 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -20,6 +20,7 @@ from shutil import copy, rmtree from unittest import mock +import astroid import platformdirs import pytest from astroid import nodes @@ -858,7 +859,7 @@ def test_init_hooks_called_before_load_plugins() -> None: def test_analyze_explicit_script(linter: PyLinter) -> None: linter.set_reporter(testutils.GenericTestReporter()) - linter.check([os.path.join(DATA_DIR, "ascript")]) + linter.check([os.path.join(DATA_DIR, "a_script")]) assert len(linter.reporter.messages) == 1 assert linter.reporter.messages[0] == Message( msg_id="C0301", @@ -869,11 +870,11 @@ def test_analyze_explicit_script(linter: PyLinter) -> None: description="Warning without any associated confidence level.", ), location=MessageLocationTuple( - abspath=os.path.join(abspath(dirname(__file__)), "ascript").replace( - f"lint{os.path.sep}ascript", f"data{os.path.sep}ascript" + abspath=os.path.join(abspath(dirname(__file__)), "a_script").replace( + f"lint{os.path.sep}a_script", f"data{os.path.sep}a_script" ), - path=f"tests{os.path.sep}data{os.path.sep}ascript", - module="data.ascript", + path=f"tests{os.path.sep}data{os.path.sep}a_script", + module="data.a_script", obj="", line=2, column=0, @@ -1053,7 +1054,9 @@ def test_finds_pyi_file() -> None: exit=False, ) assert run.linter.current_file is not None - assert run.linter.current_file.endswith("foo.pyi") + assert run.linter.current_file.endswith( + "a_module_that_we_definitely_dont_use_in_the_functional_tests.pyi" + ) def test_recursive_finds_pyi_file() -> None: @@ -1068,7 +1071,9 @@ def test_recursive_finds_pyi_file() -> None: exit=False, ) assert run.linter.current_file is not None - assert run.linter.current_file.endswith("foo.pyi") + assert run.linter.current_file.endswith( + "a_module_that_we_definitely_dont_use_in_the_functional_tests.pyi" + ) def test_no_false_positive_from_pyi_stub() -> None: @@ -1126,6 +1131,9 @@ def test_recursive_ignore(ignore_parameter: str, ignore_parameter_value: str) -> ): module = os.path.abspath(join(REGRTEST_DATA_DIR, *regrtest_data_module)) assert module in linted_file_paths + # We lint the modules in `regrtest` in other tests as well. Prevent test pollution by + # explicitly clearing the astroid caches. + astroid.MANAGER.clear_cache() def test_source_roots_globbing() -> None: diff --git a/tests/message/unittest_message.py b/tests/message/unittest_message.py index 6fddd804ce..59ad3ff4c3 100644 --- a/tests/message/unittest_message.py +++ b/tests/message/unittest_message.py @@ -53,6 +53,7 @@ def build_message( expected = ( "2:5:6: E1234: Duplicate keyword argument %r in %s call (duplicate-keyword-arg)" ) + # pylint: disable=possibly-used-before-assignment e1234 = build_message(e1234_message_definition, e1234_location_values) w1234 = build_message(w1234_message_definition, w1234_location_values) assert e1234.location == e1234_location_values diff --git a/tests/profile/test_profile_against_externals.py b/tests/profile/test_profile_against_externals.py deleted file mode 100644 index b646ede901..0000000000 --- a/tests/profile/test_profile_against_externals.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Profiles basic -jX functionality.""" - -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt - -# pylint: disable=missing-function-docstring - -from __future__ import annotations - -import os -import pprint -from pathlib import Path - -import pytest -from git.repo import Repo - -from pylint.testutils import GenericTestReporter as Reporter -from pylint.testutils._run import _Run as Run - - -def _get_py_files(scanpath: str) -> list[str]: - assert os.path.exists(scanpath), f"Dir not found {scanpath}" - - filepaths: list[str] = [] - for dirpath, dirnames, filenames in os.walk(scanpath): - dirnames[:] = [dirname for dirname in dirnames if dirname != "__pycache__"] - filepaths.extend( - [ - os.path.join(dirpath, filename) - for filename in filenames - if filename.endswith(".py") - ] - ) - return filepaths - - -@pytest.mark.skipif( - not os.environ.get("PYTEST_PROFILE_EXTERNAL", False), - reason="PYTEST_PROFILE_EXTERNAL, not set, assuming not a profile run", -) -@pytest.mark.parametrize( - "name,git_repo", [("numpy", "https://github.com/numpy/numpy.git")] -) -def test_run(tmp_path: Path, name: str, git_repo: str) -> None: - """Runs pylint against external sources.""" - checkoutdir = tmp_path / name - checkoutdir.mkdir() - Repo.clone_from(url=git_repo, to_path=checkoutdir, depth=1) - filepaths = _get_py_files(scanpath=str(checkoutdir)) - print(f"Have {len(filepaths)} files") - - runner = Run(filepaths, reporter=Reporter(), exit=False) - - print( - f"Had {len(filepaths)} files with {len(runner.linter.reporter.messages)} messages" - ) - pprint.pprint(runner.linter.reporter.messages) diff --git a/tests/pyreverse/test_diadefs.py b/tests/pyreverse/test_diadefs.py index 2b8bd5e324..06affe12e5 100644 --- a/tests/pyreverse/test_diadefs.py +++ b/tests/pyreverse/test_diadefs.py @@ -8,7 +8,6 @@ from __future__ import annotations -import sys from collections.abc import Iterator from pathlib import Path @@ -248,7 +247,6 @@ 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: GetProjectCallable ) -> None: diff --git a/tests/pyreverse/test_inspector.py b/tests/pyreverse/test_inspector.py index d28d99584f..b388569ac7 100644 --- a/tests/pyreverse/test_inspector.py +++ b/tests/pyreverse/test_inspector.py @@ -26,7 +26,7 @@ @pytest.fixture -def project(get_project: GetProjectCallable) -> Generator[Project, None, None]: +def project(get_project: GetProjectCallable) -> Generator[Project]: with _test_cwd(TESTS): project = get_project("data", "data") linker = inspector.Linker(project) diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index e8e46df2c1..59fcab16f4 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -65,6 +65,42 @@ def test_project_root_in_sys_path() -> None: assert sys.path == [PROJECT_ROOT_DIR] +def test_discover_package_path_source_root_as_parent(tmp_path: Any) -> None: + """Test discover_package_path when source root is a parent of the module.""" + # Create this temporary structure: + # /tmp_path/ + # └── project/ + # └── my-package/ + # └── __init__.py + project_dir = tmp_path / "project" + package_dir = project_dir / "mypackage" + package_dir.mkdir(parents=True) + (package_dir / "__init__.py").touch() + + # Test with project_dir as source root (parent of package) + result = discover_package_path(str(package_dir), [str(project_dir)]) + assert result == str(project_dir) + + +def test_discover_package_path_source_root_as_child(tmp_path: Any) -> None: + """Test discover_package_path when source root is a child of the module.""" + # Create this temporary structure: + # /tmp_path/ + # └── project/ + # └── src/ + # └── my-package/ + # └── __init__.py + project_dir = tmp_path / "project" + src_dir = project_dir / "src" + package_dir = src_dir / "mypackage" + package_dir.mkdir(parents=True) + (package_dir / "__init__.py").touch() + + # Test with src_dir as source root (child of project) + result = discover_package_path(str(project_dir), [str(src_dir)]) + assert result == str(src_dir) + + @mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) @mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) @mock.patch("pylint.pyreverse.main.writer") diff --git a/tests/regrtest_data/pyi/foo.py b/tests/regrtest_data/pyi/a_module_that_we_definitely_dont_use_in_the_functional_tests.py similarity index 100% rename from tests/regrtest_data/pyi/foo.py rename to tests/regrtest_data/pyi/a_module_that_we_definitely_dont_use_in_the_functional_tests.py diff --git a/tests/regrtest_data/pyi/a_module_that_we_definitely_dont_use_in_the_functional_tests.pyi b/tests/regrtest_data/pyi/a_module_that_we_definitely_dont_use_in_the_functional_tests.pyi new file mode 100644 index 0000000000..2e936e7249 --- /dev/null +++ b/tests/regrtest_data/pyi/a_module_that_we_definitely_dont_use_in_the_functional_tests.pyi @@ -0,0 +1,5 @@ +# This module is named in a particular way to prevent test pollution. It was previously named 'foo' and +# all mentions of 'foo' were wrongly resolved to this stub file. +foo = 1 + +def three_item_iterable(): ... diff --git a/tests/regrtest_data/pyi/foo.pyi b/tests/regrtest_data/pyi/foo.pyi deleted file mode 100644 index a84058c7c1..0000000000 --- a/tests/regrtest_data/pyi/foo.pyi +++ /dev/null @@ -1,4 +0,0 @@ -foo = 1 - -def three_item_iterable(): - ... diff --git a/tests/regrtest_data/uses_module_with_stub.py b/tests/regrtest_data/uses_module_with_stub.py index d7cbf63d9c..dad749dd9a 100644 --- a/tests/regrtest_data/uses_module_with_stub.py +++ b/tests/regrtest_data/uses_module_with_stub.py @@ -1,5 +1,5 @@ """If the stub is preferred over the .py, this might emit not-an-iterable""" -from pyi.foo import three_item_iterable +from pyi.a_module_that_we_definitely_dont_use_in_the_functional_tests import three_item_iterable for val in three_item_iterable(): print(val) diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 969fde3d82..d7c936b97d 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -603,6 +603,7 @@ def test_map_reduce(self, num_files: int, num_jobs: int, num_checkers: int) -> N files=file_infos, ) stats_check_parallel = linter.stats + # pylint: disable=possibly-used-before-assignment assert str(stats_single_proc.by_msg) == str( stats_check_parallel.by_msg ), "Single-proc and check_parallel() should return the same thing" diff --git a/tests/test_self.py b/tests/test_self.py index 1c72bc95f8..d01821c34c 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -219,6 +219,26 @@ def test_disable_all(self) -> None: self._runtest([UNNECESSARY_LAMBDA, "--disable=all"], out=out, code=32) assert "No files to lint: exiting." in out.getvalue().strip() + def test_disable_all_enable_invalid(self) -> None: + # Reproduces issue #9403. If disable=all is used no error was raised for invalid messages unless + # unknown-option-value was manually enabled. + out = StringIO() + self._runtest( + # Enable one valid message to not run into "No files to lint: exiting." + [ + UNNECESSARY_LAMBDA, + "--disable=all", + "--enable=import-error", + "--enable=foo", + ], + out=out, + code=0, + ) + assert ( + "W0012: Unknown option value for '--enable', expected a valid pylint message and got 'foo'" + in out.getvalue().strip() + ) + def test_output_with_verbose(self) -> None: out = StringIO() self._runtest([UNNECESSARY_LAMBDA, "--verbose"], out=out, code=4) @@ -1387,7 +1407,7 @@ def test_output_of_callback_options( [ [["--help-msg", "W0101"], ":unreachable (W0101)", False], [["--help-msg", "WX101"], "No such message id", False], - [["--help-msg"], "--help-msg: expected at least one argumen", True], + [["--help-msg"], "--help-msg: expected at least one argument", True], [["--help-msg", "C0102,C0103"], ":invalid-name (C0103):", False], ], ) diff --git a/tests/test_similar.py b/tests/test_similar.py index 4c12d4366c..c5357f4b6d 100644 --- a/tests/test_similar.py +++ b/tests/test_similar.py @@ -22,7 +22,7 @@ CLEAN_PATH = re.escape(dirname(dirname(__file__)) + os.path.sep) -class TestSimilarCodeChecker: +class TestSymilarCodeChecker: def _runtest(self, args: list[str], code: int) -> None: """Runs the tests and sees if output code is as expected.""" out = StringIO() @@ -161,7 +161,7 @@ def test_duplicate_code_raw_strings_disable_line_disable_all(self) -> None: code=0, ) - def test_duplicate_code_raw_strings_disable_line_midle(self) -> None: + def test_duplicate_code_raw_strings_disable_line_middle(self) -> None: """Tests disabling duplicate-code at a line in the middle of a piece of similar code.""" path = join(DATA, "raw_strings_disable_line_middle") self._runtest( diff --git a/tests/testutils/test_lint_module_output_update.py b/tests/testutils/test_lint_module_output_update.py index 038dc77ed3..6d732a57ab 100644 --- a/tests/testutils/test_lint_module_output_update.py +++ b/tests/testutils/test_lint_module_output_update.py @@ -12,7 +12,6 @@ import pytest -from pylint.constants import IS_PYPY, PY39_PLUS from pylint.testutils import FunctionalTestFile, LintModuleTest from pylint.testutils.functional import LintModuleOutputUpdate @@ -72,10 +71,6 @@ def test_lint_module_output_update_remove_useless_txt( assert not expected_output_file.exists() -@pytest.mark.skipif( - IS_PYPY and not PY39_PLUS, - reason="Requires accurate 'end_col' value to update output", -) @pytest.mark.parametrize( "directory_path", DIRECTORIES, ids=[str(p) for p in DIRECTORIES] ) diff --git a/towncrier.toml b/towncrier.toml index abacccc812..7975e71f3c 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -1,7 +1,7 @@ [tool.towncrier] -version = "3.2.7" +version = "3.3.3" directory = "doc/whatsnew/fragments" -filename = "doc/whatsnew/3/3.2/index.rst" +filename = "doc/whatsnew/3/3.3/index.rst" template = "doc/whatsnew/fragments/_template.rst" issue_format = "`#{issue} `_" wrap = false # doesn't wrap links correctly if beginning with indentation diff --git a/tox.ini b/tox.ini index b3e429cc8c..e44d81759f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.0 -envlist = formatting, py38, py39, py310, py311, py312, pypy, benchmark +envlist = formatting, py39, py310, py311, py312, pypy, benchmark skip_missing_interpreters = true requires = pip >=21.3.1 isolated_build = true @@ -84,14 +84,3 @@ commands = --benchmark-autosave {toxinidir}/tests \ --benchmark-group-by="group" \ {posargs:} - -[testenv:profile_against_external] -setenv = - PYTEST_PROFILE_EXTERNAL = 1 -deps = - -r {toxinidir}/requirements_test.txt - gprof2dot -commands = - pytest --exitfirst \ - --profile-svg \ - {toxinidir}/tests/profile/test_profile_against_externals.py