Compare commits
50 Commits
micha/node
...
jack/i_kno
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7a241e112 | ||
|
|
e73a8ba571 | ||
|
|
a1edb69ea5 | ||
|
|
a0d4e1f854 | ||
|
|
c0d04f2d56 | ||
|
|
8d7d02193e | ||
|
|
78dfc8af0f | ||
|
|
0c84652cc5 | ||
|
|
560ae04346 | ||
|
|
a357a68fc9 | ||
|
|
00e7d1ffd6 | ||
|
|
f4d0273532 | ||
|
|
e9cac3684a | ||
|
|
92a302e291 | ||
|
|
7b8161e80d | ||
|
|
e9b0c33703 | ||
|
|
82391b5675 | ||
|
|
464144f1c6 | ||
|
|
002f9057db | ||
|
|
f3a27406c9 | ||
|
|
2c9da80985 | ||
|
|
8e61da740a | ||
|
|
e506296cec | ||
|
|
966cc9d6e9 | ||
|
|
7b27fe966e | ||
|
|
966fd6f57a | ||
|
|
4f60f0e925 | ||
|
|
059e90a98f | ||
|
|
a4562ac673 | ||
|
|
021a70d30c | ||
|
|
fddf2f33d2 | ||
|
|
b4c42eb83b | ||
|
|
2a2cc37158 | ||
|
|
8a217e5920 | ||
|
|
f7973ac870 | ||
|
|
3560f86450 | ||
|
|
f22da352db | ||
|
|
cb530a0216 | ||
|
|
90026047f9 | ||
|
|
26f736bc46 | ||
|
|
c9f95e8714 | ||
|
|
3da8b51dc1 | ||
|
|
3cbf2fe82e | ||
|
|
221f3258d4 | ||
|
|
3fae3f248c | ||
|
|
6c73837bc4 | ||
|
|
6f8f38cb68 | ||
|
|
4e2d6f5e45 | ||
|
|
3ed3852c38 | ||
|
|
dca594f89f |
18
.github/workflows/ci.yaml
vendored
18
.github/workflows/ci.yaml
vendored
@@ -240,11 +240,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -298,11 +298,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -325,7 +325,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -903,7 +903,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -911,7 +911,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3.5.0
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
@@ -936,7 +936,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -944,7 +944,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@0010eb0ca6e89b80c88e8edaaa07cfe5f3e6664d # v3.5.0
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
144
.github/workflows/sync_typeshed.yaml
vendored
144
.github/workflows/sync_typeshed.yaml
vendored
@@ -1,5 +1,25 @@
|
||||
name: Sync typeshed
|
||||
|
||||
# How this works:
|
||||
#
|
||||
# 1. A Linux worker:
|
||||
# a. Checks out Ruff and typeshed
|
||||
# b. Deletes the vendored typeshed stdlib stubs from Ruff
|
||||
# c. Copies the latest versions of the stubs from typeshed
|
||||
# d. Uses docstring-adder to sync all docstrings available on Linux
|
||||
# e. Creates a new branch on the upstream astral-sh/ruff repository
|
||||
# f. Commits the changes it's made and pushes them to the new upstream branch
|
||||
# 2. Once the Linux worker is done, a Windows worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on Windows that are not available on Linux
|
||||
# c. Commits the changes and pushes them to the same upstream branch
|
||||
# 3. Once the Windows worker is done, a MacOS worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||
# c. Commits the changes and pushes them to the same upstream branch
|
||||
# d. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
@@ -10,7 +30,13 @@ env:
|
||||
FORCE_COLOR: 1
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
# The name of the upstream branch that the first worker creates,
|
||||
# and which all three workers push to.
|
||||
UPSTREAM_BRANCH: typeshedbot/sync-typeshed
|
||||
|
||||
jobs:
|
||||
# Sync typeshed stubs, and sync all docstrings available on Linux.
|
||||
# Push the changes to a new branch on the upstream repository.
|
||||
sync:
|
||||
name: Sync typeshed
|
||||
runs-on: ubuntu-latest
|
||||
@@ -19,7 +45,6 @@ jobs:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
name: Checkout Ruff
|
||||
@@ -36,37 +61,130 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync typeshed
|
||||
id: sync
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf ruff/crates/ty_vendored/vendor/typeshed
|
||||
mkdir ruff/crates/ty_vendored/vendor/typeshed
|
||||
cp typeshed/README.md ruff/crates/ty_vendored/vendor/typeshed
|
||||
cp typeshed/LICENSE ruff/crates/ty_vendored/vendor/typeshed
|
||||
|
||||
# The pyproject.toml file is needed by a later job for the black configuration.
|
||||
# It's deleted before creating the PR.
|
||||
cp typeshed/pyproject.toml ruff/crates/ty_vendored/vendor/typeshed
|
||||
|
||||
cp -r typeshed/stdlib ruff/crates/ty_vendored/vendor/typeshed/stdlib
|
||||
rm -rf ruff/crates/ty_vendored/vendor/typeshed/stdlib/@tests
|
||||
git -C typeshed rev-parse HEAD > ruff/crates/ty_vendored/vendor/typeshed/source_commit.txt
|
||||
- name: Commit the changes
|
||||
id: commit
|
||||
if: ${{ steps.sync.outcome == 'success' }}
|
||||
run: |
|
||||
cd ruff
|
||||
git checkout -b typeshedbot/sync-typeshed
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)"
|
||||
- name: Create a PR
|
||||
if: ${{ steps.sync.outcome == 'success' && steps.commit.outcome == 'success' }}
|
||||
git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
|
||||
- name: Sync Linux docstrings
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
cd ruff
|
||||
git push --force origin typeshedbot/sync-typeshed
|
||||
./scripts/codemod_docstrings.sh
|
||||
git commit -am "Sync Linux docstrings" --allow-empty
|
||||
- name: Push the changes
|
||||
id: commit
|
||||
if: ${{ success() }}
|
||||
run: git -C ruff push --force --set-upstream origin "${UPSTREAM_BRANCH}"
|
||||
|
||||
# Checkout the branch created by the sync job,
|
||||
# and sync all docstrings available on Windows that are not available on Linux.
|
||||
# Commit the changes and push them to the same branch.
|
||||
docstrings-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 20
|
||||
needs: [sync]
|
||||
|
||||
# Don't run the cron job on forks.
|
||||
# The job will also be skipped if the sync job failed, because it's specified in `needs` above,
|
||||
# and we haven't used `always()` in the `if` condition here
|
||||
# (https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-requiring-successful-dependent-jobs)
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync Windows docstrings
|
||||
id: docstrings
|
||||
shell: bash
|
||||
run: ./scripts/codemod_docstrings.sh
|
||||
- name: Commit the changes
|
||||
if: ${{ steps.docstrings.outcome == 'success' }}
|
||||
run: |
|
||||
git commit -am "Sync Windows docstrings" --allow-empty
|
||||
git push
|
||||
|
||||
# Checkout the branch created by the sync job,
|
||||
# and sync all docstrings available on macOS that are not available on Linux or Windows.
|
||||
# Push the changes to the same branch and create a PR against the `main` branch using that branch.
|
||||
docstrings-macos-and-pr:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 20
|
||||
needs: [sync, docstrings-windows]
|
||||
|
||||
# Don't run the cron job on forks.
|
||||
# The job will also be skipped if the sync or docstrings-windows jobs failed,
|
||||
# because they're specified in `needs` above and we haven't used an `always()` condition in the `if` here
|
||||
# (https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-requiring-successful-dependent-jobs)
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync macOS docstrings
|
||||
run: ./scripts/codemod_docstrings.sh
|
||||
- name: Commit and push the changes
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
git commit -am "Sync macOS docstrings" --allow-empty
|
||||
|
||||
# Here we just reformat the codemodded stubs so that they are
|
||||
# consistent with the other typeshed stubs around them.
|
||||
# Typeshed formats code using black in their CI, so we just invoke
|
||||
# black on the stubs the same way that typeshed does.
|
||||
uvx black crates/ty_vendored/vendor/typeshed/stdlib --config crates/ty_vendored/vendor/typeshed/pyproject.toml || true
|
||||
git commit -am "Format codemodded docstrings" --allow-empty
|
||||
|
||||
rm crates/ty_vendored/vendor/typeshed/pyproject.toml
|
||||
git commit -am "Remove pyproject.toml file"
|
||||
|
||||
git push
|
||||
- name: Create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"
|
||||
|
||||
create-issue-on-failure:
|
||||
name: Create an issue if the typeshed sync failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [sync]
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.sync.result == 'failure' }}
|
||||
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
76
.github/workflows/ty-ecosystem-report.yaml
vendored
Normal file
76
.github/workflows/ty-ecosystem-report.yaml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: ty ecosystem-report
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run every Wednesday at 5:00 UTC:
|
||||
- cron: 0 5 * * 3
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-report:
|
||||
name: Create ecosystem report
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Create report
|
||||
shell: bash
|
||||
run: |
|
||||
cd ruff
|
||||
|
||||
echo "Enabling configuration overloads (see .github/mypy-primer-ty.toml)"
|
||||
mkdir -p ~/.config/ty
|
||||
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--projects ruff/crates/ty_python_semantic/resources/primer/good.txt \
|
||||
--output ecosystem-diagnostics.json
|
||||
|
||||
mkdir dist
|
||||
|
||||
ecosystem-analyzer \
|
||||
generate-report \
|
||||
--max-diagnostics-per-project=1200 \
|
||||
ecosystem-diagnostics.json \
|
||||
--output dist/index.html
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}
|
||||
1
.github/zizmor.yml
vendored
1
.github/zizmor.yml
vendored
@@ -11,6 +11,7 @@ rules:
|
||||
- build-docker.yml
|
||||
- publish-playground.yml
|
||||
- ty-ecosystem-analyzer.yaml
|
||||
- ty-ecosystem-report.yaml
|
||||
excessive-permissions:
|
||||
# it's hard to test what the impact of removing these ignores would be
|
||||
# without actually running the release workflow...
|
||||
|
||||
@@ -81,7 +81,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.2
|
||||
rev: v0.12.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -128,5 +128,10 @@ repos:
|
||||
# but the integration only works if shellcheck is installed
|
||||
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.10.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
ci:
|
||||
skip: [cargo-fmt, dev-generate-all]
|
||||
|
||||
116
Cargo.lock
generated
116
Cargo.lock
generated
@@ -396,9 +396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.40"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -406,9 +406,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.40"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -449,9 +449,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.40"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -591,7 +591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -600,7 +600,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -933,7 +933,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1013,7 +1013,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1133,9 +1133,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aac2af9f9a6a50e31b1e541d05b7925add83d3982c2793193fe9d4ee584323c"
|
||||
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1144,9 +1144,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a0312efd19e1c45922dfcc2d6806d3ffc4bca261f89f31fcc4f63f438d885"
|
||||
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
@@ -1586,7 +1586,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.1",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1650,7 +1650,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2465,7 +2465,7 @@ dependencies = [
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2763,7 +2763,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
"wild",
|
||||
@@ -2779,7 +2779,7 @@ dependencies = [
|
||||
"ruff_annotate_snippets",
|
||||
"serde",
|
||||
"snapbox",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tryfn",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -2895,7 +2895,7 @@ dependencies = [
|
||||
"similar",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -3016,7 +3016,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.1",
|
||||
@@ -3266,7 +3266,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
@@ -3356,7 +3356,7 @@ dependencies = [
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3391,7 +3391,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3576,6 +3576,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_test"
|
||||
version = "1.0.177"
|
||||
@@ -3781,7 +3790,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3981,11 +3990,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned 1.0.0",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
@@ -3995,6 +4019,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
@@ -4003,17 +4036,25 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
name = "toml_parser"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@@ -4136,7 +4177,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
@@ -4197,7 +4238,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"ty_ide",
|
||||
"ty_python_semantic",
|
||||
@@ -4266,6 +4307,7 @@ dependencies = [
|
||||
"lsp-types",
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
@@ -4314,7 +4356,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
@@ -4808,7 +4850,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -165,7 +165,7 @@ tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "2.0.0" }
|
||||
tikv-jemallocator = { version = "0.6.0" }
|
||||
toml = { version = "0.8.11" }
|
||||
toml = { version = "0.9.0" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-flame = { version = "0.2.0" }
|
||||
tracing-indicatif = { version = "0.3.11" }
|
||||
|
||||
@@ -131,6 +131,7 @@ pub fn run(
|
||||
}: Args,
|
||||
) -> Result<ExitStatus> {
|
||||
{
|
||||
ruff_db::set_program_version(crate::version::version().to_string()).unwrap();
|
||||
let default_panic_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
#[expect(clippy::print_stderr)]
|
||||
|
||||
@@ -16,7 +16,7 @@ use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter, JunitEmitter,
|
||||
PylintEmitter, RdjsonEmitter, SarifEmitter, TextEmitter,
|
||||
SarifEmitter, TextEmitter,
|
||||
};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
@@ -238,7 +238,11 @@ impl Printer {
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Rdjson => {
|
||||
RdjsonEmitter.emit(writer, &diagnostics.inner, &context)?;
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Rdjson)
|
||||
.preview(preview);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::JsonLines => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
@@ -290,7 +294,11 @@ impl Printer {
|
||||
GitlabEmitter::default().emit(writer, &diagnostics.inner, &context)?;
|
||||
}
|
||||
OutputFormat::Pylint => {
|
||||
PylintEmitter.emit(writer, &diagnostics.inner, &context)?;
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Pylint)
|
||||
.preview(preview);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Azure => {
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
|
||||
@@ -120,7 +120,7 @@ fn nonexistent_config_file() {
|
||||
#[test]
|
||||
fn config_override_rejected_if_invalid_toml() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config", "foo = bar", "."]), @r#"
|
||||
.args(["format", "--config", "foo = bar", "."]), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -137,12 +137,11 @@ fn config_override_rejected_if_invalid_toml() {
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
| ^^^
|
||||
string values must be quoted, expected literal string
|
||||
|
||||
For more information, try '--help'.
|
||||
"#);
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2246,8 +2246,7 @@ fn pyproject_toml_stdin_syntax_error() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
pyproject.toml:1:9: RUF200 Failed to parse pyproject.toml: invalid table header
|
||||
expected `.`, `]`
|
||||
pyproject.toml:1:9: RUF200 Failed to parse pyproject.toml: unclosed table, expected `]`
|
||||
|
|
||||
1 | [project
|
||||
| ^ RUF200
|
||||
|
||||
@@ -534,7 +534,7 @@ fn nonexistent_config_file() {
|
||||
fn config_override_rejected_if_invalid_toml() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "foo = bar", "."]), @r#"
|
||||
.args(["--config", "foo = bar", "."]), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -551,12 +551,11 @@ fn config_override_rejected_if_invalid_toml() {
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
| ^^^
|
||||
string values must be quoted, expected literal string
|
||||
|
||||
For more information, try '--help'.
|
||||
"#);
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -733,9 +732,8 @@ select = [E501]
|
||||
Cause: TOML parse error at line 3, column 11
|
||||
|
|
||||
3 | select = [E501]
|
||||
| ^
|
||||
invalid array
|
||||
expected `]`
|
||||
| ^^^^
|
||||
string values must be quoted, expected literal string
|
||||
");
|
||||
});
|
||||
|
||||
@@ -876,7 +874,7 @@ fn each_toml_option_requires_a_new_flag_1() {
|
||||
|
|
||||
1 | extend-select=['F841'], line-length=90
|
||||
| ^
|
||||
expected newline, `#`
|
||||
unexpected key or value, expected newline, `#`
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
@@ -907,7 +905,7 @@ fn each_toml_option_requires_a_new_flag_2() {
|
||||
|
|
||||
1 | extend-select=['F841'] line-length=90
|
||||
| ^
|
||||
expected newline, `#`
|
||||
unexpected key or value, expected newline, `#`
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
||||
@@ -18,6 +18,6 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
input.py:1: [F401] `os` imported but unused
|
||||
input.py:2: [F821] Undefined name `y`
|
||||
input.py:3: SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
input.py:3: [invalid-syntax] SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -75,8 +75,7 @@ exit_code: 1
|
||||
},
|
||||
{
|
||||
"code": {
|
||||
"url": null,
|
||||
"value": null
|
||||
"value": "invalid-syntax"
|
||||
},
|
||||
"location": {
|
||||
"path": "[TMP]/input.py",
|
||||
@@ -94,7 +93,7 @@ exit_code: 1
|
||||
"message": "SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"severity": "WARNING",
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff"
|
||||
|
||||
@@ -60,7 +60,7 @@ fn config_option_ignored_but_validated() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("version")
|
||||
.args(["--config", "foo = bar"]), @r#"
|
||||
.args(["--config", "foo = bar"]), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -77,12 +77,11 @@ fn config_option_ignored_but_validated() {
|
||||
TOML parse error at line 1, column 7
|
||||
|
|
||||
1 | foo = bar
|
||||
| ^
|
||||
invalid string
|
||||
expected `"`, `'`
|
||||
| ^^^
|
||||
string values must be quoted, expected literal string
|
||||
|
||||
For more information, try '--help'.
|
||||
"#
|
||||
"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use ruff_benchmark::criterion;
|
||||
use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject};
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::ops::Range;
|
||||
|
||||
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
||||
@@ -441,6 +442,37 @@ fn benchmark_complex_constrained_attributes_2(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_many_enum_members(criterion: &mut Criterion) {
|
||||
const NUM_ENUM_MEMBERS: usize = 512;
|
||||
|
||||
setup_rayon();
|
||||
|
||||
let mut code = String::new();
|
||||
writeln!(&mut code, "from enum import Enum").ok();
|
||||
|
||||
writeln!(&mut code, "class E(Enum):").ok();
|
||||
for i in 0..NUM_ENUM_MEMBERS {
|
||||
writeln!(&mut code, " m{i} = {i}").ok();
|
||||
}
|
||||
writeln!(&mut code).ok();
|
||||
|
||||
for i in 0..NUM_ENUM_MEMBERS {
|
||||
writeln!(&mut code, "print(E.m{i})").ok();
|
||||
}
|
||||
|
||||
criterion.bench_function("ty_micro[many_enum_members]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| setup_micro_case(&code),
|
||||
|case| {
|
||||
let Case { db, .. } = case;
|
||||
let result = db.check();
|
||||
assert_eq!(result.len(), 0);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
struct ProjectBenchmark<'a> {
|
||||
project: InstalledProject<'a>,
|
||||
fs: MemoryFileSystem,
|
||||
@@ -591,6 +623,7 @@ criterion_group!(
|
||||
benchmark_many_tuple_assignments,
|
||||
benchmark_complex_constrained_attributes_1,
|
||||
benchmark_complex_constrained_attributes_2,
|
||||
benchmark_many_enum_members,
|
||||
);
|
||||
criterion_group!(project, anyio, attrs, hydra, datetype);
|
||||
criterion_main!(check_file, micro, project);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Formatter, sync::Arc};
|
||||
use std::{fmt::Formatter, path::Path, sync::Arc};
|
||||
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
@@ -308,6 +308,10 @@ impl Diagnostic {
|
||||
|
||||
/// Set the fix for this diagnostic.
|
||||
pub fn set_fix(&mut self, fix: Fix) {
|
||||
debug_assert!(
|
||||
self.primary_span().is_some(),
|
||||
"Expected a source file for a diagnostic with a fix"
|
||||
);
|
||||
Arc::make_mut(&mut self.inner).fix = Some(fix);
|
||||
}
|
||||
|
||||
@@ -1008,6 +1012,18 @@ impl UnifiedFile {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the file's path relative to the current working directory.
|
||||
pub fn relative_path<'a>(&'a self, resolver: &'a dyn FileResolver) -> &'a Path {
|
||||
let cwd = resolver.current_directory();
|
||||
let path = Path::new(self.path(resolver));
|
||||
|
||||
if let Ok(path) = path.strip_prefix(cwd) {
|
||||
return path;
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
fn diagnostic_source(&self, resolver: &dyn FileResolver) -> DiagnosticSource {
|
||||
match self {
|
||||
UnifiedFile::Ty(file) => DiagnosticSource::Ty(resolver.input(*file)),
|
||||
@@ -1259,6 +1275,13 @@ pub enum DiagnosticFormat {
|
||||
/// format for an array of all diagnostics. See <https://jsonlines.org/> for more details.
|
||||
#[cfg(feature = "serde")]
|
||||
JsonLines,
|
||||
/// Print diagnostics in the JSON format expected by [reviewdog].
|
||||
///
|
||||
/// [reviewdog]: https://github.com/reviewdog/reviewdog
|
||||
#[cfg(feature = "serde")]
|
||||
Rdjson,
|
||||
/// Print diagnostics in the format emitted by Pylint.
|
||||
Pylint,
|
||||
}
|
||||
|
||||
/// A representation of the kinds of messages inside a diagnostic.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_annotate_snippets::{
|
||||
Annotation as AnnotateAnnotation, Level as AnnotateLevel, Message as AnnotateMessage,
|
||||
@@ -22,12 +23,16 @@ use super::{
|
||||
};
|
||||
|
||||
use azure::AzureRenderer;
|
||||
use pylint::PylintRenderer;
|
||||
|
||||
mod azure;
|
||||
#[cfg(feature = "serde")]
|
||||
mod json;
|
||||
#[cfg(feature = "serde")]
|
||||
mod json_lines;
|
||||
mod pylint;
|
||||
#[cfg(feature = "serde")]
|
||||
mod rdjson;
|
||||
|
||||
/// A type that implements `std::fmt::Display` for diagnostic rendering.
|
||||
///
|
||||
@@ -184,6 +189,13 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
|
||||
json_lines::JsonLinesRenderer::new(self.resolver, self.config)
|
||||
.render(f, self.diagnostics)?;
|
||||
}
|
||||
#[cfg(feature = "serde")]
|
||||
DiagnosticFormat::Rdjson => {
|
||||
rdjson::RdjsonRenderer::new(self.resolver).render(f, self.diagnostics)?;
|
||||
}
|
||||
DiagnosticFormat::Pylint => {
|
||||
PylintRenderer::new(self.resolver).render(f, self.diagnostics)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -705,6 +717,9 @@ pub trait FileResolver {
|
||||
|
||||
/// Returns whether the file given is a Jupyter notebook.
|
||||
fn is_notebook(&self, file: &UnifiedFile) -> bool;
|
||||
|
||||
/// Returns the current working directory.
|
||||
fn current_directory(&self) -> &Path;
|
||||
}
|
||||
|
||||
impl<T> FileResolver for T
|
||||
@@ -740,6 +755,10 @@ where
|
||||
UnifiedFile::Ruff(_) => unimplemented!("Expected an interned ty file"),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_directory(&self) -> &Path {
|
||||
self.system().current_directory().as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileResolver for &dyn Db {
|
||||
@@ -772,6 +791,10 @@ impl FileResolver for &dyn Db {
|
||||
UnifiedFile::Ruff(_) => unimplemented!("Expected an interned ty file"),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_directory(&self) -> &Path {
|
||||
self.system().current_directory().as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction over a unit of user input.
|
||||
|
||||
@@ -262,9 +262,6 @@ struct JsonEdit<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{
|
||||
@@ -297,13 +294,7 @@ mod tests {
|
||||
env.format(DiagnosticFormat::Json);
|
||||
env.preview(false);
|
||||
|
||||
let diag = env
|
||||
.err()
|
||||
.fix(Fix::safe_edit(Edit::insertion(
|
||||
"edit".to_string(),
|
||||
TextSize::from(0),
|
||||
)))
|
||||
.build();
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@@ -317,23 +308,7 @@ mod tests {
|
||||
"row": 1
|
||||
},
|
||||
"filename": "",
|
||||
"fix": {
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "edit",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 1
|
||||
},
|
||||
"location": {
|
||||
"column": 1,
|
||||
"row": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": null
|
||||
},
|
||||
"fix": null,
|
||||
"location": {
|
||||
"column": 1,
|
||||
"row": 1
|
||||
@@ -353,13 +328,7 @@ mod tests {
|
||||
env.format(DiagnosticFormat::Json);
|
||||
env.preview(true);
|
||||
|
||||
let diag = env
|
||||
.err()
|
||||
.fix(Fix::safe_edit(Edit::insertion(
|
||||
"edit".to_string(),
|
||||
TextSize::from(0),
|
||||
)))
|
||||
.build();
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@@ -370,17 +339,7 @@ mod tests {
|
||||
"code": null,
|
||||
"end_location": null,
|
||||
"filename": null,
|
||||
"fix": {
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "edit",
|
||||
"end_location": null,
|
||||
"location": null
|
||||
}
|
||||
],
|
||||
"message": null
|
||||
},
|
||||
"fix": null,
|
||||
"location": null,
|
||||
"message": "main diagnostic message",
|
||||
"noqa_row": null,
|
||||
|
||||
97
crates/ruff_db/src/diagnostic/render/pylint.rs
Normal file
97
crates/ruff_db/src/diagnostic/render/pylint.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use crate::diagnostic::{Diagnostic, SecondaryCode, render::FileResolver};
|
||||
|
||||
/// Generate violations in Pylint format.
|
||||
///
|
||||
/// The format is given by this string:
|
||||
///
|
||||
/// ```python
|
||||
/// "%(path)s:%(row)d: [%(code)s] %(text)s"
|
||||
/// ```
|
||||
///
|
||||
/// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter)
|
||||
pub(super) struct PylintRenderer<'a> {
|
||||
resolver: &'a dyn FileResolver,
|
||||
}
|
||||
|
||||
impl<'a> PylintRenderer<'a> {
|
||||
pub(super) fn new(resolver: &'a dyn FileResolver) -> Self {
|
||||
Self { resolver }
|
||||
}
|
||||
}
|
||||
|
||||
impl PylintRenderer<'_> {
|
||||
pub(super) fn render(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> std::fmt::Result {
|
||||
for diagnostic in diagnostics {
|
||||
let (filename, row) = diagnostic
|
||||
.primary_span_ref()
|
||||
.map(|span| {
|
||||
let file = span.file();
|
||||
|
||||
let row = span
|
||||
.range()
|
||||
.filter(|_| !self.resolver.is_notebook(file))
|
||||
.map(|range| {
|
||||
file.diagnostic_source(self.resolver)
|
||||
.as_source_code()
|
||||
.line_column(range.start())
|
||||
.line
|
||||
});
|
||||
|
||||
(file.relative_path(self.resolver).to_string_lossy(), row)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let code = diagnostic
|
||||
.secondary_code()
|
||||
.map_or_else(|| diagnostic.name(), SecondaryCode::as_str);
|
||||
|
||||
let row = row.unwrap_or_default();
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{path}:{row}: [{code}] {body}",
|
||||
path = filename,
|
||||
body = diagnostic.body()
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Pylint);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Pylint);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_file() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.format(DiagnosticFormat::Pylint);
|
||||
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@":1: [test-diagnostic] main diagnostic message",
|
||||
);
|
||||
}
|
||||
}
|
||||
235
crates/ruff_db/src/diagnostic/render/rdjson.rs
Normal file
235
crates/ruff_db/src/diagnostic/render/rdjson.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::Diagnostic;
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
pub struct RdjsonRenderer<'a> {
|
||||
resolver: &'a dyn FileResolver,
|
||||
}
|
||||
|
||||
impl<'a> RdjsonRenderer<'a> {
|
||||
pub(super) fn new(resolver: &'a dyn FileResolver) -> Self {
|
||||
Self { resolver }
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:#}",
|
||||
serde_json::json!(RdjsonDiagnostics::new(diagnostics, self.resolver))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct ExpandedDiagnostics<'a> {
|
||||
resolver: &'a dyn FileResolver,
|
||||
diagnostics: &'a [Diagnostic],
|
||||
}
|
||||
|
||||
impl Serialize for ExpandedDiagnostics<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
|
||||
|
||||
for diagnostic in self.diagnostics {
|
||||
let value = diagnostic_to_rdjson(diagnostic, self.resolver);
|
||||
s.serialize_element(&value)?;
|
||||
}
|
||||
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
fn diagnostic_to_rdjson<'a>(
|
||||
diagnostic: &'a Diagnostic,
|
||||
resolver: &'a dyn FileResolver,
|
||||
) -> RdjsonDiagnostic<'a> {
|
||||
let span = diagnostic.primary_span_ref();
|
||||
let source_file = span.map(|span| {
|
||||
let file = span.file();
|
||||
(file.path(resolver), file.diagnostic_source(resolver))
|
||||
});
|
||||
|
||||
let location = source_file.as_ref().map(|(path, source)| {
|
||||
let range = diagnostic.range().map(|range| {
|
||||
let source_code = source.as_source_code();
|
||||
let start = source_code.line_column(range.start());
|
||||
let end = source_code.line_column(range.end());
|
||||
RdjsonRange::new(start, end)
|
||||
});
|
||||
|
||||
RdjsonLocation { path, range }
|
||||
});
|
||||
|
||||
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
|
||||
|
||||
RdjsonDiagnostic {
|
||||
message: diagnostic.body(),
|
||||
location,
|
||||
code: RdjsonCode {
|
||||
value: diagnostic
|
||||
.secondary_code()
|
||||
.map_or_else(|| diagnostic.name(), |code| code.as_str()),
|
||||
url: diagnostic.to_ruff_url(),
|
||||
},
|
||||
suggestions: rdjson_suggestions(
|
||||
edits,
|
||||
source_file
|
||||
.as_ref()
|
||||
.map(|(_, source)| source.as_source_code()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn rdjson_suggestions<'a>(
|
||||
edits: &'a [Edit],
|
||||
source_code: Option<SourceCode>,
|
||||
) -> Vec<RdjsonSuggestion<'a>> {
|
||||
if edits.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let Some(source_code) = source_code else {
|
||||
debug_assert!(false, "Expected a source file for a diagnostic with a fix");
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
edits
|
||||
.iter()
|
||||
.map(|edit| {
|
||||
let start = source_code.line_column(edit.start());
|
||||
let end = source_code.line_column(edit.end());
|
||||
let range = RdjsonRange::new(start, end);
|
||||
|
||||
RdjsonSuggestion {
|
||||
range,
|
||||
text: edit.content().unwrap_or_default(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonDiagnostics<'a> {
|
||||
diagnostics: ExpandedDiagnostics<'a>,
|
||||
severity: &'static str,
|
||||
source: RdjsonSource,
|
||||
}
|
||||
|
||||
impl<'a> RdjsonDiagnostics<'a> {
|
||||
fn new(diagnostics: &'a [Diagnostic], resolver: &'a dyn FileResolver) -> Self {
|
||||
Self {
|
||||
source: RdjsonSource {
|
||||
name: "ruff",
|
||||
url: env!("CARGO_PKG_HOMEPAGE"),
|
||||
},
|
||||
severity: "WARNING",
|
||||
diagnostics: ExpandedDiagnostics {
|
||||
diagnostics,
|
||||
resolver,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonSource {
|
||||
name: &'static str,
|
||||
url: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonDiagnostic<'a> {
|
||||
code: RdjsonCode<'a>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
location: Option<RdjsonLocation<'a>>,
|
||||
message: &'a str,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
suggestions: Vec<RdjsonSuggestion<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonLocation<'a> {
|
||||
path: &'a str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
range: Option<RdjsonRange>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
struct RdjsonRange {
|
||||
end: LineColumn,
|
||||
start: LineColumn,
|
||||
}
|
||||
|
||||
impl RdjsonRange {
|
||||
fn new(start: LineColumn, end: LineColumn) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonCode<'a> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<String>,
|
||||
value: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonSuggestion<'a> {
|
||||
range: RdjsonRange,
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Rdjson);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Rdjson);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_file_stable() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.format(DiagnosticFormat::Rdjson);
|
||||
env.preview(false);
|
||||
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diag));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_file_preview() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.format(DiagnosticFormat::Rdjson);
|
||||
env.preview(true);
|
||||
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diag));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/pylint.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
source: crates/ruff_db/src/diagnostic/render/pylint.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
fib.py:1: [F401] `os` imported but unused
|
||||
fib.py:6: [F841] Local variable `x` is assigned to but never used
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/pylint.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
syntax_errors.py:1: [invalid-syntax] SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3: [invalid-syntax] SyntaxError: Expected ')', found newline
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
|
||||
expression: env.render(&diag)
|
||||
---
|
||||
{
|
||||
"diagnostics": [
|
||||
{
|
||||
"code": {
|
||||
"url": "https://docs.astral.sh/ruff/rules/test-diagnostic",
|
||||
"value": "test-diagnostic"
|
||||
},
|
||||
"message": "main diagnostic message"
|
||||
}
|
||||
],
|
||||
"severity": "WARNING",
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
|
||||
expression: env.render(&diag)
|
||||
---
|
||||
{
|
||||
"diagnostics": [
|
||||
{
|
||||
"code": {
|
||||
"url": "https://docs.astral.sh/ruff/rules/test-diagnostic",
|
||||
"value": "test-diagnostic"
|
||||
},
|
||||
"message": "main diagnostic message"
|
||||
}
|
||||
],
|
||||
"severity": "WARNING",
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/rdjson.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
{
|
||||
"diagnostics": [
|
||||
@@ -96,7 +95,7 @@ snapshot_kind: text
|
||||
"message": "Undefined name `a`"
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"severity": "WARNING",
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff"
|
||||
@@ -1,14 +1,12 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/rdjson.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
source: crates/ruff_db/src/diagnostic/render/rdjson.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
{
|
||||
"diagnostics": [
|
||||
{
|
||||
"code": {
|
||||
"url": null,
|
||||
"value": null
|
||||
"value": "invalid-syntax"
|
||||
},
|
||||
"location": {
|
||||
"path": "syntax_errors.py",
|
||||
@@ -27,8 +25,7 @@ snapshot_kind: text
|
||||
},
|
||||
{
|
||||
"code": {
|
||||
"url": null,
|
||||
"value": null
|
||||
"value": "invalid-syntax"
|
||||
},
|
||||
"location": {
|
||||
"path": "syntax_errors.py",
|
||||
@@ -46,7 +43,7 @@ snapshot_kind: text
|
||||
"message": "SyntaxError: Expected ')', found newline"
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"severity": "WARNING",
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff"
|
||||
@@ -28,6 +28,21 @@ pub use web_time::{Instant, SystemTime, SystemTimeError};
|
||||
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
static VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||
|
||||
/// Returns the version of the executing program if set.
|
||||
pub fn program_version() -> Option<&'static str> {
|
||||
VERSION.get().map(|version| version.as_str())
|
||||
}
|
||||
|
||||
/// Sets the version of the executing program.
|
||||
///
|
||||
/// ## Errors
|
||||
/// If the version has already been initialized (can only be set once).
|
||||
pub fn set_program_version(version: String) -> Result<(), String> {
|
||||
VERSION.set(version)
|
||||
}
|
||||
|
||||
/// Most basic database that gives access to files, the host system, source code, and parsed AST.
|
||||
#[salsa::db]
|
||||
pub trait Db: salsa::Database {
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
_(f"{'value'}")
|
||||
|
||||
# Don't trigger for t-strings
|
||||
_(t"{'value'}")
|
||||
|
||||
@@ -13,3 +13,7 @@ from logging import info
|
||||
|
||||
info(f"{name}")
|
||||
info(f"{__name__}")
|
||||
|
||||
# Don't trigger for t-strings
|
||||
info(t"{name}")
|
||||
info(t"{__name__}")
|
||||
|
||||
@@ -47,3 +47,7 @@ def test_error_match_is_empty():
|
||||
|
||||
with pytest.raises(ValueError, match=f""):
|
||||
raise ValueError("Can't divide 1 by 0")
|
||||
|
||||
def test_ok_t_string_match():
|
||||
with pytest.raises(ValueError, match=t""):
|
||||
raise ValueError("Can't divide 1 by 0")
|
||||
|
||||
@@ -23,3 +23,9 @@ def f():
|
||||
pytest.fail(msg=f"")
|
||||
pytest.fail(reason="")
|
||||
pytest.fail(reason=f"")
|
||||
|
||||
# Skip for t-strings
|
||||
def g():
|
||||
pytest.fail(t"")
|
||||
pytest.fail(msg=t"")
|
||||
pytest.fail(reason=t"")
|
||||
|
||||
@@ -32,3 +32,7 @@ def test_error_match_is_empty():
|
||||
|
||||
with pytest.warns(UserWarning, match=f""):
|
||||
pass
|
||||
|
||||
def test_ok_match_t_string():
|
||||
with pytest.warns(UserWarning, match=t""):
|
||||
pass
|
||||
|
||||
@@ -245,3 +245,14 @@ def f(bar: str):
|
||||
class C:
|
||||
def __init__(self, x) -> None:
|
||||
print(locals())
|
||||
|
||||
###
|
||||
# Should trigger for t-string here
|
||||
# even though the corresponding f-string
|
||||
# does not trigger (since it is common in stubs)
|
||||
###
|
||||
class C:
|
||||
def f(self, x, y):
|
||||
"""Docstring."""
|
||||
msg = t"{x}..."
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
26
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH210_2.py
vendored
Normal file
26
crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH210_2.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
from pathlib import (
|
||||
Path,
|
||||
PosixPath,
|
||||
PurePath,
|
||||
PurePosixPath,
|
||||
PureWindowsPath,
|
||||
WindowsPath,
|
||||
)
|
||||
import pathlib
|
||||
|
||||
|
||||
path = Path()
|
||||
posix_path: pathlib.PosixPath = PosixPath()
|
||||
pure_path: PurePath = PurePath()
|
||||
pure_posix_path = pathlib.PurePosixPath()
|
||||
pure_windows_path: PureWindowsPath = pathlib.PureWindowsPath()
|
||||
windows_path: pathlib.WindowsPath = pathlib.WindowsPath()
|
||||
|
||||
|
||||
### No Errors
|
||||
path.with_suffix(".")
|
||||
posix_path.with_suffix(".")
|
||||
pure_path.with_suffix(".")
|
||||
pure_posix_path.with_suffix(".")
|
||||
pure_windows_path.with_suffix(".")
|
||||
windows_path.with_suffix(".")
|
||||
@@ -48,6 +48,39 @@ from typing import override, overload
|
||||
def BAD_FUNC():
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def BAD_FUNC():
|
||||
pass
|
||||
|
||||
|
||||
import ast
|
||||
from ast import NodeTransformer
|
||||
|
||||
|
||||
class Visitor(ast.NodeVisitor):
|
||||
def visit_Constant(self, node):
|
||||
pass
|
||||
|
||||
def bad_Name(self):
|
||||
pass
|
||||
|
||||
class ExtendsVisitor(Visitor):
|
||||
def visit_Constant(self, node):
|
||||
pass
|
||||
|
||||
class Transformer(NodeTransformer):
|
||||
def visit_Constant(self, node):
|
||||
pass
|
||||
|
||||
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
|
||||
class MyRequestHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
pass
|
||||
|
||||
def dont_GET(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -189,3 +189,18 @@ f"{ham[lower + 1 :, "columnname"]}"
|
||||
#: Okay: https://github.com/astral-sh/ruff/issues/12023
|
||||
f"{x = :.2f}"
|
||||
f"{(x) = :.2f}"
|
||||
|
||||
# t-strings
|
||||
t"{ {'a': 1} }"
|
||||
t"{[ { {'a': 1} } ]}"
|
||||
t"normal { {t"{ { [1, 2] } }" } } normal"
|
||||
|
||||
t"{x = :.2f}"
|
||||
t"{(x) = :.2f}"
|
||||
|
||||
#: Okay
|
||||
t"{ham[lower +1 :, "columnname"]}"
|
||||
|
||||
#: E203:1:13
|
||||
t"{ham[lower + 1 :, "columnname"]}"
|
||||
|
||||
|
||||
@@ -142,3 +142,20 @@ class PEP696GoodWithEmptyBases[A: object="foo"[::-1], B: object =[[["foo", "bar"
|
||||
|
||||
class PEP696GoodWithNonEmptyBases[A: object="foo"[::-1], B: object =[[["foo", "bar"]]], C: object= bytes](object, something_dynamic[x::-1]):
|
||||
pass
|
||||
|
||||
# E231
|
||||
t"{(a,b)}"
|
||||
|
||||
# Okay because it's hard to differentiate between the usages of a colon in a t-string
|
||||
t"{a:=1}"
|
||||
t"{ {'a':1} }"
|
||||
t"{a:.3f}"
|
||||
t"{(a:=1)}"
|
||||
t"{(lambda x:x)}"
|
||||
t"normal{t"{a:.3f}"}normal"
|
||||
|
||||
#: Okay
|
||||
snapshot.file_uri[len(t's3://{self.s3_bucket_name}/'):]
|
||||
|
||||
#: E231
|
||||
{len(t's3://{self.s3_bucket_name}/'):1}
|
||||
|
||||
@@ -722,3 +722,10 @@ def inconsistent_indent_byte_size():
|
||||
|
||||
Returns:
|
||||
"""
|
||||
|
||||
|
||||
def line_continuation_chars():\
|
||||
|
||||
"""No fix should be offered for D201/D202 because of the line continuation chars."""\
|
||||
|
||||
...
|
||||
|
||||
@@ -22,3 +22,10 @@ assert b"hello" # [assert-on-string-literal]
|
||||
assert "", b"hi" # [assert-on-string-literal]
|
||||
assert "WhyNotHere?", "HereIsOk" # [assert-on-string-literal]
|
||||
assert 12, "ok here"
|
||||
|
||||
|
||||
# t-strings are always True even when "empty"
|
||||
# skip lint in this case
|
||||
assert t""
|
||||
assert t"hey"
|
||||
assert t"{a}"
|
||||
|
||||
Binary file not shown.
@@ -140,3 +140,15 @@ class Foo:
|
||||
def unused_message_2(self, x):
|
||||
msg = ""
|
||||
raise NotImplementedError(x)
|
||||
|
||||
class TPerson:
|
||||
def developer_greeting(self, name): # [no-self-use]
|
||||
print(t"Greetings {name}!")
|
||||
|
||||
def greeting_1(self):
|
||||
print(t"Hello from {self.name} !")
|
||||
|
||||
def tstring(self, x):
|
||||
msg = t"{x}"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
|
||||
@@ -33,3 +33,11 @@ class Foo:
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
|
||||
# This is a type error, out of scope for the rule
|
||||
class Foo:
|
||||
__slots__ = t"bar{baz}"
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
|
||||
|
||||
@@ -84,3 +84,7 @@ def _match_ignore(line):
|
||||
# Not a valid type annotation but this test shouldn't result in a panic.
|
||||
# Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
x: '"foo".encode("utf-8")'
|
||||
|
||||
# AttributeError for t-strings so skip lint
|
||||
(t"foo{bar}").encode("utf-8")
|
||||
(t"foo{bar}").encode(encoding="utf-8")
|
||||
|
||||
@@ -90,3 +90,7 @@ bool(True)and None
|
||||
int(1)and None
|
||||
float(1.)and None
|
||||
bool(True)and()
|
||||
|
||||
|
||||
# t-strings are not native literals
|
||||
str(t"hey")
|
||||
|
||||
16
crates/ruff_linter/resources/test/fixtures/ruff/RUF058_2.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/ruff/RUF058_2.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
from itertools import starmap
|
||||
import itertools
|
||||
|
||||
# Errors in Python 3.14+
|
||||
starmap(func, zip(a, b, c, strict=True))
|
||||
starmap(func, zip(a, b, c, strict=False))
|
||||
starmap(func, zip(a, b, c, strict=strict))
|
||||
|
||||
|
||||
# No errors
|
||||
|
||||
starmap(func)
|
||||
starmap(func, zip(a, b, c, **kwargs))
|
||||
starmap(func, zip(a, b, c), foo)
|
||||
starmap(func, zip(a, b, c, lorem=ipsum))
|
||||
starmap(func, zip(a, b, c), lorem=ipsum)
|
||||
@@ -15,8 +15,6 @@ pub use github::GithubEmitter;
|
||||
pub use gitlab::GitlabEmitter;
|
||||
pub use grouped::GroupedEmitter;
|
||||
pub use junit::JunitEmitter;
|
||||
pub use pylint::PylintEmitter;
|
||||
pub use rdjson::RdjsonEmitter;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, SourceFile};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
@@ -31,8 +29,6 @@ mod github;
|
||||
mod gitlab;
|
||||
mod grouped;
|
||||
mod junit;
|
||||
mod pylint;
|
||||
mod rdjson;
|
||||
mod sarif;
|
||||
mod text;
|
||||
|
||||
@@ -80,6 +76,13 @@ where
|
||||
body,
|
||||
);
|
||||
|
||||
let span = Span::from(file).with_range(range);
|
||||
let mut annotation = Annotation::primary(span);
|
||||
if let Some(suggestion) = suggestion {
|
||||
annotation = annotation.message(suggestion);
|
||||
}
|
||||
diagnostic.annotate(annotation);
|
||||
|
||||
if let Some(fix) = fix {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
@@ -92,13 +95,6 @@ where
|
||||
diagnostic.set_noqa_offset(noqa_offset);
|
||||
}
|
||||
|
||||
let span = Span::from(file).with_range(range);
|
||||
let mut annotation = Annotation::primary(span);
|
||||
if let Some(suggestion) = suggestion {
|
||||
annotation = annotation.message(suggestion);
|
||||
}
|
||||
diagnostic.annotate(annotation);
|
||||
|
||||
diagnostic.set_secondary_code(SecondaryCode::new(rule.noqa_code().to_string()));
|
||||
|
||||
diagnostic
|
||||
@@ -130,6 +126,10 @@ impl FileResolver for EmitterContext<'_> {
|
||||
UnifiedFile::Ruff(file) => self.notebook_indexes.get(file.name()).is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_directory(&self) -> &std::path::Path {
|
||||
crate::fs::get_cwd()
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageWithLocation<'a> {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
use std::io::Write;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::OneIndexed;
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
|
||||
/// Generate violations in Pylint format.
|
||||
/// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter)
|
||||
#[derive(Default)]
|
||||
pub struct PylintEmitter;
|
||||
|
||||
impl Emitter for PylintEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for diagnostic in diagnostics {
|
||||
let filename = diagnostic.expect_ruff_filename();
|
||||
let row = if context.is_notebook(&filename) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
OneIndexed::from_zero_indexed(0)
|
||||
} else {
|
||||
diagnostic.expect_ruff_start_location().line
|
||||
};
|
||||
|
||||
let body = if let Some(code) = diagnostic.secondary_code() {
|
||||
format!("[{code}] {body}", body = diagnostic.body())
|
||||
} else {
|
||||
diagnostic.body().to_string()
|
||||
};
|
||||
|
||||
writeln!(
|
||||
writer,
|
||||
"{path}:{row}: {body}",
|
||||
path = relativize_path(&filename),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::PylintEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let mut emitter = PylintEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = PylintEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
use std::io::Write;
|
||||
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::SourceCode;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Edit;
|
||||
use crate::message::{Emitter, EmitterContext, LineColumn};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RdjsonEmitter;
|
||||
|
||||
impl Emitter for RdjsonEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
_context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
serde_json::to_writer_pretty(
|
||||
writer,
|
||||
&json!({
|
||||
"source": {
|
||||
"name": "ruff",
|
||||
"url": "https://docs.astral.sh/ruff",
|
||||
},
|
||||
"severity": "warning",
|
||||
"diagnostics": &ExpandedMessages{ diagnostics }
|
||||
}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct ExpandedMessages<'a> {
|
||||
diagnostics: &'a [Diagnostic],
|
||||
}
|
||||
|
||||
impl Serialize for ExpandedMessages<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?;
|
||||
|
||||
for message in self.diagnostics {
|
||||
let value = message_to_rdjson_value(message);
|
||||
s.serialize_element(&value)?;
|
||||
}
|
||||
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
fn message_to_rdjson_value(message: &Diagnostic) -> Value {
|
||||
let source_file = message.expect_ruff_source_file();
|
||||
let source_code = source_file.to_source_code();
|
||||
|
||||
let start_location = source_code.line_column(message.expect_range().start());
|
||||
let end_location = source_code.line_column(message.expect_range().end());
|
||||
|
||||
if let Some(fix) = message.fix() {
|
||||
json!({
|
||||
"message": message.body(),
|
||||
"location": {
|
||||
"path": message.expect_ruff_filename(),
|
||||
"range": rdjson_range(start_location, end_location),
|
||||
},
|
||||
"code": {
|
||||
"value": message.secondary_code(),
|
||||
"url": message.to_ruff_url(),
|
||||
},
|
||||
"suggestions": rdjson_suggestions(fix.edits(), &source_code),
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"message": message.body(),
|
||||
"location": {
|
||||
"path": message.expect_ruff_filename(),
|
||||
"range": rdjson_range(start_location, end_location),
|
||||
},
|
||||
"code": {
|
||||
"value": message.secondary_code(),
|
||||
"url": message.to_ruff_url(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn rdjson_suggestions(edits: &[Edit], source_code: &SourceCode) -> Value {
|
||||
Value::Array(
|
||||
edits
|
||||
.iter()
|
||||
.map(|edit| {
|
||||
let location = source_code.line_column(edit.start());
|
||||
let end_location = source_code.line_column(edit.end());
|
||||
|
||||
json!({
|
||||
"range": rdjson_range(location, end_location),
|
||||
"text": edit.content().unwrap_or_default(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn rdjson_range(start: LineColumn, end: LineColumn) -> Value {
|
||||
json!({
|
||||
"start": start,
|
||||
"end": end,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::RdjsonEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let mut emitter = RdjsonEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = RdjsonEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/pylint.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
---
|
||||
syntax_errors.py:1: SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3: SyntaxError: Expected ')', found newline
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
B018.py:11:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_gettext/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
INT001.py:1:3: INT001 f-string is resolved before function call; consider `_("string %s") % arg`
|
||||
|
|
||||
1 | _(f"{'value'}")
|
||||
| ^^^^^^^^^^^^ INT001
|
||||
2 |
|
||||
3 | # Don't trigger for t-strings
|
||||
|
|
||||
|
||||
@@ -52,4 +52,6 @@ G004.py:15:6: G004 Logging statement uses f-string
|
||||
14 | info(f"{name}")
|
||||
15 | info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^ G004
|
||||
16 |
|
||||
17 | # Don't trigger for t-strings
|
||||
|
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PT016.py:19:5: PT016 No message passed to `pytest.fail()`
|
||||
|
|
||||
@@ -67,4 +66,6 @@ PT016.py:25:5: PT016 No message passed to `pytest.fail()`
|
||||
24 | pytest.fail(reason="")
|
||||
25 | pytest.fail(reason=f"")
|
||||
| ^^^^^^^^^^^ PT016
|
||||
26 |
|
||||
27 | # Skip for t-strings
|
||||
|
|
||||
|
||||
@@ -23,17 +23,28 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// var: str | "int"
|
||||
/// var: "Foo" | None
|
||||
///
|
||||
///
|
||||
/// class Foo: ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// var: str | int
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// var: Foo | None
|
||||
///
|
||||
///
|
||||
/// class Foo: ...
|
||||
/// ```
|
||||
///
|
||||
/// Or, extend the quotes to include the entire union:
|
||||
/// ```python
|
||||
/// var: "str | int"
|
||||
/// var: "Foo | None"
|
||||
///
|
||||
///
|
||||
/// class Foo: ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -66,3 +66,13 @@ ARG.py:216:24: ARG002 Unused method argument: `x`
|
||||
| ^ ARG002
|
||||
217 | print("Hello, world!")
|
||||
|
|
||||
|
||||
ARG.py:255:20: ARG002 Unused method argument: `y`
|
||||
|
|
||||
253 | ###
|
||||
254 | class C:
|
||||
255 | def f(self, x, y):
|
||||
| ^ ARG002
|
||||
256 | """Docstring."""
|
||||
257 | msg = t"{x}..."
|
||||
|
|
||||
|
||||
@@ -8,6 +8,7 @@ mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::assert_diagnostics;
|
||||
@@ -143,4 +144,22 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::InvalidPathlibWithSuffix, Path::new("PTH210_2.py"))]
|
||||
fn pathlib_with_suffix_py314(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"py314__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_use_pathlib").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
unresolved_target_version: PythonVersion::PY314.into(),
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, StringFlags};
|
||||
use ruff_python_ast::{self as ast, PythonVersion, StringFlags};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -9,12 +9,13 @@ use ruff_text_size::Ranged;
|
||||
/// ## What it does
|
||||
/// Checks for `pathlib.Path.with_suffix()` calls where
|
||||
/// the given suffix does not have a leading dot
|
||||
/// or the given suffix is a single dot `"."`.
|
||||
/// or the given suffix is a single dot `"."` and the
|
||||
/// Python version is less than 3.14.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `Path.with_suffix()` will raise an error at runtime
|
||||
/// if the given suffix is not prefixed with a dot
|
||||
/// or it is a single dot `"."`.
|
||||
/// or, in versions prior to Python 3.14, if it is a single dot `"."`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@@ -57,9 +58,6 @@ use ruff_text_size::Ranged;
|
||||
/// No fix is offered if the suffix `"."` is given, since the intent is unclear.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct InvalidPathlibWithSuffix {
|
||||
// TODO: Since "." is a correct suffix in Python 3.14,
|
||||
// we will need to update this rule and documentation
|
||||
// once Ruff supports Python 3.14.
|
||||
single_dot: bool,
|
||||
}
|
||||
|
||||
@@ -116,6 +114,13 @@ pub(crate) fn invalid_pathlib_with_suffix(checker: &Checker, call: &ast::ExprCal
|
||||
};
|
||||
|
||||
let single_dot = string_value == ".";
|
||||
|
||||
// As of Python 3.14, a single dot is considered a valid suffix.
|
||||
// https://docs.python.org/3.14/library/pathlib.html#pathlib.PurePath.with_suffix
|
||||
if single_dot && checker.target_version() >= PythonVersion::PY314 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(InvalidPathlibWithSuffix { single_dot }, call.range);
|
||||
if !single_dot {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use ruff_python_ast::{Decorator, Stmt};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::{Decorator, Stmt, identifier::Identifier};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::analyze::{class::any_base_class, visibility};
|
||||
use ruff_python_stdlib::str;
|
||||
|
||||
use crate::Violation;
|
||||
@@ -84,6 +82,41 @@ pub(crate) fn invalid_function_name(
|
||||
return;
|
||||
}
|
||||
|
||||
let parent_class = semantic
|
||||
.current_statement_parent()
|
||||
.and_then(|parent| parent.as_class_def_stmt());
|
||||
|
||||
// Ignore the visit_* methods of the ast.NodeVisitor and ast.NodeTransformer classes.
|
||||
if name.starts_with("visit_")
|
||||
&& parent_class.is_some_and(|class| {
|
||||
any_base_class(class, semantic, &mut |superclass| {
|
||||
let qualified = semantic.resolve_qualified_name(superclass);
|
||||
qualified.is_some_and(|name| {
|
||||
matches!(name.segments(), ["ast", "NodeVisitor" | "NodeTransformer"])
|
||||
})
|
||||
})
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore the do_* methods of the http.server.BaseHTTPRequestHandler class
|
||||
if name.starts_with("do_")
|
||||
&& parent_class.is_some_and(|class| {
|
||||
any_base_class(class, semantic, &mut |superclass| {
|
||||
let qualified = semantic.resolve_qualified_name(superclass);
|
||||
qualified.is_some_and(|name| {
|
||||
matches!(
|
||||
name.segments(),
|
||||
["http", "server", "BaseHTTPRequestHandler"]
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(
|
||||
InvalidFunctionName {
|
||||
name: name.to_string(),
|
||||
|
||||
@@ -37,3 +37,21 @@ N802.py:40:9: N802 Function name `testTest` should be lowercase
|
||||
| ^^^^^^^^ N802
|
||||
41 | assert True
|
||||
|
|
||||
|
||||
N802.py:65:9: N802 Function name `bad_Name` should be lowercase
|
||||
|
|
||||
63 | pass
|
||||
64 |
|
||||
65 | def bad_Name(self):
|
||||
| ^^^^^^^^ N802
|
||||
66 | pass
|
||||
|
|
||||
|
||||
N802.py:84:9: N802 Function name `dont_GET` should be lowercase
|
||||
|
|
||||
82 | pass
|
||||
83 |
|
||||
84 | def dont_GET(self):
|
||||
| ^^^^^^^^ N802
|
||||
85 | pass
|
||||
|
|
||||
|
||||
@@ -126,7 +126,7 @@ impl AlwaysFixableViolation for WhitespaceBeforePunctuation {
|
||||
|
||||
/// E201, E202, E203
|
||||
pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
let mut fstrings = 0u32;
|
||||
let mut interpolated_strings = 0u32;
|
||||
let mut brackets = vec![];
|
||||
let mut prev_token = None;
|
||||
let mut iter = line.tokens().iter().peekable();
|
||||
@@ -134,8 +134,10 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
while let Some(token) = iter.next() {
|
||||
let kind = token.kind();
|
||||
match kind {
|
||||
TokenKind::FStringStart => fstrings += 1,
|
||||
TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1),
|
||||
TokenKind::FStringStart | TokenKind::TStringStart => interpolated_strings += 1,
|
||||
TokenKind::FStringEnd | TokenKind::TStringEnd => {
|
||||
interpolated_strings = interpolated_strings.saturating_sub(1);
|
||||
}
|
||||
TokenKind::Lsqb => {
|
||||
brackets.push(kind);
|
||||
}
|
||||
@@ -161,7 +163,9 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
// Here, `{{` / `}} would be interpreted as a single raw `{` / `}`
|
||||
// character.
|
||||
match symbol {
|
||||
BracketOrPunctuation::OpenBracket(symbol) if symbol != '{' || fstrings == 0 => {
|
||||
BracketOrPunctuation::OpenBracket(symbol)
|
||||
if symbol != '{' || interpolated_strings == 0 =>
|
||||
{
|
||||
let (trailing, trailing_len) = line.trailing_whitespace(token);
|
||||
if !matches!(trailing, Whitespace::None) {
|
||||
if let Some(mut diagnostic) = context.report_diagnostic_if_enabled(
|
||||
@@ -173,7 +177,9 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
BracketOrPunctuation::CloseBracket(symbol) if symbol != '}' || fstrings == 0 => {
|
||||
BracketOrPunctuation::CloseBracket(symbol)
|
||||
if symbol != '}' || interpolated_strings == 0 =>
|
||||
{
|
||||
if !matches!(prev_token, Some(TokenKind::Comma)) {
|
||||
if let (Whitespace::Single | Whitespace::Many | Whitespace::Tab, offset) =
|
||||
line.leading_whitespace(token)
|
||||
@@ -286,7 +292,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if fstrings > 0
|
||||
if interpolated_strings > 0
|
||||
&& symbol == ':'
|
||||
&& matches!(prev_token, Some(TokenKind::Equal))
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ impl AlwaysFixableViolation for MissingWhitespace {
|
||||
|
||||
/// E231
|
||||
pub(crate) fn missing_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
let mut fstrings = 0u32;
|
||||
let mut interpolated_strings = 0u32;
|
||||
let mut definition_state = DefinitionState::from_tokens(line.tokens());
|
||||
let mut brackets = Vec::new();
|
||||
let mut iter = line.tokens().iter().peekable();
|
||||
@@ -50,21 +50,23 @@ pub(crate) fn missing_whitespace(line: &LogicalLine, context: &LintContext) {
|
||||
let kind = token.kind();
|
||||
definition_state.visit_token_kind(kind);
|
||||
match kind {
|
||||
TokenKind::FStringStart => fstrings += 1,
|
||||
TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1),
|
||||
TokenKind::Lsqb if fstrings == 0 => {
|
||||
TokenKind::FStringStart | TokenKind::TStringStart => interpolated_strings += 1,
|
||||
TokenKind::FStringEnd | TokenKind::TStringEnd => {
|
||||
interpolated_strings = interpolated_strings.saturating_sub(1);
|
||||
}
|
||||
TokenKind::Lsqb if interpolated_strings == 0 => {
|
||||
brackets.push(kind);
|
||||
}
|
||||
TokenKind::Rsqb if fstrings == 0 => {
|
||||
TokenKind::Rsqb if interpolated_strings == 0 => {
|
||||
brackets.pop();
|
||||
}
|
||||
TokenKind::Lbrace if fstrings == 0 => {
|
||||
TokenKind::Lbrace if interpolated_strings == 0 => {
|
||||
brackets.push(kind);
|
||||
}
|
||||
TokenKind::Rbrace if fstrings == 0 => {
|
||||
TokenKind::Rbrace if interpolated_strings == 0 => {
|
||||
brackets.pop();
|
||||
}
|
||||
TokenKind::Colon if fstrings > 0 => {
|
||||
TokenKind::Colon if interpolated_strings > 0 => {
|
||||
// Colon in f-string, no space required. This will yield false
|
||||
// negatives for cases like the following as it's hard to
|
||||
// differentiate between the usage of a colon in a f-string.
|
||||
|
||||
@@ -183,3 +183,23 @@ E20.py:145:5: E201 [*] Whitespace after '['
|
||||
146 146 |
|
||||
147 147 | #: Okay
|
||||
148 148 | ham[lower + offset :: upper + offset]
|
||||
|
||||
E20.py:195:5: E201 [*] Whitespace after '['
|
||||
|
|
||||
193 | # t-strings
|
||||
194 | t"{ {'a': 1} }"
|
||||
195 | t"{[ { {'a': 1} } ]}"
|
||||
| ^ E201
|
||||
196 | t"normal { {t"{ { [1, 2] } }" } } normal"
|
||||
|
|
||||
= help: Remove whitespace before '['
|
||||
|
||||
ℹ Safe fix
|
||||
192 192 |
|
||||
193 193 | # t-strings
|
||||
194 194 | t"{ {'a': 1} }"
|
||||
195 |-t"{[ { {'a': 1} } ]}"
|
||||
195 |+t"{[{ {'a': 1} } ]}"
|
||||
196 196 | t"normal { {t"{ { [1, 2] } }" } } normal"
|
||||
197 197 |
|
||||
198 198 | t"{x = :.2f}"
|
||||
|
||||
@@ -165,3 +165,23 @@ E20.py:172:12: E202 [*] Whitespace before ']'
|
||||
173 173 |
|
||||
174 174 | #: E203:1:10
|
||||
175 175 | ham[upper :]
|
||||
|
||||
E20.py:195:18: E202 [*] Whitespace before ']'
|
||||
|
|
||||
193 | # t-strings
|
||||
194 | t"{ {'a': 1} }"
|
||||
195 | t"{[ { {'a': 1} } ]}"
|
||||
| ^ E202
|
||||
196 | t"normal { {t"{ { [1, 2] } }" } } normal"
|
||||
|
|
||||
= help: Remove whitespace before ']'
|
||||
|
||||
ℹ Safe fix
|
||||
192 192 |
|
||||
193 193 | # t-strings
|
||||
194 194 | t"{ {'a': 1} }"
|
||||
195 |-t"{[ { {'a': 1} } ]}"
|
||||
195 |+t"{[ { {'a': 1} }]}"
|
||||
196 196 | t"normal { {t"{ { [1, 2] } }" } } normal"
|
||||
197 197 |
|
||||
198 198 | t"{x = :.2f}"
|
||||
|
||||
@@ -345,3 +345,19 @@ E20.py:187:17: E203 [*] Whitespace before ':'
|
||||
188 188 |
|
||||
189 189 | #: Okay: https://github.com/astral-sh/ruff/issues/12023
|
||||
190 190 | f"{x = :.2f}"
|
||||
|
||||
E20.py:205:17: E203 [*] Whitespace before ':'
|
||||
|
|
||||
204 | #: E203:1:13
|
||||
205 | t"{ham[lower + 1 :, "columnname"]}"
|
||||
| ^^ E203
|
||||
|
|
||||
= help: Remove whitespace before ':'
|
||||
|
||||
ℹ Safe fix
|
||||
202 202 | t"{ham[lower +1 :, "columnname"]}"
|
||||
203 203 |
|
||||
204 204 | #: E203:1:13
|
||||
205 |-t"{ham[lower + 1 :, "columnname"]}"
|
||||
205 |+t"{ham[lower + 1:, "columnname"]}"
|
||||
206 206 |
|
||||
|
||||
@@ -905,3 +905,38 @@ E23.py:126:99: E231 [*] Missing whitespace after ':'
|
||||
127 127 | pass
|
||||
128 128 |
|
||||
129 129 | # Should be no E231 errors on any of these:
|
||||
|
||||
E23.py:147:6: E231 [*] Missing whitespace after ','
|
||||
|
|
||||
146 | # E231
|
||||
147 | t"{(a,b)}"
|
||||
| ^ E231
|
||||
148 |
|
||||
149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string
|
||||
|
|
||||
= help: Add missing whitespace
|
||||
|
||||
ℹ Safe fix
|
||||
144 144 | pass
|
||||
145 145 |
|
||||
146 146 | # E231
|
||||
147 |-t"{(a,b)}"
|
||||
147 |+t"{(a, b)}"
|
||||
148 148 |
|
||||
149 149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string
|
||||
150 150 | t"{a:=1}"
|
||||
|
||||
E23.py:161:37: E231 [*] Missing whitespace after ':'
|
||||
|
|
||||
160 | #: E231
|
||||
161 | {len(t's3://{self.s3_bucket_name}/'):1}
|
||||
| ^ E231
|
||||
|
|
||||
= help: Add missing whitespace
|
||||
|
||||
ℹ Safe fix
|
||||
158 158 | snapshot.file_uri[len(t's3://{self.s3_bucket_name}/'):]
|
||||
159 159 |
|
||||
160 160 | #: E231
|
||||
161 |-{len(t's3://{self.s3_bucket_name}/'):1}
|
||||
161 |+{len(t's3://{self.s3_bucket_name}/'): 1}
|
||||
|
||||
@@ -10,7 +10,7 @@ use ruff_text_size::TextRange;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::docstrings::Docstring;
|
||||
use crate::registry::Rule;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for docstrings on functions that are separated by one or more blank
|
||||
@@ -42,15 +42,17 @@ pub(crate) struct BlankLineBeforeFunction {
|
||||
num_lines: usize,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for BlankLineBeforeFunction {
|
||||
impl Violation for BlankLineBeforeFunction {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BlankLineBeforeFunction { num_lines } = self;
|
||||
format!("No blank lines allowed before function docstring (found {num_lines})")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove blank line(s) before function docstring".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove blank line(s) before function docstring".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,15 +88,17 @@ pub(crate) struct BlankLineAfterFunction {
|
||||
num_lines: usize,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for BlankLineAfterFunction {
|
||||
impl Violation for BlankLineAfterFunction {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BlankLineAfterFunction { num_lines } = self;
|
||||
format!("No blank lines allowed after function docstring (found {num_lines})")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove blank line(s) after function docstring".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove blank line(s) after function docstring".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +119,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
|
||||
let mut lines = UniversalNewlineIterator::with_offset(before, function.start()).rev();
|
||||
let mut blank_lines_before = 0usize;
|
||||
let mut blank_lines_start = lines.next().map(|l| l.end()).unwrap_or_default();
|
||||
let mut start_is_line_continuation = false;
|
||||
|
||||
for line in lines {
|
||||
if line.trim().is_empty() {
|
||||
blank_lines_before += 1;
|
||||
blank_lines_start = line.start();
|
||||
} else {
|
||||
start_is_line_continuation = line.ends_with('\\');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -132,11 +138,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
// Delete the blank line before the docstring.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
|
||||
blank_lines_start,
|
||||
docstring.line_start(),
|
||||
)));
|
||||
// Do not offer fix if a \ would cause it to be a syntax error
|
||||
if !start_is_line_continuation {
|
||||
// Delete the blank line before the docstring.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
|
||||
blank_lines_start,
|
||||
docstring.line_start(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +165,9 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
|
||||
// Count the number of blank lines after the docstring.
|
||||
let mut blank_lines_after = 0usize;
|
||||
let mut lines = UniversalNewlineIterator::with_offset(after, docstring.end()).peekable();
|
||||
let first_line_end = lines.next().map(|l| l.end()).unwrap_or_default();
|
||||
let first_line = lines.next();
|
||||
let first_line_line_continuation = first_line.as_ref().is_some_and(|l| l.ends_with('\\'));
|
||||
let first_line_end = first_line.map(|l| l.end()).unwrap_or_default();
|
||||
let mut blank_lines_end = first_line_end;
|
||||
|
||||
while let Some(line) = lines.peek() {
|
||||
@@ -185,11 +196,14 @@ pub(crate) fn blank_before_after_function(checker: &Checker, docstring: &Docstri
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
// Delete the blank line after the docstring.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
|
||||
first_line_end,
|
||||
blank_lines_end,
|
||||
)));
|
||||
// Do not offer fix if a \ would cause it to be a syntax error
|
||||
if !first_line_line_continuation {
|
||||
// Delete the blank line after the docstring.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(
|
||||
first_line_end,
|
||||
blank_lines_end,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,3 +82,14 @@ D.py:568:5: D201 [*] No blank lines allowed before function docstring (found 1)
|
||||
568 567 | """Trailing and leading space.
|
||||
569 568 |
|
||||
570 569 | More content.
|
||||
|
||||
D.py:729:5: D201 No blank lines allowed before function docstring (found 1)
|
||||
|
|
||||
727 | def line_continuation_chars():\
|
||||
728 |
|
||||
729 | """No fix should be offered for D201/D202 because of the line continuation chars."""\
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D201
|
||||
730 |
|
||||
731 | ...
|
||||
|
|
||||
= help: Remove blank line(s) before function docstring
|
||||
|
||||
@@ -85,4 +85,15 @@ D.py:568:5: D202 [*] No blank lines allowed after function docstring (found 1)
|
||||
572 |-
|
||||
573 572 | pass
|
||||
574 573 |
|
||||
575 574 |
|
||||
575 574 |
|
||||
|
||||
D.py:729:5: D202 No blank lines allowed after function docstring (found 1)
|
||||
|
|
||||
727 | def line_continuation_chars():\
|
||||
728 |
|
||||
729 | """No fix should be offered for D201/D202 because of the line continuation chars."""\
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D202
|
||||
730 |
|
||||
731 | ...
|
||||
|
|
||||
= help: Remove blank line(s) after function docstring
|
||||
|
||||
@@ -428,3 +428,5 @@ D.py:723:1: D208 [*] Docstring is over-indented
|
||||
723 |- Returns:
|
||||
723 |+ Returns:
|
||||
724 724 | """
|
||||
725 725 |
|
||||
726 726 |
|
||||
|
||||
@@ -24,24 +24,30 @@ const BIDI_UNICODE: [char; 10] = [
|
||||
];
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for bidirectional unicode characters.
|
||||
/// Checks for bidirectional formatting characters.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The interaction between bidirectional unicode characters and the
|
||||
/// The interaction between bidirectional formatting characters and the
|
||||
/// surrounding code can be surprising to those that are unfamiliar
|
||||
/// with right-to-left writing systems.
|
||||
///
|
||||
/// In some cases, bidirectional unicode characters can also be used to
|
||||
/// In some cases, bidirectional formatting characters can also be used to
|
||||
/// obfuscate code and introduce or mask security vulnerabilities.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// s = "א" * 100 # "א" is assigned
|
||||
/// print(s) # prints a 100-character string
|
||||
/// example = "x" * 100 # "x" is assigned
|
||||
/// ```
|
||||
///
|
||||
/// The example uses two `RIGHT-TO-LEFT MARK`s to make the `100 * ` appear inside the comment.
|
||||
/// Without the `RIGHT-TO-LEFT MARK`s, the code looks like this:
|
||||
///
|
||||
/// ```py
|
||||
/// example = "x" * 100 # "x" is assigned
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 672: Bidirectional Text](https://peps.python.org/pep-0672/#bidirectional-text)
|
||||
/// - [PEP 672: Bidirectional Marks, Embeddings, Overrides and Isolates](https://peps.python.org/pep-0672/#bidirectional-marks-embeddings-overrides-and-isolates)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BidirectionalUnicode;
|
||||
|
||||
|
||||
@@ -185,7 +185,9 @@ pub(crate) fn invalid_string_characters(context: &LintContext, token: &Token, lo
|
||||
let text = match token.kind() {
|
||||
// We can't use the `value` field since it's decoded and e.g. for f-strings removed a curly
|
||||
// brace that escaped another curly brace, which would gives us wrong column information.
|
||||
TokenKind::String | TokenKind::FStringMiddle => locator.slice(token),
|
||||
TokenKind::String | TokenKind::FStringMiddle | TokenKind::TStringMiddle => {
|
||||
locator.slice(token)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -75,3 +75,21 @@ no_self_use.py:140:9: PLR6301 Method `unused_message_2` could be a function, cla
|
||||
141 | msg = ""
|
||||
142 | raise NotImplementedError(x)
|
||||
|
|
||||
|
||||
no_self_use.py:145:9: PLR6301 Method `developer_greeting` could be a function, class method, or static method
|
||||
|
|
||||
144 | class TPerson:
|
||||
145 | def developer_greeting(self, name): # [no-self-use]
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR6301
|
||||
146 | print(t"Greetings {name}!")
|
||||
|
|
||||
|
||||
no_self_use.py:151:9: PLR6301 Method `tstring` could be a function, class method, or static method
|
||||
|
|
||||
149 | print(t"Hello from {self.name} !")
|
||||
150 |
|
||||
151 | def tstring(self, x):
|
||||
| ^^^^^^^ PLR6301
|
||||
152 | msg = t"{x}"
|
||||
153 | raise NotImplementedError(msg)
|
||||
|
|
||||
|
||||
@@ -40,12 +40,18 @@ use super::{
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Annotated, TypeAlias, TypeAliasType
|
||||
/// from annotated_types import Gt
|
||||
///
|
||||
/// ListOfInt: TypeAlias = list[int]
|
||||
/// PositiveInt = TypeAliasType("PositiveInt", Annotated[int, Gt(0)])
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Annotated
|
||||
/// from annotated_types import Gt
|
||||
///
|
||||
/// type ListOfInt = list[int]
|
||||
/// type PositiveInt = Annotated[int, Gt(0)]
|
||||
/// ```
|
||||
|
||||
@@ -576,6 +576,8 @@ UP012.py:86:5: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
85 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
86 | x: '"foo".encode("utf-8")'
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ UP012
|
||||
87 |
|
||||
88 | # AttributeError for t-strings so skip lint
|
||||
|
|
||||
= help: Rewrite as bytes literal
|
||||
|
||||
@@ -585,3 +587,6 @@ UP012.py:86:5: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
85 85 | # Refer: https://github.com/astral-sh/ruff/issues/11736
|
||||
86 |-x: '"foo".encode("utf-8")'
|
||||
86 |+x: 'b"foo"'
|
||||
87 87 |
|
||||
88 88 | # AttributeError for t-strings so skip lint
|
||||
89 89 | (t"foo{bar}").encode("utf-8")
|
||||
|
||||
@@ -660,6 +660,7 @@ UP018.py:90:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
||||
90 |+1 and None
|
||||
91 91 | float(1.)and None
|
||||
92 92 | bool(True)and()
|
||||
93 93 |
|
||||
|
||||
UP018.py:91:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
||||
|
|
||||
@@ -678,6 +679,8 @@ UP018.py:91:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
||||
91 |-float(1.)and None
|
||||
91 |+1. and None
|
||||
92 92 | bool(True)and()
|
||||
93 93 |
|
||||
94 94 |
|
||||
|
||||
UP018.py:92:1: UP018 [*] Unnecessary `bool` call (rewrite as a literal)
|
||||
|
|
||||
@@ -694,3 +697,6 @@ UP018.py:92:1: UP018 [*] Unnecessary `bool` call (rewrite as a literal)
|
||||
91 91 | float(1.)and None
|
||||
92 |-bool(True)and()
|
||||
92 |+True and()
|
||||
93 93 |
|
||||
94 94 |
|
||||
95 95 | # t-strings are not native literals
|
||||
|
||||
@@ -19,22 +19,26 @@ use crate::rules::refurb::helpers::parenthesize_loop_iter_if_necessary;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// with Path("file").open("w") as f:
|
||||
/// for line in lines:
|
||||
/// f.write(line)
|
||||
///
|
||||
/// with Path("file").open("wb") as f:
|
||||
/// for line in lines:
|
||||
/// f.write(line.encode())
|
||||
/// with Path("file").open("wb") as f_b:
|
||||
/// for line_b in lines_b:
|
||||
/// f_b.write(line_b.encode())
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// with Path("file").open("w") as f:
|
||||
/// f.writelines(lines)
|
||||
///
|
||||
/// with Path("file").open("wb") as f:
|
||||
/// f.writelines(line.encode() for line in lines)
|
||||
/// with Path("file").open("wb") as f_b:
|
||||
/// f_b.writelines(line_b.encode() for line_b in lines_b)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
|
||||
@@ -14,11 +14,15 @@ use crate::{checkers::ast::Checker, importer::ImportRequest};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// cwd = Path().resolve()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// cwd = Path.cwd()
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -581,4 +581,22 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::StarmapZip, Path::new("RUF058_2.py"))]
|
||||
fn map_strict_py314(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"py314__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
unresolved_target_version: PythonVersion::PY314.into(),
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::{Expr, ExprCall, parenthesize::parenthesized_range};
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -67,9 +68,24 @@ pub(crate) fn starmap_zip(checker: &Checker, call: &ExprCall) {
|
||||
return;
|
||||
};
|
||||
|
||||
if !iterable_call.arguments.keywords.is_empty() {
|
||||
// TODO: Pass `strict=` to `map` too when 3.14 is supported.
|
||||
return;
|
||||
let keywords = &iterable_call.arguments.keywords;
|
||||
|
||||
match checker.target_version().cmp(&PythonVersion::PY314) {
|
||||
// Keyword arguments not supported for `map` before Python 3.14
|
||||
std::cmp::Ordering::Less => {
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Only supported keyword argument is `strict` starting in 3.14
|
||||
std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {
|
||||
if keywords.len() > 1 {
|
||||
return;
|
||||
}
|
||||
if keywords.len() == 1 && iterable_call.arguments.find_keyword("strict").is_none() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let positionals = &iterable_call.arguments.args;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF058_2.py:5:1: RUF058 [*] `itertools.starmap` called on `zip` iterable
|
||||
|
|
||||
4 | # Errors in Python 3.14+
|
||||
5 | starmap(func, zip(a, b, c, strict=True))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF058
|
||||
6 | starmap(func, zip(a, b, c, strict=False))
|
||||
7 | starmap(func, zip(a, b, c, strict=strict))
|
||||
|
|
||||
= help: Use `map` instead
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | import itertools
|
||||
3 3 |
|
||||
4 4 | # Errors in Python 3.14+
|
||||
5 |-starmap(func, zip(a, b, c, strict=True))
|
||||
5 |+map(func, a, b, c, strict=True)
|
||||
6 6 | starmap(func, zip(a, b, c, strict=False))
|
||||
7 7 | starmap(func, zip(a, b, c, strict=strict))
|
||||
8 8 |
|
||||
|
||||
RUF058_2.py:6:1: RUF058 [*] `itertools.starmap` called on `zip` iterable
|
||||
|
|
||||
4 | # Errors in Python 3.14+
|
||||
5 | starmap(func, zip(a, b, c, strict=True))
|
||||
6 | starmap(func, zip(a, b, c, strict=False))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF058
|
||||
7 | starmap(func, zip(a, b, c, strict=strict))
|
||||
|
|
||||
= help: Use `map` instead
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 |
|
||||
4 4 | # Errors in Python 3.14+
|
||||
5 5 | starmap(func, zip(a, b, c, strict=True))
|
||||
6 |-starmap(func, zip(a, b, c, strict=False))
|
||||
6 |+map(func, a, b, c, strict=False)
|
||||
7 7 | starmap(func, zip(a, b, c, strict=strict))
|
||||
8 8 |
|
||||
9 9 |
|
||||
|
||||
RUF058_2.py:7:1: RUF058 [*] `itertools.starmap` called on `zip` iterable
|
||||
|
|
||||
5 | starmap(func, zip(a, b, c, strict=True))
|
||||
6 | starmap(func, zip(a, b, c, strict=False))
|
||||
7 | starmap(func, zip(a, b, c, strict=strict))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF058
|
||||
|
|
||||
= help: Use `map` instead
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | # Errors in Python 3.14+
|
||||
5 5 | starmap(func, zip(a, b, c, strict=True))
|
||||
6 6 | starmap(func, zip(a, b, c, strict=False))
|
||||
7 |-starmap(func, zip(a, b, c, strict=strict))
|
||||
7 |+map(func, a, b, c, strict=strict)
|
||||
8 8 |
|
||||
9 9 |
|
||||
10 10 | # No errors
|
||||
@@ -49,6 +49,16 @@ impl NodeIndex {
|
||||
pub fn as_usize(self) -> usize {
|
||||
self.0 as _
|
||||
}
|
||||
|
||||
pub fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for NodeIndex {
|
||||
fn from(value: u32) -> Self {
|
||||
NodeIndex(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for AtomicNodeIndex {
|
||||
|
||||
@@ -624,6 +624,12 @@ impl OneIndexed {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OneIndexed {
|
||||
fn default() -> Self {
|
||||
Self::MIN
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OneIndexed {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
std::fmt::Debug::fmt(&self.0.get(), f)
|
||||
|
||||
@@ -7,13 +7,25 @@ use std::{
|
||||
fn main() {
|
||||
// The workspace root directory is not available without walking up the tree
|
||||
// https://github.com/rust-lang/cargo/issues/3946
|
||||
let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||
.join("..")
|
||||
let ruff_workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||
.join("..")
|
||||
.join("..");
|
||||
let ty_workspace_root = ruff_workspace_root.join("..");
|
||||
|
||||
version_info(&workspace_root);
|
||||
commit_info(&workspace_root);
|
||||
version_info(&ty_workspace_root);
|
||||
|
||||
// If not in a git repository, do not attempt to retrieve commit information
|
||||
let git_dir = ty_workspace_root.join(".git");
|
||||
if git_dir.exists() {
|
||||
commit_info(&git_dir, &ty_workspace_root, false);
|
||||
} else {
|
||||
// Try if we're inside the ruff repository and, if so, use that commit hash.
|
||||
let git_dir = ruff_workspace_root.join(".git");
|
||||
|
||||
if git_dir.exists() {
|
||||
commit_info(&git_dir, &ruff_workspace_root, true);
|
||||
}
|
||||
}
|
||||
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
println!("cargo::rustc-env=RUST_HOST_TARGET={target}");
|
||||
@@ -45,14 +57,8 @@ fn version_info(workspace_root: &Path) {
|
||||
}
|
||||
|
||||
/// Retrieve commit information from the Git repository.
|
||||
fn commit_info(workspace_root: &Path) {
|
||||
// If not in a git repository, do not attempt to retrieve commit information
|
||||
let git_dir = workspace_root.join(".git");
|
||||
if !git_dir.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(git_head_path) = git_head(&git_dir) {
|
||||
fn commit_info(git_dir: &Path, workspace_root: &Path, is_ruff: bool) {
|
||||
if let Some(git_head_path) = git_head(git_dir) {
|
||||
println!("cargo:rerun-if-changed={}", git_head_path.display());
|
||||
|
||||
let git_head_contents = fs::read_to_string(git_head_path);
|
||||
@@ -96,7 +102,10 @@ fn commit_info(workspace_root: &Path) {
|
||||
let mut describe_parts = describe.split('-');
|
||||
let last_tag = describe_parts.next().unwrap();
|
||||
|
||||
println!("cargo::rustc-env=TY_LAST_TAG={last_tag}");
|
||||
println!(
|
||||
"cargo::rustc-env=TY_LAST_TAG={ruff}{last_tag}",
|
||||
ruff = if is_ruff { "ruff/" } else { "" }
|
||||
);
|
||||
|
||||
// If this is the tagged commit, this component will be missing
|
||||
println!(
|
||||
|
||||
2
crates/ty/docs/cli.md
generated
2
crates/ty/docs/cli.md
generated
@@ -84,7 +84,7 @@ over all configuration files.</p>
|
||||
<li><code>3.11</code></li>
|
||||
<li><code>3.12</code></li>
|
||||
<li><code>3.13</code></li>
|
||||
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a></dt><dd><p>Use quiet output</p>
|
||||
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output (or <code>-qq</code> for silent output)</p>
|
||||
</dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
|
||||
</dd><dt id="ty-check--typeshed"><a href="#ty-check--typeshed"><code>--typeshed</code></a>, <code>--custom-typeshed-dir</code> <i>path</i></dt><dd><p>Custom directory to use for stdlib typeshed stubs</p>
|
||||
</dd><dt id="ty-check--verbose"><a href="#ty-check--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output (or <code>-vv</code> and <code>-vvv</code> for more verbose output)</p>
|
||||
|
||||
168
crates/ty/docs/rules.md
generated
168
crates/ty/docs/rules.md
generated
@@ -36,7 +36,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L98)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L142)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -88,7 +88,7 @@ f(int) # error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L168)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -117,7 +117,7 @@ a = 1
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L193)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -147,7 +147,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L219)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -177,7 +177,7 @@ class B(A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L263)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -202,7 +202,7 @@ class B(A, A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L284)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L426)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L427)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -334,7 +334,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L451)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L316)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L317)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -445,7 +445,7 @@ an atypical memory layout.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L510)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -496,7 +496,7 @@ a: int = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1514)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1515)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L532)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L533)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -550,7 +550,7 @@ class A(42): ... # error: [invalid-base]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L583)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L584)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -575,7 +575,7 @@ with 1:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L604)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L605)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -602,7 +602,7 @@ a: str
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -644,7 +644,7 @@ except ZeroDivisionError:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L664)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -675,7 +675,7 @@ class C[U](Generic[T]): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L689)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L690)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -708,7 +708,7 @@ def f(t: TypeVar("U")): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L738)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -740,7 +740,7 @@ class B(metaclass=f): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -788,7 +788,7 @@ def foo(x: int) -> int: ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -812,7 +812,7 @@ def f(a: int = ''): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L398)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -844,7 +844,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L828)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L829)
|
||||
</small>
|
||||
|
||||
Checks for `raise` statements that raise non-exceptions or use invalid
|
||||
@@ -891,7 +891,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L491)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L492)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -914,7 +914,7 @@ def func() -> int:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -968,7 +968,7 @@ TODO #14889
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L717)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L718)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -993,7 +993,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L910)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1021,7 +1021,7 @@ TYPE_CHECKING = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L934)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L935)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1049,7 +1049,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L987)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1081,7 +1081,7 @@ f(10) # Error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L958)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1113,7 +1113,7 @@ class C:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1014)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1015)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1146,7 +1146,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1044)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1169,7 +1169,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1196,7 +1196,7 @@ func("string") # error: [no-matching-overload]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1218,7 +1218,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1103)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1242,7 +1242,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1154)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1155)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1296,7 +1296,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1490)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1324,7 +1324,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1245)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1351,7 +1351,7 @@ class B(A): ... # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1291)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1376,7 +1376,7 @@ f("foo") # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1268)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1269)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1402,7 +1402,7 @@ def _(x: int):
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1446,7 +1446,7 @@ class A:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1368)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1369)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1471,7 +1471,7 @@ f(x=1, y=2) # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1390)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1497,7 +1497,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1412)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1520,7 +1520,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1431)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1543,7 +1543,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1123)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1124)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1578,7 +1578,7 @@ b1 < b2 < b1 # exception raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1449)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1450)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1604,7 +1604,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1472)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1655,7 +1655,7 @@ a = 20 / 0 # type: ignore
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1175)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1681,7 +1681,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L116)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1711,7 +1711,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1197)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1198)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1741,7 +1741,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1542)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1543)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1766,7 +1766,7 @@ cast(int, f()) # Redundant
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1812,12 +1812,66 @@ Use instead:
|
||||
a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
```
|
||||
|
||||
## `unresolved-global`
|
||||
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Detects variables declared as `global` in an inner scope that have no explicit
|
||||
bindings or declarations in the global scope.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Function bodies with `global` statements can run in any order (or not at all), which makes
|
||||
it hard for static analysis tools to infer the types of globals without
|
||||
explicit definitions or declarations.
|
||||
|
||||
**Example**
|
||||
|
||||
```python
|
||||
def f():
|
||||
global x # unresolved global
|
||||
x = 42
|
||||
|
||||
def g():
|
||||
print(x) # unresolved reference
|
||||
```
|
||||
|
||||
Use instead:
|
||||
```python
|
||||
x: int
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 42
|
||||
|
||||
def g():
|
||||
print(x)
|
||||
```
|
||||
|
||||
Or:
|
||||
```python
|
||||
x: int | None = None
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 42
|
||||
|
||||
def g():
|
||||
print(x)
|
||||
```
|
||||
|
||||
## `unsupported-base`
|
||||
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L550)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L551)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1854,7 +1908,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L245)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1876,7 +1930,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1223)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1224)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
@@ -33,6 +33,7 @@ use ty_server::run_server;
|
||||
|
||||
pub fn run() -> anyhow::Result<ExitStatus> {
|
||||
setup_rayon();
|
||||
ruff_db::set_program_version(crate::version::version().to_string()).unwrap();
|
||||
|
||||
let args = wild::args_os();
|
||||
let args = argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX)
|
||||
@@ -67,10 +68,12 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
|
||||
let printer = Printer::default().with_verbosity(verbosity);
|
||||
|
||||
tracing::warn!(
|
||||
"ty is pre-release software and not ready for production use. \
|
||||
if std::env::var_os("I_KNOW_TY_IS_PRE_RELEASE").is_none() {
|
||||
tracing::warn!(
|
||||
"ty is pre-release software and not ready for production use. \
|
||||
Expect to encounter bugs, missing features, and fatal errors.",
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
tracing::debug!("Version: {}", version::version());
|
||||
|
||||
@@ -120,17 +123,17 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
None => ProjectMetadata::discover(&project_path, &system)?,
|
||||
};
|
||||
|
||||
let options = args.into_options();
|
||||
project_metadata.apply_options(options.clone());
|
||||
project_metadata.apply_configuration_files(&system)?;
|
||||
|
||||
let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options());
|
||||
project_metadata.apply_overrides(&project_options_overrides);
|
||||
|
||||
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
||||
|
||||
if !check_paths.is_empty() {
|
||||
db.project().set_included_paths(&mut db, check_paths);
|
||||
}
|
||||
|
||||
let project_options_overrides = ProjectOptionsOverrides::new(config_file, options);
|
||||
let (main_loop, main_loop_cancellation_token) =
|
||||
MainLoop::new(project_options_overrides, printer);
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ pub(crate) struct Verbosity {
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use quiet output",
|
||||
short,
|
||||
help = "Use quiet output (or `-qq` for silent output)",
|
||||
action = clap::ArgAction::Count,
|
||||
global = true,
|
||||
overrides_with = "verbose",
|
||||
@@ -46,8 +47,8 @@ impl Verbosity {
|
||||
// `--quiet` and `--verbose` are mutually exclusive in Clap, so we can just check one first.
|
||||
match self.quiet {
|
||||
0 => {}
|
||||
_ => return VerbosityLevel::Quiet,
|
||||
// TODO(zanieb): Add support for `-qq` with a "silent" mode
|
||||
1 => return VerbosityLevel::Quiet,
|
||||
_ => return VerbosityLevel::Silent,
|
||||
}
|
||||
|
||||
match self.verbose {
|
||||
@@ -61,6 +62,9 @@ impl Verbosity {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub(crate) enum VerbosityLevel {
|
||||
/// Silent output. Does not show any logging output or summary information.
|
||||
Silent,
|
||||
|
||||
/// Quiet output. Only shows Ruff and ty events up to the [`ERROR`](tracing::Level::ERROR).
|
||||
/// Silences output except for summary information.
|
||||
Quiet,
|
||||
@@ -84,6 +88,7 @@ pub(crate) enum VerbosityLevel {
|
||||
impl VerbosityLevel {
|
||||
const fn level_filter(self) -> LevelFilter {
|
||||
match self {
|
||||
VerbosityLevel::Silent => LevelFilter::OFF,
|
||||
VerbosityLevel::Quiet => LevelFilter::ERROR,
|
||||
VerbosityLevel::Default => LevelFilter::WARN,
|
||||
VerbosityLevel::Verbose => LevelFilter::INFO,
|
||||
|
||||
@@ -34,6 +34,7 @@ impl Printer {
|
||||
}
|
||||
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Silent => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::Quiet => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::Default => ProgressDrawTarget::stderr(),
|
||||
// Hide the progress bar when in verbose mode.
|
||||
@@ -50,6 +51,7 @@ impl Printer {
|
||||
/// [`VerbosityLevel::Quiet`] is used.
|
||||
fn stdout_important(self) -> Stdout {
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Silent => Stdout::disabled(),
|
||||
VerbosityLevel::Quiet => Stdout::enabled(),
|
||||
VerbosityLevel::Default => Stdout::enabled(),
|
||||
VerbosityLevel::Verbose => Stdout::enabled(),
|
||||
@@ -63,6 +65,7 @@ impl Printer {
|
||||
/// The returned stream will be disabled when [`VerbosityLevel::Quiet`] is used.
|
||||
fn stdout_general(self) -> Stdout {
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Silent => Stdout::disabled(),
|
||||
VerbosityLevel::Quiet => Stdout::disabled(),
|
||||
VerbosityLevel::Default => Stdout::enabled(),
|
||||
VerbosityLevel::Verbose => Stdout::enabled(),
|
||||
|
||||
@@ -69,6 +69,25 @@ fn test_quiet_output() -> anyhow::Result<()> {
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// We allow `-q`
|
||||
assert_cmd_snapshot!(case.command().arg("-q"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// And repeated `-qq`
|
||||
assert_cmd_snapshot!(case.command().arg("-qq"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -803,12 +803,12 @@ fn overrides_no_actual_overrides() -> anyhow::Result<()> {
|
||||
warning[useless-overrides-section]: Useless `overrides` section
|
||||
--> pyproject.toml:5:1
|
||||
|
|
||||
3 | division-by-zero = "error"
|
||||
3 | division-by-zero = "error"
|
||||
4 |
|
||||
5 | / [[tool.ty.overrides]]
|
||||
6 | | include = ["*.py"] # Has patterns but no rule overrides
|
||||
| |__________________^ This overrides section configures no rules
|
||||
7 | # Missing [tool.ty.overrides.rules] section entirely
|
||||
5 | [[tool.ty.overrides]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ This overrides section configures no rules
|
||||
6 | include = ["*.py"] # Has patterns but no rule overrides
|
||||
7 | # Missing [tool.ty.overrides.rules] section entirely
|
||||
|
|
||||
info: It has no `rules` table
|
||||
info: Add a `[overrides.rules]` table...
|
||||
|
||||
@@ -1303,6 +1303,139 @@ quux.b<CURSOR>
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metaclass1() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
class Meta(type):
|
||||
@property
|
||||
def meta_attr(self) -> int:
|
||||
return 0
|
||||
|
||||
class C(metaclass=Meta): ...
|
||||
|
||||
C.<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
meta_attr :: int
|
||||
mro :: bound method <class 'C'>.mro() -> list[type]
|
||||
__annotations__ :: dict[str, Any]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
__basicsize__ :: int
|
||||
__call__ :: bound method <class 'C'>.__call__(...) -> Any
|
||||
__class__ :: <class 'Meta'>
|
||||
__delattr__ :: def __delattr__(self, name: str, /) -> None
|
||||
__dict__ :: MappingProxyType[str, Any]
|
||||
__dictoffset__ :: int
|
||||
__dir__ :: def __dir__(self) -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: def __eq__(self, value: object, /) -> bool
|
||||
__flags__ :: int
|
||||
__format__ :: def __format__(self, format_spec: str, /) -> str
|
||||
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> None
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'C'>, <class 'object'>]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls) -> Self
|
||||
__or__ :: bound method <class 'C'>.__or__(value: Any, /) -> UnionType
|
||||
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
|
||||
__qualname__ :: str
|
||||
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: def __repr__(self) -> str
|
||||
__ror__ :: bound method <class 'C'>.__ror__(value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: bound method <class 'C'>.__subclasscheck__(subclass: type, /) -> bool
|
||||
__subclasses__ :: bound method <class 'C'>.__subclasses__() -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'C'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__text_signature__ :: str | None
|
||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
__weakrefoffset__ :: int
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metaclass2() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
class Meta(type):
|
||||
@property
|
||||
def meta_attr(self) -> int:
|
||||
return 0
|
||||
|
||||
class C(metaclass=Meta): ...
|
||||
|
||||
Meta.<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
insta::with_settings!({
|
||||
// The formatting of some types are different depending on
|
||||
// whether we're in release mode or not. These differences
|
||||
// aren't really relevant for completion tests AFAIK, so
|
||||
// just redact them. ---AG
|
||||
filters => [(r"(?m)\s*__(annotations|new)__.+$", "")]},
|
||||
{
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
meta_attr :: property
|
||||
mro :: def mro(self) -> list[type]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
__basicsize__ :: int
|
||||
__call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
|
||||
__class__ :: <class 'type'>
|
||||
__delattr__ :: def __delattr__(self, name: str, /) -> None
|
||||
__dict__ :: MappingProxyType[str, Any]
|
||||
__dictoffset__ :: int
|
||||
__dir__ :: def __dir__(self) -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: def __eq__(self, value: object, /) -> bool
|
||||
__flags__ :: int
|
||||
__format__ :: def __format__(self, format_spec: str, /) -> str
|
||||
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: Overload[(self, o: object, /) -> None, (self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None]
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'Meta'>, <class 'type'>, <class 'object'>]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__or__ :: def __or__(self, value: Any, /) -> UnionType
|
||||
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
|
||||
__qualname__ :: str
|
||||
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: def __repr__(self) -> str
|
||||
__ror__ :: def __ror__(self, value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
|
||||
__subclasses__ :: def __subclasses__(self: Self) -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'Meta'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__text_signature__ :: str | None
|
||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
__weakrefoffset__ :: int
|
||||
");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_init3() {
|
||||
let test = cursor_test(
|
||||
@@ -1358,7 +1491,7 @@ Quux.<CURSOR>
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
mro :: def mro(self) -> list[type]
|
||||
mro :: bound method <class 'Quux'>.mro() -> list[type]
|
||||
some_attribute :: int
|
||||
some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
|
||||
some_method :: def some_method(self) -> int
|
||||
@@ -1368,7 +1501,7 @@ Quux.<CURSOR>
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
__basicsize__ :: int
|
||||
__call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
|
||||
__call__ :: bound method <class 'Quux'>.__call__(...) -> Any
|
||||
__class__ :: <class 'type'>
|
||||
__delattr__ :: def __delattr__(self, name: str, /) -> None
|
||||
__dict__ :: MappingProxyType[str, Any]
|
||||
@@ -1383,32 +1516,122 @@ Quux.<CURSOR>
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> Unknown
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||
__instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'type'>, <class 'object'>]
|
||||
__mro__ :: tuple[<class 'Quux'>, <class 'object'>]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls) -> Self
|
||||
__or__ :: def __or__(self, value: Any, /) -> UnionType
|
||||
__or__ :: bound method <class 'Quux'>.__or__(value: Any, /) -> UnionType
|
||||
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
|
||||
__qualname__ :: str
|
||||
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: def __repr__(self) -> str
|
||||
__ror__ :: def __ror__(self, value: Any, /) -> UnionType
|
||||
__ror__ :: bound method <class 'Quux'>.__ror__(value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
|
||||
__subclasses__ :: def __subclasses__(self: Self) -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'object'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__subclasscheck__ :: bound method <class 'Quux'>.__subclasscheck__(subclass: type, /) -> bool
|
||||
__subclasses__ :: bound method <class 'Quux'>.__subclasses__() -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'Quux'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__text_signature__ :: str | None
|
||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
__weakrefoffset__ :: int
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_attributes() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
from enum import Enum
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
Answer.<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
insta::with_settings!({
|
||||
// See above: filter out some members which contain @Todo types that are
|
||||
// rendered differently in release mode.
|
||||
filters => [(r"(?m)\s*__(call|reduce_ex)__.+$", "")]},
|
||||
{
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
NO :: Literal[Answer.NO]
|
||||
YES :: Literal[Answer.YES]
|
||||
mro :: bound method <class 'Answer'>.mro() -> list[type]
|
||||
name :: Any
|
||||
value :: Any
|
||||
__annotations__ :: dict[str, Any]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
__basicsize__ :: int
|
||||
__bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
|
||||
__class__ :: <class 'EnumMeta'>
|
||||
__contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
|
||||
__copy__ :: def __copy__(self) -> Self
|
||||
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self
|
||||
__delattr__ :: def __delattr__(self, name: str, /) -> None
|
||||
__dict__ :: MappingProxyType[str, Any]
|
||||
__dictoffset__ :: int
|
||||
__dir__ :: def __dir__(self) -> list[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: def __eq__(self, value: object, /) -> bool
|
||||
__flags__ :: int
|
||||
__format__ :: def __format__(self, format_spec: str) -> str
|
||||
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
|
||||
__getitem__ :: bound method <class 'Answer'>.__getitem__(name: str) -> _EnumMemberT
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> None
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__iter__ :: bound method <class 'Answer'>.__iter__() -> Iterator[_EnumMemberT]
|
||||
__len__ :: bound method <class 'Answer'>.__len__() -> int
|
||||
__members__ :: MappingProxyType[str, Unknown]
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'Answer'>, <class 'Enum'>, <class 'object'>]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls, value: object) -> Self
|
||||
__or__ :: bound method <class 'Answer'>.__or__(value: Any, /) -> UnionType
|
||||
__order__ :: str
|
||||
__prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
|
||||
__qualname__ :: str
|
||||
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
|
||||
__repr__ :: def __repr__(self) -> str
|
||||
__reversed__ :: bound method <class 'Answer'>.__reversed__() -> Iterator[_EnumMemberT]
|
||||
__ror__ :: bound method <class 'Answer'>.__ror__(value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__signature__ :: bound method <class 'Answer'>.__signature__() -> str
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
|
||||
__subclasses__ :: bound method <class 'Answer'>.__subclasses__() -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'Answer'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__text_signature__ :: str | None
|
||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
__weakrefoffset__ :: int
|
||||
_generate_next_value_ :: def _generate_next_value_(name: str, start: int, count: int, last_values: list[Any]) -> Any
|
||||
_ignore_ :: str | list[str]
|
||||
_member_map_ :: dict[str, Enum]
|
||||
_member_names_ :: list[str]
|
||||
_missing_ :: bound method <class 'Answer'>._missing_(value: object) -> Any
|
||||
_name_ :: str
|
||||
_order_ :: str
|
||||
_value2member_map_ :: dict[Any, Enum]
|
||||
_value_ :: Any
|
||||
");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// We don't yet take function parameters into account.
|
||||
#[test]
|
||||
fn call_prefix1() {
|
||||
|
||||
@@ -423,14 +423,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:461:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
460 |
|
||||
461 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
462 | @overload
|
||||
463 | def __new__(cls, object: object = ...) -> Self: ...
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:13
|
||||
@@ -452,14 +452,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:461:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
460 |
|
||||
461 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
462 | @overload
|
||||
463 | def __new__(cls, object: object = ...) -> Self: ...
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:22
|
||||
@@ -568,14 +568,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:461:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
460 |
|
||||
461 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
462 | @overload
|
||||
463 | def __new__(cls, object: object = ...) -> Self: ...
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:18
|
||||
@@ -601,16 +601,16 @@ mod tests {
|
||||
// TODO: This should jump to `str` and not `int` because
|
||||
// the keyword is typed as a string. It's only the passed argument that
|
||||
// is an int. Navigating to `str` would match pyright's behavior.
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:244:7
|
||||
--> stdlib/builtins.pyi:338:7
|
||||
|
|
||||
242 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
|
||||
243 |
|
||||
244 | class int:
|
||||
336 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
|
||||
337 |
|
||||
338 | class int:
|
||||
| ^^^
|
||||
245 | @overload
|
||||
246 | def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ...
|
||||
339 | """int([x]) -> integer
|
||||
340 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:18
|
||||
@@ -620,7 +620,7 @@ mod tests {
|
||||
4 | test(a= 123)
|
||||
| ^
|
||||
|
|
||||
");
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -637,14 +637,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:1136:7
|
||||
--> stdlib/builtins.pyi:2892:7
|
||||
|
|
||||
1134 | def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
1135 |
|
||||
1136 | class dict(MutableMapping[_KT, _VT]):
|
||||
2890 | """See PEP 585"""
|
||||
2891 |
|
||||
2892 | class dict(MutableMapping[_KT, _VT]):
|
||||
| ^^^^
|
||||
1137 | # __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics
|
||||
1138 | # Also multiprocessing.managers.SyncManager.dict()
|
||||
2893 | """dict() -> new empty dictionary
|
||||
2894 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:5
|
||||
@@ -666,16 +666,16 @@ f(**kwargs<CURSOR>)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:461:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
460 |
|
||||
461 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
462 | @overload
|
||||
463 | def __new__(cls, object: object = ...) -> Self: ...
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -684,7 +684,7 @@ f(**kwargs<CURSOR>)
|
||||
3 | a
|
||||
| ^
|
||||
|
|
||||
");
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -759,16 +759,16 @@ f(**kwargs<CURSOR>)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:461:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
460 |
|
||||
461 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
462 | @overload
|
||||
463 | def __new__(cls, object: object = ...) -> Self: ...
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:27
|
||||
@@ -778,7 +778,7 @@ f(**kwargs<CURSOR>)
|
||||
4 | print(a)
|
||||
| ^
|
||||
|
|
||||
");
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -790,15 +790,15 @@ f(**kwargs<CURSOR>)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:691:11
|
||||
--> stdlib/types.pyi:922:11
|
||||
|
|
||||
689 | if sys.version_info >= (3, 10):
|
||||
690 | @final
|
||||
691 | class NoneType:
|
||||
920 | if sys.version_info >= (3, 10):
|
||||
921 | @final
|
||||
922 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
692 | def __bool__(self) -> Literal[False]: ...
|
||||
923 | """The type of the None singleton."""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -809,14 +809,14 @@ f(**kwargs<CURSOR>)
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:461:7
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
|
|
||||
459 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
460 |
|
||||
461 | class str(Sequence[str]):
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
462 | @overload
|
||||
463 | def __new__(cls, object: object = ...) -> Self: ...
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -825,7 +825,7 @@ f(**kwargs<CURSOR>)
|
||||
3 | a
|
||||
| ^
|
||||
|
|
||||
");
|
||||
"#);
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::panic::{AssertUnwindSafe, RefUnwindSafe};
|
||||
use std::sync::Arc;
|
||||
use std::{cmp, fmt};
|
||||
|
||||
pub use self::changes::ChangeResult;
|
||||
use crate::metadata::settings::file_settings;
|
||||
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
|
||||
use crate::{ProgressReporter, Project, ProjectMetadata};
|
||||
|
||||
@@ -41,8 +41,6 @@ impl ProjectDatabase {
|
||||
let project_root = project.root(self).to_path_buf();
|
||||
let config_file_override =
|
||||
project_options_overrides.and_then(|options| options.config_file_override.clone());
|
||||
let options =
|
||||
project_options_overrides.map(|project_options| project_options.options.clone());
|
||||
let program = Program::get(self);
|
||||
let custom_stdlib_versions_path = program
|
||||
.custom_stdlib_search_path(self)
|
||||
@@ -218,16 +216,16 @@ impl ProjectDatabase {
|
||||
};
|
||||
match new_project_metadata {
|
||||
Ok(mut metadata) => {
|
||||
if let Some(cli_options) = options {
|
||||
metadata.apply_options(cli_options);
|
||||
}
|
||||
|
||||
if let Err(error) = metadata.apply_configuration_files(self.system()) {
|
||||
tracing::error!(
|
||||
"Failed to apply configuration files, continuing without applying them: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(overrides) = project_options_overrides {
|
||||
metadata.apply_overrides(overrides);
|
||||
}
|
||||
|
||||
match metadata.to_program_settings(self.system(), self.vendored()) {
|
||||
Ok(program_settings) => {
|
||||
let program = Program::get(self);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user