Compare commits
144 Commits
micha/node
...
0.12.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d13228ab85 | ||
|
|
9461d3076f | ||
|
|
63d1d332b3 | ||
|
|
e0149cd9f3 | ||
|
|
2a00eca66b | ||
|
|
3d17897c02 | ||
|
|
fa1df4cedc | ||
|
|
89258f1938 | ||
|
|
1dcef1a011 | ||
|
|
ba629fe262 | ||
|
|
bb3a05f92b | ||
|
|
4daf59e5e7 | ||
|
|
88bd82938f | ||
|
|
5a55bab3f3 | ||
|
|
cc5885e564 | ||
|
|
4573a0f6a0 | ||
|
|
905b9d7f51 | ||
|
|
b605c3e232 | ||
|
|
c281891b5c | ||
|
|
53d795da67 | ||
|
|
385d6fa608 | ||
|
|
ba070bb6d5 | ||
|
|
dc10ab81bd | ||
|
|
7673d46b71 | ||
|
|
9d5ecacdc5 | ||
|
|
9af8597608 | ||
|
|
64e5780037 | ||
|
|
da8aa6a631 | ||
|
|
ee69d38000 | ||
|
|
fd335eb8b7 | ||
|
|
c82fa94e0a | ||
|
|
6d4687c9af | ||
|
|
9180cd094d | ||
|
|
9d98a66f65 | ||
|
|
cb60ecef6b | ||
|
|
215a1c55d4 | ||
|
|
5e29278aa2 | ||
|
|
af62d0368f | ||
|
|
30683e3a93 | ||
|
|
cbc8c08016 | ||
|
|
897889d1ce | ||
|
|
cb5a9ff8dc | ||
|
|
fcdffe4ac9 | ||
|
|
88de5727df | ||
|
|
b8dec79182 | ||
|
|
dc66019fbc | ||
|
|
926e83323a | ||
|
|
5cace28c3e | ||
|
|
3785e13231 | ||
|
|
c2380fa0e2 | ||
|
|
4dec44ae49 | ||
|
|
b6579eaf04 | ||
|
|
f063c0e874 | ||
|
|
6a65734ee3 | ||
|
|
00066e094c | ||
|
|
37a1958374 | ||
|
|
2535d791ae | ||
|
|
05c4399e7b | ||
|
|
b18434b0f6 | ||
|
|
17779c9a17 | ||
|
|
53fc0614da | ||
|
|
59249f483b | ||
|
|
84e76f4d04 | ||
|
|
0acc273286 | ||
|
|
93a9fabb26 | ||
|
|
98d1811dd1 | ||
|
|
06f9f52e59 | ||
|
|
e9a64e5825 | ||
|
|
360eb7005f | ||
|
|
630c7a3152 | ||
|
|
e6e029a8b7 | ||
|
|
64f9481fd0 | ||
|
|
99d0ac60b4 | ||
|
|
ba7ed3a6f9 | ||
|
|
39b41838f3 | ||
|
|
c7640a433e | ||
|
|
1765014be3 | ||
|
|
997dc2e7cc | ||
|
|
4aee0398cb | ||
|
|
1fd9103e81 | ||
|
|
ee2759b365 | ||
|
|
35f33d9bf5 | ||
|
|
5d78b3117a | ||
|
|
c2a05b4825 | ||
|
|
fae0b5c89e | ||
|
|
cbe94b094b | ||
|
|
029de784f1 | ||
|
|
ff94fe7447 | ||
|
|
b2501b45e0 | ||
|
|
291699b375 | ||
|
|
64ac7d7dbf | ||
|
|
5f2e855c29 | ||
|
|
3b4667ec32 | ||
|
|
893f5727e5 | ||
|
|
b8dddd514f | ||
|
|
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 |
34
.github/workflows/ci.yaml
vendored
34
.github/workflows/ci.yaml
vendored
@@ -143,12 +143,12 @@ jobs:
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- ':**' \
|
||||
':!**/*.md' \
|
||||
':crates/ty_python_semantic/resources/mdtest/**/*.md' \
|
||||
# NOTE: Do not exclude all Markdown files here, but rather use
|
||||
# specific exclude patterns like 'docs/**'), because tests for
|
||||
# 'ty' are written in Markdown.
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
||||
':!docs/**' \
|
||||
':!assets/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
@@ -238,13 +238,13 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -296,13 +296,13 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
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@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -381,7 +381,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
|
||||
- name: "Build"
|
||||
run: cargo build --release --locked
|
||||
|
||||
@@ -406,7 +406,7 @@ jobs:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: rustup default "${MSRV}"
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
|
||||
- name: "Build tests"
|
||||
shell: bash
|
||||
env:
|
||||
@@ -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@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
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@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19
|
||||
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 }}
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
|
||||
166
.github/workflows/sync_typeshed.yaml
vendored
166
.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,17 @@ 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
|
||||
|
||||
# The path to the directory that contains the vendored typeshed stubs,
|
||||
# relative to the root of the Ruff repository.
|
||||
VENDORED_TYPESHED: crates/ty_vendored/vendor/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 +49,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 +65,130 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync typeshed
|
||||
id: sync
|
||||
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
|
||||
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' }}
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
mkdir "ruff/${VENDORED_TYPESHED}"
|
||||
cp typeshed/README.md "ruff/${VENDORED_TYPESHED}"
|
||||
cp typeshed/LICENSE "ruff/${VENDORED_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/${VENDORED_TYPESHED}"
|
||||
|
||||
cp -r typeshed/stdlib "ruff/${VENDORED_TYPESHED}/stdlib"
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}/stdlib/@tests"
|
||||
git -C typeshed rev-parse HEAD > "ruff/${VENDORED_TYPESHED}/source_commit.txt"
|
||||
cd ruff
|
||||
git checkout -b typeshedbot/sync-typeshed
|
||||
git checkout -b "${UPSTREAM_BRANCH}"
|
||||
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
|
||||
gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
./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 "${VENDORED_TYPESHED}/stdlib" --config "${VENDORED_TYPESHED}/pyproject.toml" || true
|
||||
git commit -am "Format codemodded docstrings" --allow-empty
|
||||
|
||||
rm "${VENDORED_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 "${UPSTREAM_BRANCH}" --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:
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@27dd66d9e397d986ef9c631119ee09556eab8af9"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
|
||||
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@27dd66d9e397d986ef9c631119ee09556eab8af9"
|
||||
|
||||
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.4
|
||||
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]
|
||||
|
||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,5 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.5
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH101`, `PTH104`, `PTH105`, `PTH121` ([#19404](https://github.com/astral-sh/ruff/pull/19404))
|
||||
- \[`ruff`\] Support byte strings (`RUF055`) ([#18926](https://github.com/astral-sh/ruff/pull/18926))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix `unreachable` panic in parser ([#19183](https://github.com/astral-sh/ruff/pull/19183))
|
||||
- \[`flake8-pyi`\] Skip fix if all `Union` members are `None` (`PYI016`) ([#19416](https://github.com/astral-sh/ruff/pull/19416))
|
||||
- \[`perflint`\] Parenthesize generator expressions (`PERF401`) ([#19325](https://github.com/astral-sh/ruff/pull/19325))
|
||||
- \[`pylint`\] Handle empty comments after line continuation (`PLR2044`) ([#19405](https://github.com/astral-sh/ruff/pull/19405))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pep8-naming`\] Fix `N802` false positives for `CGIHTTPRequestHandler` and `SimpleHTTPRequestHandler` ([#19432](https://github.com/astral-sh/ruff/pull/19432))
|
||||
|
||||
## 0.12.4
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-type-checking`, `pyupgrade`, `ruff`\] Add `from __future__ import annotations` when it would allow new fixes (`TC001`, `TC002`, `TC003`, `UP037`, `RUF013`) ([#19100](https://github.com/astral-sh/ruff/pull/19100))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH109` ([#19245](https://github.com/astral-sh/ruff/pull/19245))
|
||||
- \[`pylint`\] Detect indirect `pathlib.Path` usages for `unspecified-encoding` (`PLW1514`) ([#19304](https://github.com/astral-sh/ruff/pull/19304))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-bugbear`\] Fix `B017` false negatives for keyword exception arguments ([#19217](https://github.com/astral-sh/ruff/pull/19217))
|
||||
- \[`flake8-use-pathlib`\] Fix false negative on direct `Path()` instantiation (`PTH210`) ([#19388](https://github.com/astral-sh/ruff/pull/19388))
|
||||
- \[`flake8-django`\] Fix `DJ008` false positive for abstract models with type-annotated `abstract` field ([#19221](https://github.com/astral-sh/ruff/pull/19221))
|
||||
- \[`isort`\] Fix `I002` import insertion after docstring with multiple string statements ([#19222](https://github.com/astral-sh/ruff/pull/19222))
|
||||
- \[`isort`\] Treat form feed as valid whitespace before a semicolon ([#19343](https://github.com/astral-sh/ruff/pull/19343))
|
||||
- \[`pydoclint`\] Fix `SyntaxError` from fixes with line continuations (`D201`, `D202`) ([#19246](https://github.com/astral-sh/ruff/pull/19246))
|
||||
- \[`refurb`\] `FURB164` fix should validate arguments and should usually be marked unsafe ([#19136](https://github.com/astral-sh/ruff/pull/19136))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-use-pathlib`\] Skip single dots for `invalid-pathlib-with-suffix` (`PTH210`) on versions >= 3.14 ([#19331](https://github.com/astral-sh/ruff/pull/19331))
|
||||
- \[`pep8_naming`\] Avoid false positives on standard library functions with uppercase names (`N802`) ([#18907](https://github.com/astral-sh/ruff/pull/18907))
|
||||
- \[`pycodestyle`\] Handle brace escapes for t-strings in logical lines ([#19358](https://github.com/astral-sh/ruff/pull/19358))
|
||||
- \[`pylint`\] Extend invalid string character rules to include t-strings ([#19355](https://github.com/astral-sh/ruff/pull/19355))
|
||||
- \[`ruff`\] Allow `strict` kwarg when checking for `starmap-zip` (`RUF058`) in Python 3.14+ ([#19333](https://github.com/astral-sh/ruff/pull/19333))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-type-checking`\] Make `TC010` docs example more realistic ([#19356](https://github.com/astral-sh/ruff/pull/19356))
|
||||
- Make more documentation examples error out-of-the-box ([#19288](https://github.com/astral-sh/ruff/pull/19288),[#19272](https://github.com/astral-sh/ruff/pull/19272),[#19291](https://github.com/astral-sh/ruff/pull/19291),[#19296](https://github.com/astral-sh/ruff/pull/19296),[#19292](https://github.com/astral-sh/ruff/pull/19292),[#19295](https://github.com/astral-sh/ruff/pull/19295),[#19297](https://github.com/astral-sh/ruff/pull/19297),[#19309](https://github.com/astral-sh/ruff/pull/19309))
|
||||
|
||||
## 0.12.3
|
||||
|
||||
### Preview features
|
||||
|
||||
@@ -266,6 +266,13 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
|
||||
|
||||
## MkDocs
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The documentation uses Material for MkDocs Insiders, which is closed-source software.
|
||||
> This means only members of the Astral organization can preview the documentation exactly as it
|
||||
> will appear in production.
|
||||
> Outside contributors can still preview the documentation, but there will be some differences. Consult [the Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/insiders/benefits/#features) for which features are exclusively available in the insiders version.
|
||||
|
||||
To preview any changes to the documentation locally:
|
||||
|
||||
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
||||
|
||||
234
Cargo.lock
generated
234
Cargo.lock
generated
@@ -261,6 +261,18 @@ version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -396,9 +408,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 +418,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 +461,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 +603,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 +612,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 +945,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1013,7 +1025,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]]
|
||||
@@ -1121,6 +1133,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -1133,9 +1151,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 +1162,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",
|
||||
@@ -1557,6 +1575,15 @@ dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
@@ -1586,7 +1613,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.1",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1650,7 +1677,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2123,16 +2150,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "papaya"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"seize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@@ -2465,7 +2482,7 @@ dependencies = [
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2549,6 +2566,12 @@ version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -2562,9 +2585,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
@@ -2711,7 +2734,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.3"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2763,7 +2786,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
"wild",
|
||||
@@ -2779,7 +2802,7 @@ dependencies = [
|
||||
"ruff_annotate_snippets",
|
||||
"serde",
|
||||
"snapbox",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tryfn",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -2828,7 +2851,6 @@ dependencies = [
|
||||
"anstyle",
|
||||
"arc-swap",
|
||||
"camino",
|
||||
"countme",
|
||||
"dashmap",
|
||||
"dunce",
|
||||
"etcetera",
|
||||
@@ -2839,6 +2861,7 @@ dependencies = [
|
||||
"insta",
|
||||
"matchit",
|
||||
"path-slash",
|
||||
"quick-junit",
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_cache",
|
||||
"ruff_diagnostics",
|
||||
@@ -2895,7 +2918,7 @@ dependencies = [
|
||||
"similar",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -2962,7 +2985,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.3"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -2987,7 +3010,6 @@ dependencies = [
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
"regex",
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_cache",
|
||||
@@ -3016,7 +3038,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.1",
|
||||
@@ -3042,7 +3064,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.2",
|
||||
"ruff_diagnostics",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
@@ -3266,7 +3288,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
@@ -3295,7 +3317,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.3"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3356,7 +3378,7 @@ dependencies = [
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3391,7 +3413,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3409,7 +3431,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=fc00eba89e5dcaa5edba51c41aa5f309b5cb126b#fc00eba89e5dcaa5edba51c41aa5f309b5cb126b"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=dba66f1a37acca014c2402f231ed5b361bd7d8fe#dba66f1a37acca014c2402f231ed5b361bd7d8fe"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3419,7 +3441,7 @@ dependencies = [
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
"intrusive-collections",
|
||||
"papaya",
|
||||
"inventory",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
@@ -3434,12 +3456,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=fc00eba89e5dcaa5edba51c41aa5f309b5cb126b#fc00eba89e5dcaa5edba51c41aa5f309b5cb126b"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=dba66f1a37acca014c2402f231ed5b361bd7d8fe#dba66f1a37acca014c2402f231ed5b361bd7d8fe"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=fc00eba89e5dcaa5edba51c41aa5f309b5cb126b#fc00eba89e5dcaa5edba51c41aa5f309b5cb126b"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=dba66f1a37acca014c2402f231ed5b361bd7d8fe#dba66f1a37acca014c2402f231ed5b361bd7d8fe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3492,16 +3514,6 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "seize"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4b8d813387d566f627f3ea1b914c068aac94c40ae27ec43f5f33bde65abefe7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@@ -3546,9 +3558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
version = "1.0.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -3576,6 +3588,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"
|
||||
@@ -3729,23 +3750,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -3771,6 +3791,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
@@ -3781,7 +3807,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3981,11 +4007,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 +4036,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 +4053,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 +4194,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
@@ -4153,6 +4211,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
@@ -4161,11 +4220,10 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4197,9 +4255,8 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"ty_ide",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -4210,6 +4267,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.1",
|
||||
"bitvec",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"compact_str",
|
||||
@@ -4246,6 +4304,7 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thin-vec",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
@@ -4259,13 +4318,18 @@ name = "ty_server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.1",
|
||||
"crossbeam",
|
||||
"dunce",
|
||||
"insta",
|
||||
"jod-thread",
|
||||
"libc",
|
||||
"lsp-server",
|
||||
"lsp-types",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
@@ -4273,6 +4337,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -4314,7 +4379,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
@@ -4532,7 +4597,7 @@ checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"rand 0.9.1",
|
||||
"rand 0.9.2",
|
||||
"uuid-macro-internal",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -4808,7 +4873,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]]
|
||||
@@ -5061,6 +5126,15 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -57,6 +57,9 @@ assert_fs = { version = "1.1.0" }
|
||||
argfile = { version = "0.2.0" }
|
||||
bincode = { version = "2.0.0" }
|
||||
bitflags = { version = "2.5.0" }
|
||||
bitvec = { version = "1.0.1", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
bstr = { version = "1.9.1" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
camino = { version = "1.1.7" }
|
||||
@@ -138,7 +141,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa", rev = "fc00eba89e5dcaa5edba51c41aa5f309b5cb126b" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa", rev = "dba66f1a37acca014c2402f231ed5b361bd7d8fe" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
@@ -150,7 +153,7 @@ serde_with = { version = "3.6.0", default-features = false, features = [
|
||||
] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.2" }
|
||||
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new"] }
|
||||
snapbox = { version = "0.6.0", features = [
|
||||
"diff",
|
||||
"term-svg",
|
||||
@@ -163,9 +166,10 @@ strum_macros = { version = "0.27.0" }
|
||||
syn = { version = "2.0.55" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thin-vec = { version = "0.2.14" }
|
||||
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" }
|
||||
|
||||
@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.12.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.3/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.12.5/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.5/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.3
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.3"
|
||||
version = "0.12.5"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -454,7 +454,7 @@ impl LintCacheData {
|
||||
CacheMessage {
|
||||
rule,
|
||||
body: msg.body().to_string(),
|
||||
suggestion: msg.suggestion().map(ToString::to_string),
|
||||
suggestion: msg.first_help_text().map(ToString::to_string),
|
||||
range: msg.expect_range(),
|
||||
parent: msg.parent(),
|
||||
fix: msg.fix().cloned(),
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -15,8 +15,8 @@ use ruff_db::diagnostic::{
|
||||
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,
|
||||
Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter, 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()
|
||||
@@ -248,7 +252,11 @@ impl Printer {
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Junit => {
|
||||
JunitEmitter.emit(writer, &diagnostics.inner, &context)?;
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Junit)
|
||||
.preview(preview);
|
||||
let value = DisplayDiagnostics::new(&context, &config, &diagnostics.inner);
|
||||
write!(writer, "{value}")?;
|
||||
}
|
||||
OutputFormat::Concise | OutputFormat::Full => {
|
||||
TextEmitter::default()
|
||||
@@ -256,6 +264,7 @@ impl Printer {
|
||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
||||
.with_show_source(self.format == OutputFormat::Full)
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.with_preview(preview)
|
||||
.emit(writer, &diagnostics.inner, &context)?;
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
@@ -290,7 +299,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'.
|
||||
");
|
||||
@@ -995,6 +993,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
|
||||
- `lint.exclude`
|
||||
- `lint.preview`
|
||||
- `lint.typing-extensions`
|
||||
- `lint.future-annotations`
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
@@ -5719,8 +5718,11 @@ match 42: # invalid-syntax
|
||||
|
||||
let snapshot = format!("output_format_{output_format}");
|
||||
|
||||
let project_dir = dunce::canonicalize(tempdir.path())?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![
|
||||
(tempdir_filter(&project_dir).as_str(), "[TMP]/"),
|
||||
(tempdir_filter(&tempdir).as_str(), "[TMP]/"),
|
||||
(r#""[^"]+\\?/?input.py"#, r#""[TMP]/input.py"#),
|
||||
(ruff_linter::VERSION, "[VERSION]"),
|
||||
@@ -5746,3 +5748,25 @@ match 42: # invalid-syntax
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations_preview_warning() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "lint.future-annotations = true"])
|
||||
.args(["--select", "F"])
|
||||
.arg("--no-preview")
|
||||
.arg("-")
|
||||
.pass_stdin("1"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: The `lint.future-annotations` setting will have no effect because `preview` is disabled
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ exit_code: 1
|
||||
<testcase name="org.ruff.F821" classname="[TMP]/input" line="2" column="5">
|
||||
<failure message="Undefined name `y`">line 2, col 5, Undefined name `y`</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff" classname="[TMP]/input" line="3" column="1">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="[TMP]/input" line="3" column="1">
|
||||
<failure message="SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)">line 3, col 1, SyntaxError: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
|
||||
@@ -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};
|
||||
@@ -17,7 +18,7 @@ use ruff_python_ast::PythonVersion;
|
||||
use ty_project::metadata::options::{EnvironmentOptions, Options};
|
||||
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||
use ty_project::watch::{ChangeEvent, ChangedKind};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
use ty_project::{CheckMode, Db, ProjectDatabase, ProjectMetadata};
|
||||
|
||||
struct Case {
|
||||
db: ProjectDatabase,
|
||||
@@ -101,6 +102,7 @@ fn setup_tomllib_case() -> Case {
|
||||
|
||||
let re = re.unwrap();
|
||||
|
||||
db.set_check_mode(CheckMode::OpenFiles);
|
||||
db.project().set_open_files(&mut db, tomllib_files);
|
||||
|
||||
let re_path = re.path(&db).as_system_path().unwrap().to_owned();
|
||||
@@ -236,6 +238,7 @@ fn setup_micro_case(code: &str) -> Case {
|
||||
let mut db = ProjectDatabase::new(metadata, system).unwrap();
|
||||
let file = system_path_to_file(&db, SystemPathBuf::from(file_path)).unwrap();
|
||||
|
||||
db.set_check_mode(CheckMode::OpenFiles);
|
||||
db.project()
|
||||
.set_open_files(&mut db, FxHashSet::from_iter([file]));
|
||||
|
||||
@@ -441,6 +444,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,
|
||||
@@ -493,14 +527,21 @@ impl<'a> ProjectBenchmark<'a> {
|
||||
|
||||
#[track_caller]
|
||||
fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
|
||||
fn check_project(db: &mut ProjectDatabase, max_diagnostics: usize) {
|
||||
fn check_project(db: &mut ProjectDatabase, project_name: &str, max_diagnostics: usize) {
|
||||
let result = db.check();
|
||||
let diagnostics = result.len();
|
||||
|
||||
assert!(
|
||||
diagnostics <= max_diagnostics,
|
||||
"Expected <={max_diagnostics} diagnostics but got {diagnostics}"
|
||||
);
|
||||
if diagnostics > max_diagnostics {
|
||||
let details = result
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.concise_message().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ");
|
||||
assert!(
|
||||
diagnostics <= max_diagnostics,
|
||||
"{project_name}: Expected <={max_diagnostics} diagnostics but got {diagnostics}:\n {details}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setup_rayon();
|
||||
@@ -510,7 +551,7 @@ fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
|
||||
group.bench_function(benchmark.project.config.name, |b| {
|
||||
b.iter_batched_ref(
|
||||
|| benchmark.setup_iteration(),
|
||||
|db| check_project(db, benchmark.max_diagnostics),
|
||||
|db| check_project(db, benchmark.project.config.name, benchmark.max_diagnostics),
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
@@ -578,7 +619,7 @@ fn datetype(criterion: &mut Criterion) {
|
||||
max_dep_date: "2025-07-04",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
0,
|
||||
2,
|
||||
);
|
||||
|
||||
bench_project(&benchmark, criterion);
|
||||
@@ -591,6 +632,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);
|
||||
|
||||
@@ -25,7 +25,6 @@ ty_static = { workspace = true }
|
||||
anstyle = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
camino = { workspace = true }
|
||||
countme = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
filetime = { workspace = true }
|
||||
@@ -34,6 +33,7 @@ glob = { workspace = true }
|
||||
ignore = { workspace = true, optional = true }
|
||||
matchit = { workspace = true }
|
||||
path-slash = { workspace = true }
|
||||
quick-junit = { workspace = true, optional = true }
|
||||
rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
@@ -56,7 +56,13 @@ tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
cache = ["ruff_cache"]
|
||||
junit = ["dep:quick-junit"]
|
||||
os = ["ignore", "dep:etcetera"]
|
||||
serde = ["camino/serde1", "dep:serde", "dep:serde_json", "ruff_diagnostics/serde"]
|
||||
serde = [
|
||||
"camino/serde1",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"ruff_diagnostics/serde",
|
||||
]
|
||||
# Exposes testing utilities.
|
||||
testing = ["tracing-subscriber"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fmt::Formatter, sync::Arc};
|
||||
use std::{fmt::Formatter, path::Path, sync::Arc};
|
||||
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
@@ -122,7 +122,14 @@ impl Diagnostic {
|
||||
/// directly. If callers want or need to avoid cloning the diagnostic
|
||||
/// message, then they can also pass a `DiagnosticMessage` directly.
|
||||
pub fn info<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
|
||||
self.sub(SubDiagnostic::new(Severity::Info, message));
|
||||
self.sub(SubDiagnostic::new(SubDiagnosticSeverity::Info, message));
|
||||
}
|
||||
|
||||
/// Adds a "help" sub-diagnostic with the given message.
|
||||
///
|
||||
/// See the closely related [`Diagnostic::info`] method for more details.
|
||||
pub fn help<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
|
||||
self.sub(SubDiagnostic::new(SubDiagnosticSeverity::Help, message));
|
||||
}
|
||||
|
||||
/// Adds a "sub" diagnostic to this diagnostic.
|
||||
@@ -308,6 +315,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);
|
||||
}
|
||||
|
||||
@@ -373,9 +384,15 @@ impl Diagnostic {
|
||||
self.primary_message()
|
||||
}
|
||||
|
||||
/// Returns the fix suggestion for the violation.
|
||||
pub fn suggestion(&self) -> Option<&str> {
|
||||
self.primary_annotation()?.get_message()
|
||||
/// Returns the message of the first sub-diagnostic with a `Help` severity.
|
||||
///
|
||||
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
|
||||
/// general this is not the guaranteed meaning of such a message.
|
||||
pub fn first_help_text(&self) -> Option<&str> {
|
||||
self.sub_diagnostics()
|
||||
.iter()
|
||||
.find(|sub| matches!(sub.inner.severity, SubDiagnosticSeverity::Help))
|
||||
.map(|sub| sub.inner.message.as_str())
|
||||
}
|
||||
|
||||
/// Returns the URL for the rule documentation, if it exists.
|
||||
@@ -561,7 +578,10 @@ impl SubDiagnostic {
|
||||
/// Callers can pass anything that implements `std::fmt::Display`
|
||||
/// directly. If callers want or need to avoid cloning the diagnostic
|
||||
/// message, then they can also pass a `DiagnosticMessage` directly.
|
||||
pub fn new<'a>(severity: Severity, message: impl IntoDiagnosticMessage + 'a) -> SubDiagnostic {
|
||||
pub fn new<'a>(
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: impl IntoDiagnosticMessage + 'a,
|
||||
) -> SubDiagnostic {
|
||||
let inner = Box::new(SubDiagnosticInner {
|
||||
severity,
|
||||
message: message.into_diagnostic_message(),
|
||||
@@ -639,7 +659,7 @@ impl SubDiagnostic {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
struct SubDiagnosticInner {
|
||||
severity: Severity,
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: DiagnosticMessage,
|
||||
annotations: Vec<Annotation>,
|
||||
}
|
||||
@@ -1008,6 +1028,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)),
|
||||
@@ -1154,6 +1186,32 @@ impl Severity {
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`Severity`] but exclusively for sub-diagnostics.
|
||||
///
|
||||
/// This type only exists to add an additional `Help` severity that isn't present in `Severity` or
|
||||
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
|
||||
/// deleted and the two combined again.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
|
||||
pub enum SubDiagnosticSeverity {
|
||||
Help,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
}
|
||||
|
||||
impl SubDiagnosticSeverity {
|
||||
fn to_annotate(self) -> AnnotateLevel {
|
||||
match self {
|
||||
SubDiagnosticSeverity::Help => AnnotateLevel::Help,
|
||||
SubDiagnosticSeverity::Info => AnnotateLevel::Info,
|
||||
SubDiagnosticSeverity::Warning => AnnotateLevel::Warning,
|
||||
SubDiagnosticSeverity::Error => AnnotateLevel::Error,
|
||||
SubDiagnosticSeverity::Fatal => AnnotateLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for rendering diagnostics.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DisplayDiagnosticConfig {
|
||||
@@ -1180,6 +1238,15 @@ pub struct DisplayDiagnosticConfig {
|
||||
reason = "This is currently only used for JSON but will be needed soon for other formats"
|
||||
)]
|
||||
preview: bool,
|
||||
/// Whether to hide the real `Severity` of diagnostics.
|
||||
///
|
||||
/// This is intended for temporary use by Ruff, which only has a single `error` severity at the
|
||||
/// moment. We should be able to remove this option when Ruff gets more severities.
|
||||
hide_severity: bool,
|
||||
/// Whether to show the availability of a fix in a diagnostic.
|
||||
show_fix_status: bool,
|
||||
/// The lowest applicability that should be shown when reporting diagnostics.
|
||||
fix_applicability: Applicability,
|
||||
}
|
||||
|
||||
impl DisplayDiagnosticConfig {
|
||||
@@ -1208,6 +1275,35 @@ impl DisplayDiagnosticConfig {
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to hide a diagnostic's severity or not.
|
||||
pub fn hide_severity(self, yes: bool) -> DisplayDiagnosticConfig {
|
||||
DisplayDiagnosticConfig {
|
||||
hide_severity: yes,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to show a fix's availability or not.
|
||||
pub fn show_fix_status(self, yes: bool) -> DisplayDiagnosticConfig {
|
||||
DisplayDiagnosticConfig {
|
||||
show_fix_status: yes,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the lowest fix applicability that should be shown.
|
||||
///
|
||||
/// In other words, an applicability of `Safe` (the default) would suppress showing fixes or fix
|
||||
/// availability for unsafe or display-only fixes.
|
||||
///
|
||||
/// Note that this option is currently ignored when `hide_severity` is false.
|
||||
pub fn fix_applicability(self, applicability: Applicability) -> DisplayDiagnosticConfig {
|
||||
DisplayDiagnosticConfig {
|
||||
fix_applicability: applicability,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DisplayDiagnosticConfig {
|
||||
@@ -1217,6 +1313,9 @@ impl Default for DisplayDiagnosticConfig {
|
||||
color: false,
|
||||
context: 2,
|
||||
preview: false,
|
||||
hide_severity: false,
|
||||
show_fix_status: false,
|
||||
fix_applicability: Applicability::Safe,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1259,6 +1358,16 @@ 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,
|
||||
/// Print diagnostics in the format expected by JUnit.
|
||||
#[cfg(feature = "junit")]
|
||||
Junit,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@@ -8,7 +9,7 @@ use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
|
||||
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
|
||||
use crate::{
|
||||
Db,
|
||||
files::File,
|
||||
@@ -17,17 +18,26 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticSource, DisplayDiagnosticConfig, Severity,
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticSource, DisplayDiagnosticConfig,
|
||||
SubDiagnostic, UnifiedFile,
|
||||
};
|
||||
|
||||
use azure::AzureRenderer;
|
||||
use concise::ConciseRenderer;
|
||||
use pylint::PylintRenderer;
|
||||
|
||||
mod azure;
|
||||
mod concise;
|
||||
mod full;
|
||||
#[cfg(feature = "serde")]
|
||||
mod json;
|
||||
#[cfg(feature = "serde")]
|
||||
mod json_lines;
|
||||
#[cfg(feature = "junit")]
|
||||
mod junit;
|
||||
mod pylint;
|
||||
#[cfg(feature = "serde")]
|
||||
mod rdjson;
|
||||
|
||||
/// A type that implements `std::fmt::Display` for diagnostic rendering.
|
||||
///
|
||||
@@ -97,48 +107,7 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self.config.format {
|
||||
DiagnosticFormat::Concise => {
|
||||
let stylesheet = if self.config.color {
|
||||
DiagnosticStylesheet::styled()
|
||||
} else {
|
||||
DiagnosticStylesheet::plain()
|
||||
};
|
||||
|
||||
for diag in self.diagnostics {
|
||||
let (severity, severity_style) = match diag.severity() {
|
||||
Severity::Info => ("info", stylesheet.info),
|
||||
Severity::Warning => ("warning", stylesheet.warning),
|
||||
Severity::Error => ("error", stylesheet.error),
|
||||
Severity::Fatal => ("fatal", stylesheet.error),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{severity}[{id}]",
|
||||
severity = fmt_styled(severity, severity_style),
|
||||
id = fmt_styled(diag.id(), stylesheet.emphasis)
|
||||
)?;
|
||||
if let Some(span) = diag.primary_span() {
|
||||
write!(
|
||||
f,
|
||||
" {path}",
|
||||
path = fmt_styled(span.file().path(self.resolver), stylesheet.emphasis)
|
||||
)?;
|
||||
if let Some(range) = span.range() {
|
||||
let diagnostic_source = span.file().diagnostic_source(self.resolver);
|
||||
let start = diagnostic_source
|
||||
.as_source_code()
|
||||
.line_column(range.start());
|
||||
|
||||
write!(
|
||||
f,
|
||||
":{line}:{col}",
|
||||
line = fmt_styled(start.line, stylesheet.emphasis),
|
||||
col = fmt_styled(start.column, stylesheet.emphasis),
|
||||
)?;
|
||||
}
|
||||
write!(f, ":")?;
|
||||
}
|
||||
writeln!(f, " {message}", message = diag.concise_message())?;
|
||||
}
|
||||
ConciseRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?;
|
||||
}
|
||||
DiagnosticFormat::Full => {
|
||||
let stylesheet = if self.config.color {
|
||||
@@ -151,7 +120,8 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
|
||||
AnnotateRenderer::styled()
|
||||
} else {
|
||||
AnnotateRenderer::plain()
|
||||
};
|
||||
}
|
||||
.cut_indicator("…");
|
||||
|
||||
renderer = renderer
|
||||
.error(stylesheet.error)
|
||||
@@ -184,6 +154,17 @@ 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)?;
|
||||
}
|
||||
#[cfg(feature = "junit")]
|
||||
DiagnosticFormat::Junit => {
|
||||
junit::JunitRenderer::new(self.resolver).render(f, self.diagnostics)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -237,7 +218,7 @@ impl<'a> Resolved<'a> {
|
||||
/// both.)
|
||||
#[derive(Debug)]
|
||||
struct ResolvedDiagnostic<'a> {
|
||||
severity: Severity,
|
||||
level: AnnotateLevel,
|
||||
id: Option<String>,
|
||||
message: String,
|
||||
annotations: Vec<ResolvedAnnotation<'a>>,
|
||||
@@ -262,7 +243,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
let id = Some(diag.inner.id.to_string());
|
||||
let message = diag.inner.message.as_str().to_string();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
level: diag.inner.severity.to_annotate(),
|
||||
id,
|
||||
message,
|
||||
annotations,
|
||||
@@ -285,7 +266,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
})
|
||||
.collect();
|
||||
ResolvedDiagnostic {
|
||||
severity: diag.inner.severity,
|
||||
level: diag.inner.severity.to_annotate(),
|
||||
id: None,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
@@ -352,7 +333,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
snippets_by_input
|
||||
.sort_by(|snips1, snips2| snips1.has_primary.cmp(&snips2.has_primary).reverse());
|
||||
RenderableDiagnostic {
|
||||
severity: self.severity,
|
||||
level: self.level,
|
||||
id: self.id.as_deref(),
|
||||
message: &self.message,
|
||||
snippets_by_input,
|
||||
@@ -440,7 +421,7 @@ struct Renderable<'r> {
|
||||
#[derive(Debug)]
|
||||
struct RenderableDiagnostic<'r> {
|
||||
/// The severity of the diagnostic.
|
||||
severity: Severity,
|
||||
level: AnnotateLevel,
|
||||
/// The ID of the diagnostic. The ID can usually be used on the CLI or in a
|
||||
/// config file to change the severity of a lint.
|
||||
///
|
||||
@@ -459,7 +440,6 @@ struct RenderableDiagnostic<'r> {
|
||||
impl RenderableDiagnostic<'_> {
|
||||
/// Convert this to an "annotate" snippet.
|
||||
fn to_annotate(&self) -> AnnotateMessage<'_> {
|
||||
let level = self.severity.to_annotate();
|
||||
let snippets = self.snippets_by_input.iter().flat_map(|snippets| {
|
||||
let path = snippets.path;
|
||||
snippets
|
||||
@@ -467,7 +447,7 @@ impl RenderableDiagnostic<'_> {
|
||||
.iter()
|
||||
.map(|snippet| snippet.to_annotate(path))
|
||||
});
|
||||
let mut message = level.title(self.message);
|
||||
let mut message = self.level.title(self.message);
|
||||
if let Some(id) = self.id {
|
||||
message = message.id(id);
|
||||
}
|
||||
@@ -705,6 +685,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 +723,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 +759,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.
|
||||
@@ -832,9 +823,12 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_diagnostics::{Applicability, Edit, Fix};
|
||||
|
||||
use crate::diagnostic::{Annotation, DiagnosticId, SecondaryCode, Severity, Span};
|
||||
use crate::diagnostic::{
|
||||
Annotation, DiagnosticId, IntoDiagnosticMessage, SecondaryCode, Severity, Span,
|
||||
SubDiagnosticSeverity,
|
||||
};
|
||||
use crate::files::system_path_to_file;
|
||||
use crate::system::{DbWithWritableSystem, SystemPath};
|
||||
use crate::tests::TestDb;
|
||||
@@ -1518,7 +1512,7 @@ watermelon
|
||||
|
||||
let mut diag = env.err().primary("animals", "3", "3", "").build();
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "this is a helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "this is a helpful note")
|
||||
.build(),
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
@@ -1547,15 +1541,15 @@ watermelon
|
||||
|
||||
let mut diag = env.err().primary("animals", "3", "3", "").build();
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "this is a helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "this is a helpful note")
|
||||
.build(),
|
||||
);
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "another helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "another helpful note")
|
||||
.build(),
|
||||
);
|
||||
diag.sub(
|
||||
env.sub_builder(Severity::Info, "and another helpful note")
|
||||
env.sub_builder(SubDiagnosticSeverity::Info, "and another helpful note")
|
||||
.build(),
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
@@ -2277,6 +2271,27 @@ watermelon
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Hide diagnostic severity when rendering.
|
||||
pub(super) fn hide_severity(&mut self, yes: bool) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
config = config.hide_severity(yes);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Show fix availability when rendering.
|
||||
pub(super) fn show_fix_status(&mut self, yes: bool) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
config = config.show_fix_status(yes);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// The lowest fix applicability to show when rendering.
|
||||
pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
config = config.fix_applicability(applicability);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Add a file with the given path and contents to this environment.
|
||||
pub(super) fn add(&mut self, path: &str, contents: &str) {
|
||||
let path = SystemPath::new(path);
|
||||
@@ -2340,7 +2355,7 @@ watermelon
|
||||
/// sub-diagnostic with "error" severity and canned values for
|
||||
/// its identifier and message.
|
||||
fn sub_warn(&mut self) -> SubDiagnosticBuilder<'_> {
|
||||
self.sub_builder(Severity::Warning, "sub-diagnostic message")
|
||||
self.sub_builder(SubDiagnosticSeverity::Warning, "sub-diagnostic message")
|
||||
}
|
||||
|
||||
/// Returns a builder for tersely constructing diagnostics.
|
||||
@@ -2361,7 +2376,11 @@ watermelon
|
||||
}
|
||||
|
||||
/// Returns a builder for tersely constructing sub-diagnostics.
|
||||
fn sub_builder(&mut self, severity: Severity, message: &str) -> SubDiagnosticBuilder<'_> {
|
||||
fn sub_builder(
|
||||
&mut self,
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: &str,
|
||||
) -> SubDiagnosticBuilder<'_> {
|
||||
let subdiag = SubDiagnostic::new(severity, message);
|
||||
SubDiagnosticBuilder { env: self, subdiag }
|
||||
}
|
||||
@@ -2464,6 +2483,12 @@ watermelon
|
||||
self.diag.set_noqa_offset(noqa_offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a "help" sub-diagnostic with the given message.
|
||||
fn help(mut self, message: impl IntoDiagnosticMessage) -> DiagnosticBuilder<'e> {
|
||||
self.diag.help(message);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper builder for tersely populating a `SubDiagnostic`.
|
||||
@@ -2570,7 +2595,8 @@ def fibonacci(n):
|
||||
|
||||
let diagnostics = vec![
|
||||
env.builder("unused-import", Severity::Error, "`os` imported but unused")
|
||||
.primary("fib.py", "1:7", "1:9", "Remove unused import: `os`")
|
||||
.primary("fib.py", "1:7", "1:9", "")
|
||||
.help("Remove unused import: `os`")
|
||||
.secondary_code("F401")
|
||||
.fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(0),
|
||||
@@ -2583,12 +2609,8 @@ def fibonacci(n):
|
||||
Severity::Error,
|
||||
"Local variable `x` is assigned to but never used",
|
||||
)
|
||||
.primary(
|
||||
"fib.py",
|
||||
"6:4",
|
||||
"6:5",
|
||||
"Remove assignment to unused variable `x`",
|
||||
)
|
||||
.primary("fib.py", "6:4", "6:5", "")
|
||||
.help("Remove assignment to unused variable `x`")
|
||||
.secondary_code("F841")
|
||||
.fix(Fix::unsafe_edit(Edit::deletion(
|
||||
TextSize::from(94),
|
||||
@@ -2635,6 +2657,25 @@ if call(foo
|
||||
}
|
||||
|
||||
/// Create Ruff-style diagnostics for testing the various output formats for a notebook.
|
||||
///
|
||||
/// The concatenated cells look like this:
|
||||
///
|
||||
/// ```python
|
||||
/// # cell 1
|
||||
/// import os
|
||||
/// # cell 2
|
||||
/// import math
|
||||
///
|
||||
/// print('hello world')
|
||||
/// # cell 3
|
||||
/// def foo():
|
||||
/// print()
|
||||
/// x = 1
|
||||
/// ```
|
||||
///
|
||||
/// The first diagnostic is on the unused `os` import with location cell 1, row 2, column 8
|
||||
/// (`cell 1:2:8`). The second diagnostic is the unused `math` import at `cell 2:2:8`, and the
|
||||
/// third diagnostic is an unfixable unused variable at `cell 3:4:5`.
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "This is currently only used for JSON but will be needed soon for other formats"
|
||||
@@ -2690,7 +2731,8 @@ if call(foo
|
||||
|
||||
let diagnostics = vec![
|
||||
env.builder("unused-import", Severity::Error, "`os` imported but unused")
|
||||
.primary("notebook.ipynb", "2:7", "2:9", "Remove unused import: `os`")
|
||||
.primary("notebook.ipynb", "2:7", "2:9", "")
|
||||
.help("Remove unused import: `os`")
|
||||
.secondary_code("F401")
|
||||
.fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(9),
|
||||
@@ -2703,12 +2745,8 @@ if call(foo
|
||||
Severity::Error,
|
||||
"`math` imported but unused",
|
||||
)
|
||||
.primary(
|
||||
"notebook.ipynb",
|
||||
"4:7",
|
||||
"4:11",
|
||||
"Remove unused import: `math`",
|
||||
)
|
||||
.primary("notebook.ipynb", "4:7", "4:11", "")
|
||||
.help("Remove unused import: `math`")
|
||||
.secondary_code("F401")
|
||||
.fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(28),
|
||||
@@ -2721,12 +2759,8 @@ if call(foo
|
||||
Severity::Error,
|
||||
"Local variable `x` is assigned to but never used",
|
||||
)
|
||||
.primary(
|
||||
"notebook.ipynb",
|
||||
"10:4",
|
||||
"10:5",
|
||||
"Remove assignment to unused variable `x`",
|
||||
)
|
||||
.primary("notebook.ipynb", "10:4", "10:5", "")
|
||||
.help("Remove assignment to unused variable `x`")
|
||||
.secondary_code("F841")
|
||||
.fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(94),
|
||||
|
||||
195
crates/ruff_db/src/diagnostic/render/concise.rs
Normal file
195
crates/ruff_db/src/diagnostic/render/concise.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use crate::diagnostic::{
|
||||
Diagnostic, DisplayDiagnosticConfig, Severity,
|
||||
stylesheet::{DiagnosticStylesheet, fmt_styled},
|
||||
};
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
pub(super) struct ConciseRenderer<'a> {
|
||||
resolver: &'a dyn FileResolver,
|
||||
config: &'a DisplayDiagnosticConfig,
|
||||
}
|
||||
|
||||
impl<'a> ConciseRenderer<'a> {
|
||||
pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self {
|
||||
Self { resolver, config }
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> std::fmt::Result {
|
||||
let stylesheet = if self.config.color {
|
||||
DiagnosticStylesheet::styled()
|
||||
} else {
|
||||
DiagnosticStylesheet::plain()
|
||||
};
|
||||
|
||||
let sep = fmt_styled(":", stylesheet.separator);
|
||||
for diag in diagnostics {
|
||||
if let Some(span) = diag.primary_span() {
|
||||
write!(
|
||||
f,
|
||||
"{path}",
|
||||
path = fmt_styled(
|
||||
span.file().relative_path(self.resolver).to_string_lossy(),
|
||||
stylesheet.emphasis
|
||||
)
|
||||
)?;
|
||||
if let Some(range) = span.range() {
|
||||
let diagnostic_source = span.file().diagnostic_source(self.resolver);
|
||||
let start = diagnostic_source
|
||||
.as_source_code()
|
||||
.line_column(range.start());
|
||||
|
||||
if let Some(notebook_index) = self.resolver.notebook_index(span.file()) {
|
||||
write!(
|
||||
f,
|
||||
"{sep}cell {cell}{sep}{line}{sep}{col}",
|
||||
cell = notebook_index.cell(start.line).unwrap_or_default(),
|
||||
line = notebook_index.cell_row(start.line).unwrap_or_default(),
|
||||
col = start.column,
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{sep}{line}{sep}{col}",
|
||||
line = start.line,
|
||||
col = start.column,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
write!(f, "{sep} ")?;
|
||||
}
|
||||
if self.config.hide_severity {
|
||||
if let Some(code) = diag.secondary_code() {
|
||||
write!(
|
||||
f,
|
||||
"{code} ",
|
||||
code = fmt_styled(code, stylesheet.secondary_code)
|
||||
)?;
|
||||
}
|
||||
if self.config.show_fix_status {
|
||||
if let Some(fix) = diag.fix() {
|
||||
// Do not display an indicator for inapplicable fixes
|
||||
if fix.applies(self.config.fix_applicability) {
|
||||
write!(f, "[{fix}] ", fix = fmt_styled("*", stylesheet.separator))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (severity, severity_style) = match diag.severity() {
|
||||
Severity::Info => ("info", stylesheet.info),
|
||||
Severity::Warning => ("warning", stylesheet.warning),
|
||||
Severity::Error => ("error", stylesheet.error),
|
||||
Severity::Fatal => ("fatal", stylesheet.error),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{severity}[{id}] ",
|
||||
severity = fmt_styled(severity, severity_style),
|
||||
id = fmt_styled(diag.id(), stylesheet.emphasis)
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(f, "{message}", message = diag.concise_message())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_diagnostics::Applicability;
|
||||
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{
|
||||
TestEnvironment, create_diagnostics, create_notebook_diagnostics,
|
||||
create_syntax_error_diagnostics,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Concise);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
fib.py:1:8: error[unused-import] `os` imported but unused
|
||||
fib.py:6:5: error[unused-variable] Local variable `x` is assigned to but never used
|
||||
undef.py:1:4: error[undefined-name] Undefined name `a`
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_fixes() {
|
||||
let (mut env, diagnostics) = create_diagnostics(DiagnosticFormat::Concise);
|
||||
env.hide_severity(true);
|
||||
env.show_fix_status(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
fib.py:1:8: F401 [*] `os` imported but unused
|
||||
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
undef.py:1:4: F821 Undefined name `a`
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_fixes_preview() {
|
||||
let (mut env, diagnostics) = create_diagnostics(DiagnosticFormat::Concise);
|
||||
env.hide_severity(true);
|
||||
env.show_fix_status(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
env.preview(true);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
fib.py:1:8: F401 [*] `os` imported but unused
|
||||
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
undef.py:1:4: F821 Undefined name `a`
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_fixes_syntax_errors() {
|
||||
let (mut env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Concise);
|
||||
env.hide_severity(true);
|
||||
env.show_fix_status(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
syntax_errors.py:1:15: SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3:12: SyntaxError: Expected ')', found newline
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Concise);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
syntax_errors.py:1:15: error[invalid-syntax] SyntaxError: Expected one or more symbol names after import
|
||||
syntax_errors.py:3:12: error[invalid-syntax] SyntaxError: Expected ')', found newline
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let (env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Concise);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
notebook.ipynb:cell 1:2:8: error[unused-import] `os` imported but unused
|
||||
notebook.ipynb:cell 2:2:8: error[unused-import] `math` imported but unused
|
||||
notebook.ipynb:cell 3:4:5: error[unused-variable] Local variable `x` is assigned to but never used
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_file() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.format(DiagnosticFormat::Concise);
|
||||
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@"error[test-diagnostic] main diagnostic message",
|
||||
);
|
||||
}
|
||||
}
|
||||
66
crates/ruff_db/src/diagnostic/render/full.rs
Normal file
66
crates/ruff_db/src/diagnostic/render/full.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Full);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r#"
|
||||
error[unused-import]: `os` imported but unused
|
||||
--> fib.py:1:8
|
||||
|
|
||||
1 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
error[unused-variable]: Local variable `x` is assigned to but never used
|
||||
--> fib.py:6:5
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
error[undefined-name]: Undefined name `a`
|
||||
--> undef.py:1:4
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Full);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[invalid-syntax]: SyntaxError: Expected one or more symbol names after import
|
||||
--> syntax_errors.py:1:15
|
||||
|
|
||||
1 | from os import
|
||||
| ^
|
||||
2 |
|
||||
3 | if call(foo
|
||||
|
|
||||
|
||||
error[invalid-syntax]: SyntaxError: Expected ')', found newline
|
||||
--> syntax_errors.py:3:12
|
||||
|
|
||||
1 | from os import
|
||||
2 |
|
||||
3 | if call(foo
|
||||
| ^
|
||||
4 | def bar():
|
||||
5 | pass
|
||||
|
|
||||
");
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
|
||||
let fix = diagnostic.fix().map(|fix| JsonFix {
|
||||
applicability: fix.applicability(),
|
||||
message: diagnostic.suggestion(),
|
||||
message: diagnostic.first_help_text(),
|
||||
edits: ExpandedEdits {
|
||||
edits: fix.edits(),
|
||||
notebook_index,
|
||||
@@ -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,
|
||||
|
||||
195
crates/ruff_db/src/diagnostic/render/junit.rs
Normal file
195
crates/ruff_db/src/diagnostic/render/junit.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use std::{collections::BTreeMap, ops::Deref, path::Path};
|
||||
|
||||
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, XmlString};
|
||||
|
||||
use ruff_source_file::LineColumn;
|
||||
|
||||
use crate::diagnostic::{Diagnostic, SecondaryCode, render::FileResolver};
|
||||
|
||||
/// A renderer for diagnostics in the [JUnit] format.
|
||||
///
|
||||
/// See [`junit.xsd`] for the specification in the JUnit repository and an annotated [version]
|
||||
/// linked from the [`quick_junit`] docs.
|
||||
///
|
||||
/// [JUnit]: https://junit.org/
|
||||
/// [`junit.xsd`]: https://github.com/junit-team/junit-framework/blob/2870b7d8fd5bf7c1efe489d3991d3ed3900e82bb/platform-tests/src/test/resources/jenkins-junit.xsd
|
||||
/// [version]: https://llg.cubic.org/docs/junit/
|
||||
/// [`quick_junit`]: https://docs.rs/quick-junit/latest/quick_junit/
|
||||
pub struct JunitRenderer<'a> {
|
||||
resolver: &'a dyn FileResolver,
|
||||
}
|
||||
|
||||
impl<'a> JunitRenderer<'a> {
|
||||
pub fn new(resolver: &'a dyn FileResolver) -> Self {
|
||||
Self { resolver }
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
diagnostics: &[Diagnostic],
|
||||
) -> std::fmt::Result {
|
||||
let mut report = Report::new("ruff");
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
let mut test_suite = TestSuite::new("ruff");
|
||||
test_suite
|
||||
.extra
|
||||
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
|
||||
let mut case = TestCase::new("No errors found", TestCaseStatus::success());
|
||||
case.set_classname("ruff");
|
||||
test_suite.add_test_case(case);
|
||||
report.add_test_suite(test_suite);
|
||||
} else {
|
||||
for (filename, diagnostics) in group_diagnostics_by_filename(diagnostics, self.resolver)
|
||||
{
|
||||
let mut test_suite = TestSuite::new(filename);
|
||||
test_suite
|
||||
.extra
|
||||
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
|
||||
|
||||
let classname = Path::new(filename).with_extension("");
|
||||
|
||||
for diagnostic in diagnostics {
|
||||
let DiagnosticWithLocation {
|
||||
diagnostic,
|
||||
start_location: location,
|
||||
} = diagnostic;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(diagnostic.body());
|
||||
|
||||
if let Some(location) = location {
|
||||
status.set_description(format!(
|
||||
"line {row}, col {col}, {body}",
|
||||
row = location.line,
|
||||
col = location.column,
|
||||
body = diagnostic.body()
|
||||
));
|
||||
} else {
|
||||
status.set_description(diagnostic.body());
|
||||
}
|
||||
|
||||
let code = diagnostic
|
||||
.secondary_code()
|
||||
.map_or_else(|| diagnostic.name(), SecondaryCode::as_str);
|
||||
let mut case = TestCase::new(format!("org.ruff.{code}"), status);
|
||||
case.set_classname(classname.to_str().unwrap());
|
||||
|
||||
if let Some(location) = location {
|
||||
case.extra.insert(
|
||||
XmlString::new("line"),
|
||||
XmlString::new(location.line.to_string()),
|
||||
);
|
||||
case.extra.insert(
|
||||
XmlString::new("column"),
|
||||
XmlString::new(location.column.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
test_suite.add_test_case(case);
|
||||
}
|
||||
report.add_test_suite(test_suite);
|
||||
}
|
||||
}
|
||||
|
||||
let adapter = FmtAdapter { fmt: f };
|
||||
report.serialize(adapter).map_err(|_| std::fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(brent) this and `group_diagnostics_by_filename` are also used by the `grouped` output
|
||||
// format. I think they'd make more sense in that file, but I started here first. I'll move them to
|
||||
// that module when adding the `grouped` output format.
|
||||
struct DiagnosticWithLocation<'a> {
|
||||
diagnostic: &'a Diagnostic,
|
||||
start_location: Option<LineColumn>,
|
||||
}
|
||||
|
||||
impl Deref for DiagnosticWithLocation<'_> {
|
||||
type Target = Diagnostic;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.diagnostic
|
||||
}
|
||||
}
|
||||
|
||||
fn group_diagnostics_by_filename<'a>(
|
||||
diagnostics: &'a [Diagnostic],
|
||||
resolver: &'a dyn FileResolver,
|
||||
) -> BTreeMap<&'a str, Vec<DiagnosticWithLocation<'a>>> {
|
||||
let mut grouped_diagnostics = BTreeMap::default();
|
||||
for diagnostic in diagnostics {
|
||||
let (filename, start_location) = diagnostic
|
||||
.primary_span_ref()
|
||||
.map(|span| {
|
||||
let file = span.file();
|
||||
let start_location =
|
||||
span.range()
|
||||
.filter(|_| !resolver.is_notebook(file))
|
||||
.map(|range| {
|
||||
file.diagnostic_source(resolver)
|
||||
.as_source_code()
|
||||
.line_column(range.start())
|
||||
});
|
||||
|
||||
(span.file().path(resolver), start_location)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
grouped_diagnostics
|
||||
.entry(filename)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(DiagnosticWithLocation {
|
||||
diagnostic,
|
||||
start_location,
|
||||
});
|
||||
}
|
||||
grouped_diagnostics
|
||||
}
|
||||
|
||||
struct FmtAdapter<'a> {
|
||||
fmt: &'a mut dyn std::fmt::Write,
|
||||
}
|
||||
|
||||
impl std::io::Write for FmtAdapter<'_> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.fmt
|
||||
.write_str(std::str::from_utf8(buf).map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Invalid UTF-8 in JUnit report",
|
||||
)
|
||||
})?)
|
||||
.map_err(std::io::Error::other)?;
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> {
|
||||
self.fmt.write_fmt(args).map_err(std::io::Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat,
|
||||
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let (env, diagnostics) = create_diagnostics(DiagnosticFormat::Junit);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let (env, diagnostics) = create_syntax_error_diagnostics(DiagnosticFormat::Junit);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
}
|
||||
}
|
||||
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/junit.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
source: crates/ruff_db/src/diagnostic/render/junit.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="ruff" tests="3" failures="3" errors="0">
|
||||
@@ -1,15 +1,14 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/junit.rs
|
||||
expression: content
|
||||
snapshot_kind: text
|
||||
source: crates/ruff_db/src/diagnostic/render/junit.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="ruff" tests="2" failures="2" errors="0">
|
||||
<testsuite name="syntax_errors.py" tests="2" disabled="0" errors="0" failures="2" package="org.ruff">
|
||||
<testcase name="org.ruff" classname="syntax_errors" line="1" column="15">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="1" column="15">
|
||||
<failure message="SyntaxError: Expected one or more symbol names after import">line 1, col 15, SyntaxError: Expected one or more symbol names after import</failure>
|
||||
</testcase>
|
||||
<testcase name="org.ruff" classname="syntax_errors" line="3" column="12">
|
||||
<testcase name="org.ruff.invalid-syntax" classname="syntax_errors" line="3" column="12">
|
||||
<failure message="SyntaxError: Expected ')', found newline">line 3, col 12, SyntaxError: Expected ')', found newline</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
@@ -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"
|
||||
@@ -41,6 +41,8 @@ pub struct DiagnosticStylesheet {
|
||||
pub(crate) line_no: Style,
|
||||
pub(crate) emphasis: Style,
|
||||
pub(crate) none: Style,
|
||||
pub(crate) separator: Style,
|
||||
pub(crate) secondary_code: Style,
|
||||
}
|
||||
|
||||
impl Default for DiagnosticStylesheet {
|
||||
@@ -62,6 +64,8 @@ impl DiagnosticStylesheet {
|
||||
line_no: bright_blue.effects(Effects::BOLD),
|
||||
emphasis: Style::new().effects(Effects::BOLD),
|
||||
none: Style::new(),
|
||||
separator: AnsiColor::Cyan.on_default(),
|
||||
secondary_code: AnsiColor::Red.on_default().effects(Effects::BOLD),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +79,8 @@ impl DiagnosticStylesheet {
|
||||
line_no: Style::new(),
|
||||
emphasis: Style::new(),
|
||||
none: Style::new(),
|
||||
separator: Style::new(),
|
||||
secondary_code: Style::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use countme::Count;
|
||||
use dashmap::mapref::entry::Entry;
|
||||
pub use file_root::{FileRoot, FileRootKind};
|
||||
pub use path::FilePath;
|
||||
@@ -232,7 +231,7 @@ impl Files {
|
||||
let roots = inner.roots.read().unwrap();
|
||||
|
||||
for root in roots.all() {
|
||||
if root.path(db).starts_with(&path) {
|
||||
if path.starts_with(root.path(db)) {
|
||||
root.set_revision(db).to(FileRevision::now());
|
||||
}
|
||||
}
|
||||
@@ -312,11 +311,6 @@ pub struct File {
|
||||
/// the file has been deleted is to change the status to `Deleted`.
|
||||
#[default]
|
||||
status: FileStatus,
|
||||
|
||||
/// Counter that counts the number of created file instances and active file instances.
|
||||
/// Only enabled in debug builds.
|
||||
#[default]
|
||||
count: Count<File>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
@@ -375,12 +369,25 @@ impl File {
|
||||
}
|
||||
|
||||
/// Refreshes the file metadata by querying the file system if needed.
|
||||
///
|
||||
/// This also "touches" the file root associated with the given path.
|
||||
/// This means that any Salsa queries that depend on the corresponding
|
||||
/// root's revision will become invalidated.
|
||||
pub fn sync_path(db: &mut dyn Db, path: &SystemPath) {
|
||||
let absolute = SystemPath::absolute(path, db.system().current_directory());
|
||||
Files::touch_root(db, &absolute);
|
||||
Self::sync_system_path(db, &absolute, None);
|
||||
}
|
||||
|
||||
/// Refreshes *only* the file metadata by querying the file system if needed.
|
||||
///
|
||||
/// This specifically does not touch any file root associated with the
|
||||
/// given file path.
|
||||
pub fn sync_path_only(db: &mut dyn Db, path: &SystemPath) {
|
||||
let absolute = SystemPath::absolute(path, db.system().current_directory());
|
||||
Self::sync_system_path(db, &absolute, None);
|
||||
}
|
||||
|
||||
/// Increments the revision for the virtual file at `path`.
|
||||
pub fn sync_virtual_path(db: &mut dyn Db, path: &SystemVirtualPath) {
|
||||
if let Some(virtual_file) = db.files().try_virtual_file(path) {
|
||||
@@ -486,7 +493,7 @@ impl fmt::Debug for File {
|
||||
///
|
||||
/// This is a wrapper around a [`File`] that provides additional methods to interact with a virtual
|
||||
/// file.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct VirtualFile(File);
|
||||
|
||||
impl VirtualFile {
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct FileRoot {
|
||||
pub path: SystemPathBuf,
|
||||
|
||||
/// The kind of the root at the time of its creation.
|
||||
kind_at_time_of_creation: FileRootKind,
|
||||
pub kind_at_time_of_creation: FileRootKind,
|
||||
|
||||
/// A revision that changes when the contents of the source root change.
|
||||
///
|
||||
|
||||
@@ -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,8 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use countme::Count;
|
||||
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_source_file::LineIndex;
|
||||
@@ -38,11 +36,7 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
};
|
||||
|
||||
SourceText {
|
||||
inner: Arc::new(SourceTextInner {
|
||||
kind,
|
||||
read_error,
|
||||
count: Count::new(),
|
||||
}),
|
||||
inner: Arc::new(SourceTextInner { kind, read_error }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +119,6 @@ impl std::fmt::Debug for SourceText {
|
||||
|
||||
#[derive(Eq, PartialEq, get_size2::GetSize)]
|
||||
struct SourceTextInner {
|
||||
#[get_size(ignore)]
|
||||
count: Count<SourceText>,
|
||||
kind: SourceTextKind,
|
||||
read_error: Option<SourceTextError>,
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl SourceDb for ModuleDb {
|
||||
|
||||
#[salsa::db]
|
||||
impl Db for ModuleDb {
|
||||
fn is_file_open(&self, file: File) -> bool {
|
||||
fn should_check_file(&self, file: File) -> bool {
|
||||
!file.path(self).is_vendored_path()
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ impl<'a> Resolver<'a> {
|
||||
match import {
|
||||
CollectedImport::Import(import) => {
|
||||
let module = resolve_module(self.db, &import)?;
|
||||
Some(module.file()?.path(self.db))
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
CollectedImport::ImportFrom(import) => {
|
||||
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
|
||||
@@ -32,7 +32,7 @@ impl<'a> Resolver<'a> {
|
||||
resolve_module(self.db, &parent?)
|
||||
})?;
|
||||
|
||||
Some(module.file()?.path(self.db))
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.3"
|
||||
version = "0.12.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -15,7 +15,7 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_annotate_snippets = { workspace = true }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, features = ["serde"] }
|
||||
ruff_db = { workspace = true, features = ["junit", "serde"] }
|
||||
ruff_diagnostics = { workspace = true, features = ["serde"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
@@ -55,7 +55,6 @@ path-absolutize = { workspace = true, features = [
|
||||
pathdiff = { workspace = true }
|
||||
pep440_rs = { workspace = true }
|
||||
pyproject-toml = { workspace = true }
|
||||
quick-junit = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
|
||||
@@ -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__}")
|
||||
|
||||
@@ -142,3 +142,7 @@ field47: typing.Optional[int] | typing.Optional[dict]
|
||||
# avoid reporting twice
|
||||
field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
|
||||
field49: typing.Optional[complex | complex] | complex
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/19403
|
||||
# Should throw duplicate union member but not fix
|
||||
isinstance(None, typing.Union[None, None])
|
||||
@@ -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
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001-3_future.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001-3_future.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from collections import Counter
|
||||
|
||||
from elsewhere import third_party
|
||||
|
||||
from . import first_party
|
||||
|
||||
|
||||
def f(x: first_party.foo): ...
|
||||
def g(x: third_party.bar): ...
|
||||
def h(x: Counter): ...
|
||||
68
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future.py
vendored
Normal file
68
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future.py
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
def f():
|
||||
from . import first_party
|
||||
|
||||
def f(x: first_party.foo): ...
|
||||
|
||||
|
||||
# Type parameter bounds
|
||||
def g():
|
||||
from . import foo
|
||||
|
||||
class C[T: foo.Ty]: ...
|
||||
|
||||
|
||||
def h():
|
||||
from . import foo
|
||||
|
||||
def f[T: foo.Ty](x: T): ...
|
||||
|
||||
|
||||
def i():
|
||||
from . import foo
|
||||
|
||||
type Alias[T: foo.Ty] = list[T]
|
||||
|
||||
|
||||
# Type parameter defaults
|
||||
def j():
|
||||
from . import foo
|
||||
|
||||
class C[T = foo.Ty]: ...
|
||||
|
||||
|
||||
def k():
|
||||
from . import foo
|
||||
|
||||
def f[T = foo.Ty](x: T): ...
|
||||
|
||||
|
||||
def l():
|
||||
from . import foo
|
||||
|
||||
type Alias[T = foo.Ty] = list[T]
|
||||
|
||||
|
||||
# non-generic type alias
|
||||
def m():
|
||||
from . import foo
|
||||
|
||||
type Alias = foo.Ty
|
||||
|
||||
|
||||
# unions
|
||||
from typing import Union
|
||||
|
||||
|
||||
def n():
|
||||
from . import foo
|
||||
|
||||
def f(x: Union[foo.Ty, int]): ...
|
||||
def g(x: foo.Ty | int): ...
|
||||
|
||||
|
||||
# runtime and typing usage
|
||||
def o():
|
||||
from . import foo
|
||||
|
||||
def f(x: foo.Ty):
|
||||
return foo.Ty()
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future_present.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future_present.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from . import first_party
|
||||
|
||||
|
||||
def f(x: first_party.foo): ...
|
||||
@@ -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)
|
||||
|
||||
@@ -54,6 +54,13 @@ windows_path.with_suffix(r"s")
|
||||
windows_path.with_suffix(u'' "json")
|
||||
windows_path.with_suffix(suffix="js")
|
||||
|
||||
Path().with_suffix(".")
|
||||
Path().with_suffix("py")
|
||||
PosixPath().with_suffix("py")
|
||||
PurePath().with_suffix("py")
|
||||
PurePosixPath().with_suffix("py")
|
||||
PureWindowsPath().with_suffix("py")
|
||||
WindowsPath().with_suffix("py")
|
||||
|
||||
### No errors
|
||||
path.with_suffix()
|
||||
|
||||
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(".")
|
||||
@@ -104,3 +104,6 @@ os.chmod(x)
|
||||
os.replace("src", "dst", src_dir_fd=1, dst_dir_fd=2)
|
||||
os.replace("src", "dst", src_dir_fd=1)
|
||||
os.replace("src", "dst", dst_dir_fd=2)
|
||||
|
||||
os.getcwd()
|
||||
os.getcwdb()
|
||||
@@ -47,3 +47,19 @@ def _():
|
||||
from builtin import open
|
||||
|
||||
with open(p) as _: ... # No error
|
||||
|
||||
file = "file_1.py"
|
||||
|
||||
rename(file, "file_2.py")
|
||||
|
||||
rename(
|
||||
# commment 1
|
||||
file, # comment 2
|
||||
"file_2.py"
|
||||
,
|
||||
# comment 3
|
||||
)
|
||||
|
||||
rename(file, "file_2.py", src_dir_fd=None, dst_dir_fd=None)
|
||||
|
||||
rename(file, "file_2.py", src_dir_fd=1)
|
||||
5
crates/ruff_linter/resources/test/fixtures/isort/required_imports/whitespace.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/isort/required_imports/whitespace.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# This is a regression test for https://github.com/astral-sh/ruff/issues/19310
|
||||
# there is a (potentially invisible) unicode formfeed character (000C) between "docstring" and the semicolon
|
||||
"docstring"; print(
|
||||
f"{__doc__=}",
|
||||
)
|
||||
@@ -48,6 +48,61 @@ 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
|
||||
|
||||
|
||||
from http.server import CGIHTTPRequestHandler
|
||||
|
||||
|
||||
class MyCGIRequestHandler(CGIHTTPRequestHandler):
|
||||
def do_OPTIONS(self):
|
||||
pass
|
||||
|
||||
def dont_OPTIONS(self):
|
||||
pass
|
||||
|
||||
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
|
||||
class MySimpleRequestHandler(SimpleHTTPRequestHandler):
|
||||
def do_OPTIONS(self):
|
||||
pass
|
||||
|
||||
def dont_OPTIONS(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -278,3 +278,15 @@ def f():
|
||||
for i in src:
|
||||
if lambda: 0:
|
||||
dst.append(i)
|
||||
|
||||
def f():
|
||||
i = "xyz"
|
||||
result = []
|
||||
for i in range(3):
|
||||
result.append(x for x in [i])
|
||||
|
||||
def f():
|
||||
i = "xyz"
|
||||
result = []
|
||||
for i in range(3):
|
||||
result.append((x for x in [i]))
|
||||
@@ -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}"
|
||||
|
||||
5
crates/ruff_linter/resources/test/fixtures/pylint/empty_comment_line_continuation.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pylint/empty_comment_line_continuation.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
x = 0 \
|
||||
#
|
||||
+1
|
||||
print(x)
|
||||
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
|
||||
|
||||
|
||||
@@ -91,9 +91,16 @@ Path("foo.txt").write_text(text, encoding="utf-8")
|
||||
Path("foo.txt").write_text(text, *args)
|
||||
Path("foo.txt").write_text(text, **kwargs)
|
||||
|
||||
# Violation but not detectable
|
||||
# https://github.com/astral-sh/ruff/issues/19294
|
||||
x = Path("foo.txt")
|
||||
x.open()
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18107
|
||||
codecs.open("plw1514.py", "r", "utf-8").close() # this is fine
|
||||
|
||||
# function argument annotated as Path
|
||||
from pathlib import Path
|
||||
|
||||
def format_file(file: Path):
|
||||
with file.open() as f:
|
||||
contents = f.read()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -27,7 +27,24 @@ _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
_ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
_ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
|
||||
# OK
|
||||
# Cases with keyword arguments - should produce unsafe fixes
|
||||
_ = Fraction.from_decimal(dec=Decimal("4.2"))
|
||||
_ = Decimal.from_float(f=4.2)
|
||||
|
||||
# Cases with invalid argument counts - should not get fixes
|
||||
_ = Fraction.from_decimal(Decimal("4.2"), 1)
|
||||
_ = Decimal.from_float(4.2, None)
|
||||
|
||||
# Cases with wrong keyword arguments - should not get fixes
|
||||
_ = Fraction.from_decimal(numerator=Decimal("4.2"))
|
||||
_ = Decimal.from_float(value=4.2)
|
||||
|
||||
# Cases with type validation issues - should produce unsafe fixes
|
||||
_ = Decimal.from_float("4.2") # Invalid type for from_float
|
||||
_ = Fraction.from_decimal(4.2) # Invalid type for from_decimal
|
||||
_ = Fraction.from_float("4.2") # Invalid type for from_float
|
||||
|
||||
# OK - should not trigger the rule
|
||||
_ = Fraction(0.1)
|
||||
_ = Fraction(-0.5)
|
||||
_ = Fraction(5.0)
|
||||
|
||||
@@ -79,3 +79,8 @@ def in_type_def():
|
||||
from typing import cast
|
||||
a = 'int'
|
||||
cast('f"{a}"','11')
|
||||
|
||||
# Regression test for parser bug
|
||||
# https://github.com/astral-sh/ruff/issues/18860
|
||||
def fuzz_bug():
|
||||
c('{\t"i}')
|
||||
|
||||
24
crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py
vendored
Normal file
24
crates/ruff_linter/resources/test/fixtures/ruff/RUF055_3.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import re
|
||||
|
||||
b_src = b"abc"
|
||||
|
||||
# Should be replaced with `b_src.replace(rb"x", b"y")`
|
||||
re.sub(rb"x", b"y", b_src)
|
||||
|
||||
# Should be replaced with `b_src.startswith(rb"abc")`
|
||||
if re.match(rb"abc", b_src):
|
||||
pass
|
||||
|
||||
# Should be replaced with `rb"x" in b_src`
|
||||
if re.search(rb"x", b_src):
|
||||
pass
|
||||
|
||||
# Should be replaced with `b_src.split(rb"abc")`
|
||||
re.split(rb"abc", b_src)
|
||||
|
||||
# Patterns containing metacharacters should NOT be replaced
|
||||
re.sub(rb"ab[c]", b"", b_src)
|
||||
re.match(rb"ab[c]", b_src)
|
||||
re.search(rb"ab[c]", b_src)
|
||||
re.fullmatch(rb"ab[c]", b_src)
|
||||
re.split(rb"ab[c]", b_src)
|
||||
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)
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
|
||||
flake8_type_checking::helpers::is_valid_runtime_import(
|
||||
binding,
|
||||
&checker.semantic,
|
||||
&checker.settings().flake8_type_checking,
|
||||
checker.settings(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -1039,15 +1039,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
|
||||
}
|
||||
if checker.any_rule_enabled(&[
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::OsReplace,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PyPath,
|
||||
@@ -1110,6 +1105,21 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::OsReadlink) {
|
||||
flake8_use_pathlib::rules::os_readlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsGetcwd) {
|
||||
flake8_use_pathlib::rules::os_getcwd(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsChmod) {
|
||||
flake8_use_pathlib::rules::os_chmod(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRename) {
|
||||
flake8_use_pathlib::rules::os_rename(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsReplace) {
|
||||
flake8_use_pathlib::rules::os_replace(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathSamefile) {
|
||||
flake8_use_pathlib::rules::os_path_samefile(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(
|
||||
checker, call, segments,
|
||||
|
||||
@@ -2770,11 +2770,10 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_typing_only_annotation() {
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, annotation, range);
|
||||
}
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, annotation, range);
|
||||
}
|
||||
|
||||
if self.source_type.is_stub() {
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotationInStub) {
|
||||
flake8_pyi::rules::quoted_annotation_in_stub(
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::EmptyComment) {
|
||||
pylint::rules::empty_comments(context, comment_ranges, locator);
|
||||
pylint::rules::empty_comments(context, comment_ranges, locator, indexer);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::AmbiguousUnicodeCharacterComment) {
|
||||
|
||||
@@ -920,15 +920,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// flake8-use-pathlib
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
|
||||
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsChmod),
|
||||
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsChmod),
|
||||
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
|
||||
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
|
||||
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRename),
|
||||
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReplace),
|
||||
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRename),
|
||||
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReplace),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsUnlink),
|
||||
(Flake8UsePathlib, "109") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsGetcwd),
|
||||
(Flake8UsePathlib, "109") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsGetcwd),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsdir),
|
||||
@@ -940,7 +940,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8UsePathlib, "118") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathJoin),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathDirname),
|
||||
(Flake8UsePathlib, "121") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSamefile),
|
||||
(Flake8UsePathlib, "121") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathSamefile),
|
||||
(Flake8UsePathlib, "122") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSplitext),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::BuiltinOpen),
|
||||
(Flake8UsePathlib, "124") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::PyPath),
|
||||
|
||||
@@ -288,7 +288,7 @@ fn match_docstring_end(body: &[Stmt]) -> Option<TextSize> {
|
||||
fn match_semicolon(s: &str) -> Option<TextSize> {
|
||||
for (offset, c) in s.char_indices() {
|
||||
match c {
|
||||
' ' | '\t' => continue,
|
||||
_ if is_python_whitespace(c) => continue,
|
||||
';' => return Some(TextSize::try_from(offset).unwrap()),
|
||||
_ => break,
|
||||
}
|
||||
|
||||
@@ -527,6 +527,17 @@ impl<'a> Importer<'a> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a `from __future__ import annotations` import.
|
||||
pub(crate) fn add_future_import(&self) -> Edit {
|
||||
let import = &NameImport::ImportFrom(MemberNameImport::member(
|
||||
"__future__".to_string(),
|
||||
"annotations".to_string(),
|
||||
));
|
||||
// Note that `TextSize::default` should ensure that the import is added at the very
|
||||
// beginning of the file via `Insertion::start_of_file`.
|
||||
self.add_import(import, TextSize::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// An edit to the top-level of a module, making it available at runtime.
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, XmlString};
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::LineColumn;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, MessageWithLocation, group_diagnostics_by_filename};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JunitEmitter;
|
||||
|
||||
impl Emitter for JunitEmitter {
|
||||
fn emit(
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
diagnostics: &[Diagnostic],
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut report = Report::new("ruff");
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
let mut test_suite = TestSuite::new("ruff");
|
||||
test_suite
|
||||
.extra
|
||||
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
|
||||
let mut case = TestCase::new("No errors found", TestCaseStatus::success());
|
||||
case.set_classname("ruff");
|
||||
test_suite.add_test_case(case);
|
||||
report.add_test_suite(test_suite);
|
||||
} else {
|
||||
for (filename, messages) in group_diagnostics_by_filename(diagnostics) {
|
||||
let mut test_suite = TestSuite::new(&filename);
|
||||
test_suite
|
||||
.extra
|
||||
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
|
||||
|
||||
for message in messages {
|
||||
let MessageWithLocation {
|
||||
message,
|
||||
start_location,
|
||||
} = message;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(message.body());
|
||||
let location = if context.is_notebook(&message.expect_ruff_filename()) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
// so we show one that's clearly a fallback
|
||||
LineColumn::default()
|
||||
} else {
|
||||
start_location
|
||||
};
|
||||
|
||||
status.set_description(format!(
|
||||
"line {row}, col {col}, {body}",
|
||||
row = location.line,
|
||||
col = location.column,
|
||||
body = message.body()
|
||||
));
|
||||
let mut case = TestCase::new(
|
||||
if let Some(code) = message.secondary_code() {
|
||||
format!("org.ruff.{code}")
|
||||
} else {
|
||||
"org.ruff".to_string()
|
||||
},
|
||||
status,
|
||||
);
|
||||
let file_path = Path::new(&*filename);
|
||||
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
|
||||
let classname = file_path.parent().unwrap().join(file_stem);
|
||||
case.set_classname(classname.to_str().unwrap());
|
||||
case.extra.insert(
|
||||
XmlString::new("line"),
|
||||
XmlString::new(location.line.to_string()),
|
||||
);
|
||||
case.extra.insert(
|
||||
XmlString::new("column"),
|
||||
XmlString::new(location.column.to_string()),
|
||||
);
|
||||
|
||||
test_suite.add_test_case(case);
|
||||
}
|
||||
report.add_test_suite(test_suite);
|
||||
}
|
||||
}
|
||||
|
||||
report.serialize(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::JunitEmitter;
|
||||
use crate::message::tests::{
|
||||
capture_emitter_output, create_diagnostics, create_syntax_error_diagnostics,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
let mut emitter = JunitEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_errors() {
|
||||
let mut emitter = JunitEmitter;
|
||||
let content = capture_emitter_output(&mut emitter, &create_syntax_error_diagnostics());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,6 @@ use ruff_db::files::File;
|
||||
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};
|
||||
@@ -30,9 +27,6 @@ mod diff;
|
||||
mod github;
|
||||
mod gitlab;
|
||||
mod grouped;
|
||||
mod junit;
|
||||
mod pylint;
|
||||
mod rdjson;
|
||||
mod sarif;
|
||||
mod text;
|
||||
|
||||
@@ -80,6 +74,14 @@ where
|
||||
body,
|
||||
);
|
||||
|
||||
let span = Span::from(file).with_range(range);
|
||||
let annotation = Annotation::primary(span);
|
||||
diagnostic.annotate(annotation);
|
||||
|
||||
if let Some(suggestion) = suggestion {
|
||||
diagnostic.help(suggestion);
|
||||
}
|
||||
|
||||
if let Some(fix) = fix {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
@@ -92,13 +94,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 +125,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
|
||||
@@ -6,13 +6,12 @@ use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use ruff_annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, SecondaryCode};
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed};
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
@@ -21,8 +20,6 @@ use crate::settings::types::UnsafeFixes;
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
struct EmitterFlags: u8 {
|
||||
/// Whether to show the fix status of a diagnostic.
|
||||
const SHOW_FIX_STATUS = 1 << 0;
|
||||
/// Whether to show the diff of a fix, for diagnostics that have a fix.
|
||||
const SHOW_FIX_DIFF = 1 << 1;
|
||||
/// Whether to show the source code of a diagnostic.
|
||||
@@ -30,17 +27,27 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextEmitter {
|
||||
flags: EmitterFlags,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
config: DisplayDiagnosticConfig,
|
||||
}
|
||||
|
||||
impl Default for TextEmitter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
flags: EmitterFlags::default(),
|
||||
config: DisplayDiagnosticConfig::default()
|
||||
.format(DiagnosticFormat::Concise)
|
||||
.hide_severity(true)
|
||||
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextEmitter {
|
||||
#[must_use]
|
||||
pub fn with_show_fix_status(mut self, show_fix_status: bool) -> Self {
|
||||
self.flags
|
||||
.set(EmitterFlags::SHOW_FIX_STATUS, show_fix_status);
|
||||
self.config = self.config.show_fix_status(show_fix_status);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -58,7 +65,15 @@ impl TextEmitter {
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
|
||||
self.unsafe_fixes = unsafe_fixes;
|
||||
self.config = self
|
||||
.config
|
||||
.fix_applicability(unsafe_fixes.required_applicability());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_preview(mut self, preview: bool) -> Self {
|
||||
self.config = self.config.preview(preview);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -71,51 +86,10 @@ impl Emitter for TextEmitter {
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for message in diagnostics {
|
||||
write!(writer, "{}", message.display(context, &self.config))?;
|
||||
|
||||
let filename = message.expect_ruff_filename();
|
||||
write!(
|
||||
writer,
|
||||
"{path}{sep}",
|
||||
path = relativize_path(&filename).bold(),
|
||||
sep = ":".cyan(),
|
||||
)?;
|
||||
|
||||
let start_location = message.expect_ruff_start_location();
|
||||
let notebook_index = context.notebook_index(&filename);
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(notebook_index) = notebook_index {
|
||||
write!(
|
||||
writer,
|
||||
"cell {cell}{sep}",
|
||||
cell = notebook_index
|
||||
.cell(start_location.line)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
sep = ":".cyan(),
|
||||
)?;
|
||||
|
||||
LineColumn {
|
||||
line: notebook_index
|
||||
.cell_row(start_location.line)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
column: start_location.column,
|
||||
}
|
||||
} else {
|
||||
start_location
|
||||
};
|
||||
|
||||
writeln!(
|
||||
writer,
|
||||
"{row}{sep}{col}{sep} {code_and_body}",
|
||||
row = diagnostic_location.line,
|
||||
col = diagnostic_location.column,
|
||||
sep = ":".cyan(),
|
||||
code_and_body = RuleCodeAndBody {
|
||||
message,
|
||||
show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS),
|
||||
unsafe_fixes: self.unsafe_fixes,
|
||||
}
|
||||
)?;
|
||||
|
||||
if self.flags.intersects(EmitterFlags::SHOW_SOURCE) {
|
||||
// The `0..0` range is used to highlight file-level diagnostics.
|
||||
if message.expect_range() != TextRange::default() {
|
||||
@@ -186,7 +160,7 @@ pub(super) struct MessageCodeFrame<'a> {
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let suggestion = self.message.suggestion();
|
||||
let suggestion = self.message.first_help_text();
|
||||
let footers = if let Some(suggestion) = suggestion {
|
||||
vec![Level::Help.title(suggestion)]
|
||||
} else {
|
||||
|
||||
@@ -134,6 +134,31 @@ pub(crate) const fn is_fix_os_path_dirname_enabled(settings: &LinterSettings) ->
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19404
|
||||
pub(crate) const fn is_fix_os_chmod_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19404
|
||||
pub(crate) const fn is_fix_os_rename_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19404
|
||||
pub(crate) const fn is_fix_os_replace_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19404
|
||||
pub(crate) const fn is_fix_os_path_samefile_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19245
|
||||
pub(crate) const fn is_fix_os_getcwd_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11436
|
||||
// https://github.com/astral-sh/ruff/pull/11168
|
||||
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
|
||||
@@ -195,3 +220,8 @@ pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
|
||||
pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19100
|
||||
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
|
||||
|
||||
@@ -2,8 +2,7 @@ use std::fmt;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::{MemberNameImport, NameImport};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{AlwaysFixableViolation, Fix};
|
||||
@@ -85,15 +84,7 @@ impl AlwaysFixableViolation for FutureRequiredTypeAnnotation {
|
||||
|
||||
/// FA102
|
||||
pub(crate) fn future_required_type_annotation(checker: &Checker, expr: &Expr, reason: Reason) {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range());
|
||||
let required_import = NameImport::ImportFrom(MemberNameImport::member(
|
||||
"__future__".to_string(),
|
||||
"annotations".to_string(),
|
||||
));
|
||||
diagnostic.set_fix(Fix::unsafe_edit(
|
||||
checker
|
||||
.importer()
|
||||
.add_import(&required_import, TextSize::default()),
|
||||
));
|
||||
checker
|
||||
.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range())
|
||||
.set_fix(Fix::unsafe_edit(checker.importer().add_future_import()));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use ruff_diagnostics::Fix;
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::{MemberNameImport, NameImport};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::AlwaysFixableViolation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{AlwaysFixableViolation, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing `from __future__ import annotations` imports upon
|
||||
@@ -95,15 +93,7 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
|
||||
|
||||
let Some(name) = name else { return };
|
||||
|
||||
let import = &NameImport::ImportFrom(MemberNameImport::member(
|
||||
"__future__".to_string(),
|
||||
"annotations".to_string(),
|
||||
));
|
||||
checker
|
||||
.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range())
|
||||
.set_fix(Fix::unsafe_edit(
|
||||
checker
|
||||
.importer()
|
||||
.add_import(import, ruff_text_size::TextSize::default()),
|
||||
));
|
||||
.set_fix(Fix::unsafe_edit(checker.importer().add_future_import()));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user