Compare commits

..

10 Commits

Author SHA1 Message Date
Brent Westbrook
fc513684d2 update handle_unary_op_comment docs 2025-11-14 17:42:47 -05:00
Brent Westbrook
ca5f4cc96f factor out has_parenthesis_between, add some docs 2025-11-14 15:50:33 -05:00
Brent Westbrook
e0b0b54b25 try a different approach 2025-11-13 10:09:37 -05:00
Brent Westbrook
9bee558c3a add more variations on issue test case 2025-11-13 09:37:15 -05:00
Brent Westbrook
f49fda317e Revert "try only dangling"
This reverts commit 4c8540ede798f544a895225b541f46552649fd7b.
2025-11-12 15:33:48 -05:00
Brent Westbrook
1cf5eff9f4 try only dangling 2025-11-12 15:33:48 -05:00
Brent Westbrook
ecccfd2a56 dangling operator comment
fix leading comments
2025-11-12 15:33:48 -05:00
Brent Westbrook
a303d1aa49 leading operand comment 2025-11-12 15:33:48 -05:00
Brent Westbrook
6eebc35eec add failing test 2025-11-12 15:33:48 -05:00
Brent Westbrook
923b4dd01e Fix panic when formatting comments in unary expressions
Summary
--

This is a second attempt at fixing #19226 based on the feedback in https://github.com/astral-sh/ruff/pull/20494#issuecomment-3467920065.

We currently mark the comment in an expression like this:

```py
if '' and (not #
0):
    pass
```

as a leading comment on `not`, eventually causing a panic in
`Operand::has_unparenthesized_leading_comments` because the end of the comment
is greater than the start of the expression.

a1d9cb5830/crates/ruff_python_formatter/src/expression/binary_like.rs (L843)

This PR fixes the issue by instead making such a comment a dangling comment on
the unary expression.

In the third commit, I instead tried making the comment a leading comment on the
operand, which also looks pretty reasonable to me. Making it a dangling comment
seems more in line with the docs on `handle_unary_op_comments`, though.

I also tried deleting the leading comment logic in favor of the new dangling
logic in the fifth commit before reverting in the sixth. This looks okay to me
too, but the current state of the PR seems like the least invasive fix.

Test Plan
--

A new, minimized test case based on the issue. I also checked that the original
snippet from the report works now.

Co-authored-by: Takayuki Maeda <takoyaki0316@gmail.com>
2025-11-12 15:31:49 -05:00
2269 changed files with 32693 additions and 325732 deletions

View File

@@ -5,4 +5,4 @@ rustup component add clippy rustfmt
cargo install cargo-insta
cargo fetch
pip install maturin prek
pip install maturin pre-commit

1
.gitattributes vendored
View File

@@ -22,7 +22,6 @@ crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
crates/ruff_python_parser/resources/inline linguist-generated=true
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
ty.schema.json -diff linguist-generated=true text=auto eol=lf

10
.github/CODEOWNERS vendored
View File

@@ -20,11 +20,9 @@
# ty
/crates/ty* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ruff_db/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_project/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/crates/ty_ide/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager @Gankra
/crates/ty_server/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/crates/ty_project/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_server/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_wasm/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/crates/ty_wasm/ @carljm @MichaReiser @sharkdp @dcreager
/scripts/ty_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ty_python_semantic/ @carljm @AlexWaygood @sharkdp @dcreager
/crates/ty_module_resolver/ @carljm @MichaReiser @AlexWaygood @Gankra
/crates/ty_python_semantic @carljm @AlexWaygood @sharkdp @dcreager

View File

@@ -1,20 +1,13 @@
# Configuration for the actionlint tool, which we run via prek
# Configuration for the actionlint tool, which we run via pre-commit
# to verify the correctness of the syntax in our GitHub Actions workflows.
self-hosted-runner:
# Various runners we use that aren't recognized out-of-the-box by actionlint:
labels:
- depot-ubuntu-24.04-4
- depot-ubuntu-latest-8
- depot-ubuntu-22.04-16
- depot-ubuntu-22.04-32
- depot-windows-2022-16
- depot-ubuntu-22.04-arm-4
- github-windows-2025-x86_64-8
- github-windows-2025-x86_64-16
- codspeed-macro
paths:
".github/workflows/mypy_primer.yaml":
ignore:
- 'constant expression "false" in condition. remove the if: section'

View File

@@ -4,6 +4,5 @@
# Enable off-by-default rules.
[rules]
possibly-unresolved-reference = "warn"
possibly-missing-import = "warn"
unused-ignore-comment = "warn"
division-by-zero = "warn"
unsupported-dynamic-base = "warn"

View File

@@ -2,11 +2,12 @@
$schema: "https://docs.renovatebot.com/renovate-schema.json",
dependencyDashboard: true,
suppressNotifications: ["prEditedNotification"],
extends: ["github>astral-sh/renovate-config"],
extends: ["config:recommended"],
labels: ["internal"],
schedule: ["before 4am on Monday"],
semanticCommits: "disabled",
separateMajorMinor: false,
prHourlyLimit: 10,
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "pip_requirements", "npm"],
cargo: {
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
@@ -15,7 +16,7 @@
pep621: {
// The default for this package manager is to only search for `pyproject.toml` files
// found at the repository root: https://docs.renovatebot.com/modules/manager/pep621/#file-matching
managerFilePatterns: ["^(python|scripts)/.*pyproject\\.toml$"],
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
},
pip_requirements: {
// The default for this package manager is to run on all requirements.txt files:
@@ -33,7 +34,7 @@
npm: {
// The default for this package manager is to only search for `package.json` files
// found at the repository root: https://docs.renovatebot.com/modules/manager/npm/#file-matching
managerFilePatterns: ["^playground/.*package\\.json$"],
fileMatch: ["^playground/.*package\\.json$"],
},
"pre-commit": {
enabled: true,
@@ -76,9 +77,17 @@
enabled: false,
},
{
groupName: "prek dependencies",
// `mkdocs-material` requires a manual update to keep the version in sync
// with `mkdocs-material-insider`.
// See: https://squidfunk.github.io/mkdocs-material/insiders/upgrade/
matchManagers: ["pip_requirements"],
matchPackageNames: ["mkdocs-material"],
enabled: false,
},
{
groupName: "pre-commit dependencies",
matchManagers: ["pre-commit"],
description: "Weekly update of prek dependencies",
description: "Weekly update of pre-commit dependencies",
},
{
groupName: "NPM Development dependencies",

View File

@@ -39,11 +39,11 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -51,7 +51,6 @@ jobs:
- name: "Build sdist"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
command: sdist
args: --out dist
- name: "Test sdist"
@@ -60,7 +59,7 @@ jobs:
"${MODULE_NAME}" --help
python -m "${MODULE_NAME}" --help
- name: "Upload sdist"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-sdist
path: dist
@@ -69,11 +68,11 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: macos-14
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -82,11 +81,10 @@ jobs:
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: x86_64
args: --release --locked --out dist
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-macos-x86_64
path: dist
@@ -101,7 +99,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-macos-x86_64
path: |
@@ -112,11 +110,11 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: macos-14
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: arm64
@@ -125,7 +123,6 @@ jobs:
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: aarch64
args: --release --locked --out dist
- name: "Test wheel - aarch64"
@@ -134,7 +131,7 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-aarch64-apple-darwin
path: dist
@@ -149,7 +146,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-aarch64-apple-darwin
path: |
@@ -169,11 +166,11 @@ jobs:
- target: aarch64-pc-windows-msvc
arch: x64
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.platform.arch }}
@@ -182,7 +179,6 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
args: --release --locked --out dist
env:
@@ -196,7 +192,7 @@ jobs:
"${MODULE_NAME}" --help
python -m "${MODULE_NAME}" --help
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-${{ matrix.platform.target }}
path: dist
@@ -207,7 +203,7 @@ jobs:
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-${{ matrix.platform.target }}
path: |
@@ -223,11 +219,11 @@ jobs:
- x86_64-unknown-linux-gnu
- i686-unknown-linux-gnu
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -236,7 +232,6 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.target }}
manylinux: auto
args: --release --locked --out dist
@@ -247,7 +242,7 @@ jobs:
"${MODULE_NAME}" --help
python -m "${MODULE_NAME}" --help
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-${{ matrix.target }}
path: dist
@@ -265,7 +260,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-${{ matrix.target }}
path: |
@@ -301,11 +296,11 @@ jobs:
arch: riscv64
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -313,7 +308,6 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
@@ -333,7 +327,7 @@ jobs:
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-${{ matrix.platform.target }}
path: dist
@@ -351,7 +345,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-${{ matrix.platform.target }}
path: |
@@ -367,11 +361,11 @@ jobs:
- x86_64-unknown-linux-musl
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
@@ -380,7 +374,6 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --locked --out dist
@@ -396,7 +389,7 @@ jobs:
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
.venv/bin/${{ env.MODULE_NAME }} --help
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-${{ matrix.target }}
path: dist
@@ -414,7 +407,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-${{ matrix.target }}
path: |
@@ -434,11 +427,11 @@ jobs:
arch: armv7
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Prep README.md"
@@ -446,7 +439,6 @@ jobs:
- name: "Build wheels"
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --locked --out dist
@@ -464,7 +456,7 @@ jobs:
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
.venv/bin/${{ env.MODULE_NAME }} --help
- name: "Upload wheels"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels-${{ matrix.platform.target }}
path: dist
@@ -482,7 +474,7 @@ jobs:
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
- name: "Upload binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: artifacts-${{ matrix.platform.target }}
path: |

View File

@@ -20,12 +20,6 @@ on:
env:
RUFF_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ruff
permissions:
contents: read
# TODO(zanieb): Ideally, this would be `read` on dry-run but that will require
# significant changes to the workflow.
packages: write # zizmor: ignore[excessive-permissions]
jobs:
docker-build:
name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }}
@@ -39,12 +33,12 @@ jobs:
- linux/amd64
- linux/arm64
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
submodules: recursive
persist-credentials: false
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
@@ -69,7 +63,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with:
images: ${{ env.RUFF_BASE_IMG }}
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
@@ -102,7 +96,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digests
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: digests-${{ env.PLATFORM_TUPLE }}
path: /tmp/digests/*
@@ -119,17 +113,17 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps:
- name: Download digests
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with:
images: ${{ env.RUFF_BASE_IMG }}
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
@@ -173,7 +167,7 @@ jobs:
- debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian
steps:
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
@@ -225,7 +219,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
# ghcr.io prefers index level annotations
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
@@ -262,17 +256,17 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps:
- name: Download digests
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
with:

View File

@@ -1,58 +0,0 @@
# Build ruff_wasm for npm.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
# artifacts job within `cargo-dist`.
name: "Build wasm"
on:
workflow_call:
inputs:
plan:
required: true
type: string
pull_request:
paths:
- .github/workflows/build-wasm.yml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
build:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: ubuntu-latest
strategy:
matrix:
target: [web, bundler, nodejs]
fail-fast: false
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
with:
version: v0.13.1
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Run wasm-pack build"
run: wasm-pack build --target ${{ matrix.target }} crates/ruff_wasm
- name: "Rename generated package"
run: | # Replace the package name w/ jq
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
mv /tmp/package.json crates/ruff_wasm/pkg
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
- name: "Upload wasm artifact"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-wasm-${{ matrix.target }}
path: crates/ruff_wasm/pkg

View File

@@ -24,8 +24,6 @@ env:
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.14"
NEXTEST_PROFILE: ci
# Enable mdtests that require external dependencies
MDTEST_EXTERNAL: "1"
jobs:
determine_changes:
@@ -49,10 +47,8 @@ jobs:
py-fuzzer: ${{ steps.check_py_fuzzer.outputs.changed }}
# Flag that is set to "true" when code related to the playground changes.
playground: ${{ steps.check_playground.outputs.changed }}
# Flag that is set to "true" when code related to the benchmarks changes.
benchmarks: ${{ steps.check_benchmarks.outputs.changed }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -97,7 +93,6 @@ jobs:
':!crates/ruff_python_formatter/**' \
':!crates/ruff_formatter/**' \
':!crates/ruff_dev/**' \
':!crates/ruff_benchmark/**' \
':scripts/*' \
':python/**' \
':.github/workflows/ci.yaml' \
@@ -205,21 +200,6 @@ jobs:
':crates/ruff_python_trivia/**' \
':crates/ruff_source_file/**' \
':crates/ruff_text_size/**' \
':.github/workflows/ci.yaml' \
; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Check if the benchmark code changed
id: check_benchmarks
env:
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
':Cargo.toml' \
':Cargo.lock' \
':crates/ruff_benchmark/**' \
':.github/workflows/ci.yaml' \
; then
@@ -233,7 +213,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Install Rust toolchain"
@@ -247,10 +227,10 @@ jobs:
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -269,10 +249,10 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -281,15 +261,15 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: "true"
- name: ty mdtests (GitHub annotations)
@@ -304,10 +284,6 @@ jobs:
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
- name: Dogfood ty on py-fuzzer
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
- name: Dogfood ty on the scripts directory
run: uv run --project=./scripts cargo run -p ty check --project=./scripts
- name: Dogfood ty on ty_benchmark
run: uv run --project=./scripts/ty_benchmark cargo run -p ty check --project=./scripts/ty_benchmark
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -316,7 +292,7 @@ jobs:
# sync, not just public items. Eventually we should do this for all
# crates; for now add crates here as they are warning-clean to prevent
# regression.
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db -p ruff_python_formatter --document-private-items
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
@@ -332,10 +308,10 @@ jobs:
(needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main')
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -343,17 +319,19 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: "true"
- name: "Run tests"
run: cargo nextest run --cargo-profile profiling --all-features
- name: "Run doctests"
run: cargo test --doc --profile profiling --all-features
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
cargo-test-other:
strategy:
@@ -367,20 +345,20 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: "true"
- name: "Run tests"
@@ -395,17 +373,17 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 24
node-version: 22
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
@@ -427,7 +405,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: SebRollen/toml-action@b1b3628f55fc3a28208d4203ada8b737e9687876 # v1.2.0
@@ -435,7 +413,7 @@ jobs:
with:
file: "Cargo.toml"
field: "workspace.package.rust-version"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -456,10 +434,10 @@ jobs:
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' || needs.determine_changes.outputs.code == 'true' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "fuzz -> target"
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -468,7 +446,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
- name: "Install cargo-fuzz"
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
@@ -483,11 +461,11 @@ jobs:
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
@@ -515,13 +493,13 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 5
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -548,20 +526,15 @@ jobs:
needs: determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
# Ecosystem check needs linter and/or formatter changes.
if: |
!contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' &&
(
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
)
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
@@ -572,7 +545,7 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
@@ -582,7 +555,7 @@ jobs:
cargo build --bin ruff
mv target/debug/ruff target/debug/ruff-baseline
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
clean: false
@@ -648,7 +621,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
name: Upload Results
with:
name: ecosystem-result
@@ -663,12 +636,12 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 20 }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -710,10 +683,10 @@ jobs:
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@80aaafe04903087c333980fa2686259ddd34b2d9 # v1.16.6
- uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -723,11 +696,11 @@ jobs:
needs: determine_changes
if: ${{ needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -745,14 +718,14 @@ jobs:
timeout-minutes: 20
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Prep README.md"
@@ -769,29 +742,32 @@ jobs:
- name: "Remove wheels from cache"
run: rm -rf target/wheels
prek:
name: "prek"
pre-commit:
name: "pre-commit"
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
node-version: 24
- name: "Cache prek"
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
path: ~/.cache/prek
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
- name: "Run prek"
node-version: 22
- name: "Cache pre-commit"
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: "Run pre-commit"
run: |
echo '```console' > "$GITHUB_STEP_SUMMARY"
# Enable color output for prek and remove it for the summary
# Use --hook-stage=manual to enable slower hooks that are skipped by default
SKIP=rustfmt uvx prek run --all-files --show-diff-on-failure --color always --hook-stage manual | \
# Enable color output for pre-commit and remove it for the summary
# Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default
SKIP=cargo-fmt,clippy,dev-generate-all uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
exit_code="${PIPESTATUS[0]}"
echo '```' >> "$GITHUB_STEP_SUMMARY"
@@ -801,21 +777,32 @@ jobs:
name: "mkdocs"
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
python-version: 3.13
activate-environment: true
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: uv pip install -r docs/requirements.txt
- name: "Update README File"
run: python scripts/transform_readme.py --target mkdocs
@@ -823,8 +810,12 @@ jobs:
run: python scripts/generate_mkdocs.py
- name: "Check docs formatting"
run: python scripts/check_docs_formatted.py
- name: "Build Insiders docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: mkdocs build --strict -f mkdocs.insiders.yml
- name: "Build docs"
run: mkdocs build --strict -f mkdocs.yml
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.public.yml
check-formatter-instability-and-black-similarity:
name: "formatter instabilities and black similarity"
@@ -833,10 +824,10 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
@@ -859,12 +850,12 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: "Checkout ruff source"
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
@@ -875,14 +866,14 @@ jobs:
- name: Build Ruff binary
run: cargo build -p ruff --bin ruff
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: "Checkout ruff-lsp source"
with:
persist-credentials: false
repository: "astral-sh/ruff-lsp"
path: ruff-lsp
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
# installation fails on 3.13 and newer
python-version: "3.12"
@@ -910,22 +901,22 @@ jobs:
- determine_changes
if: ${{ (needs.determine_changes.outputs.playground == 'true') }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 24
node-version: 22
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci --ignore-scripts
run: npm ci
working-directory: playground
- name: "Build playgrounds"
run: npm run dev:wasm
@@ -946,208 +937,113 @@ jobs:
(
github.ref == 'refs/heads/main' ||
needs.determine_changes.outputs.formatter == 'true' ||
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.parser == 'true' ||
needs.determine_changes.outputs.benchmarks == 'true'
needs.determine_changes.outputs.linter == 'true'
)
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3
with:
mode: simulation
mode: instrumentation
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-instrumented-ty-build:
name: "benchmarks instrumented ty (build)"
runs-on: depot-ubuntu-24.04-4
benchmarks-instrumented-ty:
name: "benchmarks instrumented (ty)"
runs-on: ubuntu-24.04
needs: determine_changes
if: |
github.repository == 'astral-sh/ruff' &&
(
github.ref == 'refs/heads/main' ||
needs.determine_changes.outputs.ty == 'true' ||
needs.determine_changes.outputs.benchmarks == 'true'
needs.determine_changes.outputs.ty == 'true'
)
timeout-minutes: 20
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m instrumentation --features "codspeed,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Upload benchmark binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: benchmarks-instrumented-ty-binary
path: target/codspeed/simulation/ruff_benchmark
retention-days: 1
benchmarks-instrumented-ty-run:
name: "benchmarks instrumented ty (${{ matrix.benchmark }})"
runs-on: ubuntu-24.04
needs: benchmarks-instrumented-ty-build
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
fail-fast: false
matrix:
benchmark:
- "check_file|micro|anyio"
- "attrs|hydra|datetype"
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install codspeed"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
- name: "Download benchmark binary"
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: benchmarks-instrumented-ty-binary
path: target/codspeed/simulation/ruff_benchmark
- name: "Restore binary permissions"
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3
with:
mode: simulation
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
mode: instrumentation
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-walltime-build:
name: "benchmarks walltime (build)"
# We only run this job if `github.repository == 'astral-sh/ruff'`,
# so hardcoding depot here is fine
runs-on: depot-ubuntu-22.04-arm-4
needs: determine_changes
if: |
github.repository == 'astral-sh/ruff' &&
(
!contains(github.event.pull_request.labels.*.name, 'no-test') &&
(
needs.determine_changes.outputs.ty == 'true' ||
needs.determine_changes.outputs.benchmarks == 'true' ||
github.ref == 'refs/heads/main'
)
)
timeout-minutes: 20
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m walltime --features "codspeed,ty_walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Upload benchmark binary"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: benchmarks-walltime-binary
path: target/codspeed/walltime/ruff_benchmark
retention-days: 1
benchmarks-walltime-run:
name: "benchmarks walltime (${{ matrix.benchmark }})"
benchmarks-walltime:
name: "benchmarks walltime (${{ matrix.benchmarks }})"
runs-on: codspeed-macro
needs: benchmarks-walltime-build
needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed
strategy:
matrix:
benchmark:
- colour_science
- "pandas|tanjun|altair"
- "static_frame|sympy"
- "pydantic|multithreaded|freqtrade"
benchmarks:
- "medium|multithreaded"
- "small|large"
steps:
- name: "Checkout Branch"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-codspeed
- name: "Download benchmark binary"
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: benchmarks-walltime-binary
path: target/codspeed/walltime/ruff_benchmark
- name: "Restore binary permissions"
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3
env:
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
# appear to provide much useful insight for our walltime benchmarks right now
@@ -1155,4 +1051,5 @@ jobs:
CODSPEED_PERF_ENABLED: false
with:
mode: walltime
run: cargo codspeed run --bench ty_walltime -m walltime "${{ matrix.benchmark }}"
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -31,15 +31,15 @@ jobs:
# Don't run the cron job on forks:
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: Build ruff
# A debug build means the script runs slower once it gets started,
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
@@ -62,7 +62,7 @@ jobs:
name: Create an issue if the daily fuzz surfaced any bugs
runs-on: ubuntu-latest
needs: fuzz
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
permissions:
issues: write
steps:

View File

@@ -6,11 +6,6 @@ on:
pull_request:
paths:
- "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"
@@ -41,18 +36,17 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: "mypy-primer"
workspaces: "ruff"
- name: Install Rust toolchain
@@ -61,7 +55,6 @@ jobs:
- name: Run mypy_primer
env:
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
CLICOLOR_FORCE: "1"
DIFF_FILE: mypy_primer.diff
run: |
cd ruff
@@ -70,7 +63,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mypy_primer_diff
path: mypy_primer.diff
@@ -80,19 +73,18 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
shared-key: "mypy-primer"
- name: Install Rust toolchain
run: rustup show
@@ -108,58 +100,7 @@ jobs:
scripts/mypy_primer.sh
- name: Upload diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mypy_primer_memory_diff
path: mypy_primer_memory.diff
# Runs mypy twice against the same ty version to catch any non-deterministic behavior (ideally).
# The job is disabled for now because there are some non-deterministic diagnostics.
mypy_primer_same_revision:
name: Run mypy_primer on same revision
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
# TODO: Enable once we fixed the non-deterministic diagnostics
if: false
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: "ruff"
shared-key: "mypy-primer"
- name: Install Rust toolchain
run: rustup show
- name: Run determinism check
env:
BASE_REVISION: ${{ github.event.pull_request.head.sha }}
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
CLICOLOR_FORCE: "1"
DIFF_FILE: mypy_primer_determinism.diff
run: |
cd ruff
scripts/mypy_primer.sh
- name: Check for non-determinism
run: |
# Remove ANSI color codes for checking
sed -e 's/\x1b\[[0-9;]*m//g' mypy_primer_determinism.diff > mypy_primer_determinism_clean.diff
# Check if there are any differences (non-determinism)
if [ -s mypy_primer_determinism_clean.diff ]; then
echo "ERROR: Non-deterministic output detected!"
echo "The following differences were found when running ty twice on the same commit:"
cat mypy_primer_determinism_clean.diff
exit 1
else
echo "✓ Output is deterministic"
fi

View File

@@ -17,19 +17,18 @@ on:
required: true
type: string
permissions:
contents: read
jobs:
mkdocs:
runs-on: ubuntu-latest
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ inputs.ref }}
persist-credentials: true
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: 3.12
@@ -60,12 +59,23 @@ jobs:
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: pip install -r docs/requirements.txt
- name: "Copy README File"
@@ -73,8 +83,13 @@ jobs:
python scripts/transform_readme.py --target mkdocs
python scripts/generate_mkdocs.py
- name: "Build Insiders docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: mkdocs build --strict -f mkdocs.insiders.yml
- name: "Build docs"
run: mkdocs build --strict -f mkdocs.yml
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.public.yml
- name: "Clone docs repo"
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs

View File

@@ -26,18 +26,18 @@ jobs:
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 24
node-version: 22
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci --ignore-scripts
run: npm ci
working-directory: playground
- name: "Run TypeScript checks"
run: npm run check

View File

@@ -22,8 +22,8 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: wheels-*
path: wheels

View File

@@ -30,18 +30,18 @@ jobs:
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 24
node-version: 22
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci --ignore-scripts
run: npm ci
working-directory: playground
- name: "Run TypeScript checks"
run: npm run check

View File

@@ -1,18 +1,25 @@
# Publish ruff_wasm to npm.
# Build and publish ruff-api for wasm.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish
# job within `cargo-dist`.
name: "Publish wasm"
name: "Build and publish wasm"
on:
workflow_dispatch:
workflow_call:
inputs:
plan:
required: true
type: string
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
publish:
ruff_wasm:
runs-on: ubuntu-latest
permissions:
contents: read
@@ -22,19 +29,31 @@ jobs:
target: [web, bundler, nodejs]
fail-fast: false
steps:
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
name: artifacts-wasm-${{ matrix.target }}
path: pkg
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
with:
node-version: 24
version: v0.13.1
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Run wasm-pack build"
run: wasm-pack build --target ${{ matrix.target }} crates/ruff_wasm
- name: "Rename generated package"
run: | # Replace the package name w/ jq
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
mv /tmp/package.json crates/ruff_wasm/pkg
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
registry-url: "https://registry.npmjs.org"
- name: "Publish (dry-run)"
if: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
run: npm publish --dry-run pkg
run: npm publish --dry-run crates/ruff_wasm/pkg
- name: "Publish"
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
run: npm publish --provenance --access public pkg
run: npm publish --provenance --access public crates/ruff_wasm/pkg
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -60,7 +60,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
@@ -70,7 +70,7 @@ jobs:
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -86,7 +86,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -112,40 +112,30 @@ jobs:
"contents": "read"
"packages": "write"
custom-build-wasm:
needs:
- plan
if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
uses: ./.github/workflows/build-wasm.yml
with:
plan: ${{ needs.plan.outputs.val }}
secrets: inherit
# Build and package all the platform-agnostic(ish) things
build-global-artifacts:
needs:
- plan
- custom-build-binaries
- custom-build-docker
- custom-build-wasm
runs-on: "depot-ubuntu-latest-4"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
pattern: artifacts-*
path: target/distrib/
@@ -163,7 +153,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: artifacts-build-global
path: |
@@ -175,29 +165,28 @@ jobs:
- plan
- custom-build-binaries
- custom-build-docker
- custom-build-wasm
- build-global-artifacts
# Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') && (needs.custom-build-wasm.result == 'skipped' || needs.custom-build-wasm.result == 'success') }}
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "depot-ubuntu-latest-4"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
pattern: artifacts-*
path: target/distrib/
@@ -211,7 +200,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
@@ -261,13 +250,13 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
with:
persist-credentials: false
submodules: recursive
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
pattern: artifacts-*
path: artifacts

View File

@@ -16,7 +16,8 @@ name: Sync typeshed
# 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. Formats the code again
# c. Attempts to update any snapshots that might have changed
# (this sub-step is allowed to fail)
# d. Commits the changes and pushes them to the same upstream branch
# e. 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
@@ -61,12 +62,12 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout Ruff
with:
path: ruff
persist-credentials: true
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout typeshed
with:
repository: python/typeshed
@@ -76,7 +77,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -125,12 +126,12 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout Ruff
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -164,12 +165,12 @@ jobs:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout Ruff
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -197,6 +198,37 @@ jobs:
run: |
rm "${VENDORED_TYPESHED}/pyproject.toml"
git commit -am "Remove pyproject.toml file"
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Install Rust toolchain"
if: ${{ success() }}
run: rustup show
- name: "Install mold"
if: ${{ success() }}
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
if: ${{ success() }}
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install cargo insta"
if: ${{ success() }}
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-insta
- name: Update snapshots
if: ${{ success() }}
run: |
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
#
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
# We don't care about other tests failing here, we just want snapshots updated where possible,
# so we use `|| true` here to ignore the exit code.
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
- name: Commit snapshot changes
if: ${{ success() }}
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
- name: Push changes upstream and create a PR
if: ${{ success() }}
run: |
@@ -208,7 +240,7 @@ jobs:
name: Create an issue if the typeshed sync failed
runs-on: ubuntu-latest
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
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:

View File

@@ -4,13 +4,7 @@ permissions: {}
on:
pull_request:
# The default for `pull_request` is to trigger on `synchronize`, `opened` and `reopened`.
# We also add `labeled` here so that the workflow triggers when a label is initially added.
types:
- labeled
- synchronize
- opened
- reopened
types: [labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
@@ -23,29 +17,30 @@ env:
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
REF_NAME: ${{ github.ref_name }}
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-analyzer:
name: Compute diagnostic diff
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
if: contains( github.event.pull_request.labels.*.name, 'ecosystem-analyzer')
if: contains(github.event.label.name, 'ecosystem-analyzer')
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: true
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show
@@ -72,7 +67,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
ecosystem-analyzer \
--repository ruff \
@@ -117,30 +112,39 @@ jobs:
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: "Upload full report"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
name: full-report
path: dist/
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
- name: "Append deployment URL"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
env:
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
run: |
echo >> comment.md
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload comment
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: comment.md
path: comment.md
- name: Upload diagnostics diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: diff.html
path: dist/diff.html
- name: Upload timing diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: timing.html
path: dist/timing.html

View File

@@ -1,7 +1,3 @@
# This workflow is a cron job that generates a report describing
# all diagnostics ty emits across the whole ecosystem. The report
# is uploaded to https://ty-ecosystem-ext.pages.dev/ on a weekly basis.
name: ty ecosystem-report
permissions: {}
@@ -18,6 +14,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs:
ty-ecosystem-report:
@@ -25,21 +22,21 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: true
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show
@@ -55,7 +52,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
ecosystem-analyzer \
--verbose \
@@ -73,10 +70,11 @@ jobs:
ecosystem-diagnostics.json \
--output dist/index.html
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
# Make sure to update the bot if you rename the artifact.
- name: "Upload ecosystem report"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
name: full-report
path: dist/
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}

View File

@@ -6,11 +6,6 @@ on:
pull_request:
paths:
- "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"
@@ -37,20 +32,20 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: python/typing
ref: ${{ env.CONFORMANCE_SUITE_COMMIT }}
path: typing
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
@@ -59,6 +54,9 @@ jobs:
- name: Compute diagnostic diff
shell: bash
env:
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
TY_MAX_PARALLELISM: 1
run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
@@ -101,7 +99,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: typing_conformance_diagnostics_diff
path: typing_conformance_diagnostics.diff
@@ -109,7 +107,7 @@ jobs:
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload conformance suite commit
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: conformance-suite-commit
path: conformance-suite-commit

23
.github/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://docs.zizmor.sh/configuration/
#
# TODO: can we remove the ignores here so that our workflows are more secure?
rules:
cache-poisoning:
ignore:
- build-docker.yml
excessive-permissions:
# it's hard to test what the impact of removing these ignores would be
# without actually running the release workflow...
ignore:
- build-docker.yml
- publish-docs.yml
secrets-inherit:
# `cargo dist` makes extensive use of `secrets: inherit`,
# and we can't easily fix that until an upstream release changes that.
disable: true
template-injection:
ignore:
# like with `secrets-inherit`, `cargo dist` introduces some
# template injections. We've manually audited these usages for safety.
- release.yml

View File

@@ -21,91 +21,32 @@ exclude: |
)$
repos:
# Priority 0: Read-only hooks; hooks that modify disjoint file types.
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
rev: v5.0.0
hooks:
- id: check-merge-conflict
priority: 0
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.24.1
hooks:
- id: validate-pyproject
priority: 0
- repo: https://github.com/crate-ci/typos
rev: v1.41.0
hooks:
- id: typos
priority: 0
- repo: local
hooks:
- id: rustfmt
name: rustfmt
entry: rustfmt
language: system
types: [rust]
priority: 0
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.7.4
hooks:
- id: prettier
types: [yaml]
priority: 0
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.19.0
hooks:
- id: zizmor
priority: 0
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.36.0
hooks:
- id: check-github-workflows
priority: 0
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
priority: 0
- repo: https://github.com/executablebooks/mdformat
rev: 1.0.0
rev: 0.7.22
hooks:
- id: mdformat
language: python # means renovate will also update `additional_dependencies`
additional_dependencies:
- mdformat-mkdocs==5.0.0
- mdformat-footnote==0.1.2
- mdformat-mkdocs==4.0.0
- mdformat-footnote==0.1.1
exclude: |
(?x)^(
docs/formatter/black\.md
| docs/\w+\.md
)$
priority: 0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.10
hooks:
- id: ruff-format
priority: 0
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
require_serial: true
priority: 1
# Priority 1: Second-pass fixers (e.g., markdownlint-fix runs after mdformat).
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.47.0
rev: v0.45.0
hooks:
- id: markdownlint-fix
exclude: |
@@ -113,11 +54,9 @@ repos:
docs/formatter/black\.md
| docs/\w+\.md
)$
priority: 1
# Priority 2: blacken-docs runs after markdownlint-fix (both modify markdown).
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.20.0
rev: 1.19.1
hooks:
- id: blacken-docs
language: python # means renovate will also update `additional_dependencies`
@@ -128,27 +67,74 @@ repos:
.*?invalid(_.+)*_syntax\.md
)$
additional_dependencies:
- black==25.12.0
priority: 2
- black==25.1.0
- repo: https://github.com/crate-ci/typos
rev: v1.34.0
hooks:
- id: typos
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
types: [rust]
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.7
hooks:
- id: ruff-format
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
require_serial: true
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.6.2
hooks:
- id: prettier
types: [yaml]
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.16.0
hooks:
- id: zizmor
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.2
hooks:
- id: check-github-workflows
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
- repo: https://github.com/rhysd/actionlint
rev: v1.7.10
rev: v1.7.7
hooks:
- id: actionlint
stages:
# This hook is disabled by default, since it's quite slow.
# To run all hooks *including* this hook, use `uvx prek run -a --hook-stage=manual`.
# To run *just* this hook, use `uvx prek run -a actionlint --hook-stage=manual`.
# To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`.
# To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`.
- manual
args:
- "-ignore=SC2129" # ignorable stylistic lint from shellcheck
- "-ignore=SC2016" # another shellcheck lint: seems to have false positives?
language: golang # means renovate will also update `additional_dependencies`
additional_dependencies:
# actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions
# and checks these with shellcheck. This is arguably its most useful feature,
# but the integration only works if shellcheck is installed
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1"
priority: 0
- "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]

View File

@@ -5,6 +5,5 @@
"rust-analyzer.check.command": "clippy",
"search.exclude": {
"**/*.snap": true
},
"ty.diagnosticMode": "openFilesOnly"
}
}

View File

@@ -1,314 +1,5 @@
# Changelog
## 0.14.11
Released on 2026-01-08.
### Preview features
- Consolidate diagnostics for matched disable/enable suppression comments ([#22099](https://github.com/astral-sh/ruff/pull/22099))
- Report diagnostics for invalid/unmatched range suppression comments ([#21908](https://github.com/astral-sh/ruff/pull/21908))
- \[`airflow`\] Passing positional argument into `airflow.lineage.hook.HookLineageCollector.create_asset` is not allowed (`AIR303`) ([#22046](https://github.com/astral-sh/ruff/pull/22046))
- \[`refurb`\] Mark `FURB192` fix as always unsafe ([#22210](https://github.com/astral-sh/ruff/pull/22210))
- \[`ruff`\] Add `non-empty-init-module` (`RUF067`) ([#22143](https://github.com/astral-sh/ruff/pull/22143))
### Bug fixes
- Fix GitHub format for multi-line diagnostics ([#22108](https://github.com/astral-sh/ruff/pull/22108))
- \[`flake8-unused-arguments`\] Mark `**kwargs` in `TypeVar` as used (`ARG001`) ([#22214](https://github.com/astral-sh/ruff/pull/22214))
### Rule changes
- Add `help:` subdiagnostics for several Ruff rules that can sometimes appear to disagree with `ty` ([#22331](https://github.com/astral-sh/ruff/pull/22331))
- \[`pylint`\] Demote `PLW1510` fix to display-only ([#22318](https://github.com/astral-sh/ruff/pull/22318))
- \[`pylint`\] Ignore identical members (`PLR1714`) ([#22220](https://github.com/astral-sh/ruff/pull/22220))
- \[`pylint`\] Improve diagnostic range for `PLC0206` ([#22312](https://github.com/astral-sh/ruff/pull/22312))
- \[`ruff`\] Improve fix title for `RUF102` invalid rule code ([#22100](https://github.com/astral-sh/ruff/pull/22100))
- \[`flake8-simplify`\]: Avoid unnecessary builtins import for `SIM105` ([#22358](https://github.com/astral-sh/ruff/pull/22358))
### Configuration
- Allow Python 3.15 as valid `target-version` value in preview ([#22419](https://github.com/astral-sh/ruff/pull/22419))
- Check `required-version` before parsing rules ([#22410](https://github.com/astral-sh/ruff/pull/22410))
- Include configured `src` directories when resolving graphs ([#22451](https://github.com/astral-sh/ruff/pull/22451))
### Documentation
- Update `T201` suggestion to not use root logger to satisfy `LOG015` ([#22059](https://github.com/astral-sh/ruff/pull/22059))
- Fix `iter` example in unsafe fixes doc ([#22118](https://github.com/astral-sh/ruff/pull/22118))
- \[`flake8_print`\] better suggestion for `basicConfig` in `T201` docs ([#22101](https://github.com/astral-sh/ruff/pull/22101))
- \[`pylint`\] Restore the fix safety docs for `PLW0133` ([#22211](https://github.com/astral-sh/ruff/pull/22211))
- Fix Jupyter notebook discovery info for editors ([#22447](https://github.com/astral-sh/ruff/pull/22447))
### Contributors
- [@charliermarsh](https://github.com/charliermarsh)
- [@ntBre](https://github.com/ntBre)
- [@cenviity](https://github.com/cenviity)
- [@njhearp](https://github.com/njhearp)
- [@cbachhuber](https://github.com/cbachhuber)
- [@jelle-openai](https://github.com/jelle-openai)
- [@AlexWaygood](https://github.com/AlexWaygood)
- [@ValdonVitija](https://github.com/ValdonVitija)
- [@BurntSushi](https://github.com/BurntSushi)
- [@Jkhall81](https://github.com/Jkhall81)
- [@PeterJCLaw](https://github.com/PeterJCLaw)
- [@harupy](https://github.com/harupy)
- [@amyreese](https://github.com/amyreese)
- [@sjyangkevin](https://github.com/sjyangkevin)
- [@woodruffw](https://github.com/woodruffw)
## 0.14.10
Released on 2025-12-18.
### Preview features
- [formatter] Fluent formatting of method chains ([#21369](https://github.com/astral-sh/ruff/pull/21369))
- [formatter] Keep lambda parameters on one line and parenthesize the body if it expands ([#21385](https://github.com/astral-sh/ruff/pull/21385))
- \[`flake8-implicit-str-concat`\] New rule to prevent implicit string concatenation in collections (`ISC004`) ([#21972](https://github.com/astral-sh/ruff/pull/21972))
- \[`flake8-use-pathlib`\] Make fixes unsafe when types change in compound statements (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#22009](https://github.com/astral-sh/ruff/pull/22009))
- \[`refurb`\] Extend support for `Path.open` (`FURB101`, `FURB103`) ([#21080](https://github.com/astral-sh/ruff/pull/21080))
### Bug fixes
- \[`pyupgrade`\] Fix parsing named Unicode escape sequences (`UP032`) ([#21901](https://github.com/astral-sh/ruff/pull/21901))
### Rule changes
- \[`eradicate`\] Ignore `ruff:disable` and `ruff:enable` comments in `ERA001` ([#22038](https://github.com/astral-sh/ruff/pull/22038))
- \[`flake8-pytest-style`\] Allow `match` and `check` keyword arguments without an expected exception type (`PT010`) ([#21964](https://github.com/astral-sh/ruff/pull/21964))
- [syntax-errors] Annotated name cannot be global ([#20868](https://github.com/astral-sh/ruff/pull/20868))
### Documentation
- Add `uv` and `ty` to the Ruff README ([#21996](https://github.com/astral-sh/ruff/pull/21996))
- Document known lambda formatting deviations from Black ([#21954](https://github.com/astral-sh/ruff/pull/21954))
- Update `setup.md` ([#22024](https://github.com/astral-sh/ruff/pull/22024))
- \[`flake8-bandit`\] Fix broken link (`S704`) ([#22039](https://github.com/astral-sh/ruff/pull/22039))
### Other changes
- Fix playground Share button showing "Copied!" before clipboard copy completes ([#21942](https://github.com/astral-sh/ruff/pull/21942))
### Contributors
- [@dylwil3](https://github.com/dylwil3)
- [@charliecloudberry](https://github.com/charliecloudberry)
- [@charliermarsh](https://github.com/charliermarsh)
- [@chirizxc](https://github.com/chirizxc)
- [@ntBre](https://github.com/ntBre)
- [@zanieb](https://github.com/zanieb)
- [@amyreese](https://github.com/amyreese)
- [@hauntsaninja](https://github.com/hauntsaninja)
- [@11happy](https://github.com/11happy)
- [@mahiro72](https://github.com/mahiro72)
- [@MichaReiser](https://github.com/MichaReiser)
- [@phongddo](https://github.com/phongddo)
- [@PeterJCLaw](https://github.com/PeterJCLaw)
## 0.14.9
Released on 2025-12-11.
### Preview features
- \[`ruff`\] New `RUF100` diagnostics for unused range suppressions ([#21783](https://github.com/astral-sh/ruff/pull/21783))
- \[`pylint`\] Detect subclasses of builtin exceptions (`PLW0133`) ([#21382](https://github.com/astral-sh/ruff/pull/21382))
### Bug fixes
- Fix comment placement in lambda parameters ([#21868](https://github.com/astral-sh/ruff/pull/21868))
- Skip over trivia tokens after re-lexing ([#21895](https://github.com/astral-sh/ruff/pull/21895))
- \[`flake8-bandit`\] Fix false positive when using non-standard `CSafeLoader` path (S506). ([#21830](https://github.com/astral-sh/ruff/pull/21830))
- \[`flake8-bugbear`\] Accept immutable slice default arguments (`B008`) ([#21823](https://github.com/astral-sh/ruff/pull/21823))
### Rule changes
- \[`pydocstyle`\] Suppress `D417` for parameters with `Unpack` annotations ([#21816](https://github.com/astral-sh/ruff/pull/21816))
### Performance
- Use `memchr` for computing line indexes ([#21838](https://github.com/astral-sh/ruff/pull/21838))
### Documentation
- Document `*.pyw` is included by default in preview ([#21885](https://github.com/astral-sh/ruff/pull/21885))
- Document range suppressions, reorganize suppression docs ([#21884](https://github.com/astral-sh/ruff/pull/21884))
- Update mkdocs-material to 9.7.0 (Insiders now free) ([#21797](https://github.com/astral-sh/ruff/pull/21797))
### Contributors
- [@Avasam](https://github.com/Avasam)
- [@MichaReiser](https://github.com/MichaReiser)
- [@charliermarsh](https://github.com/charliermarsh)
- [@amyreese](https://github.com/amyreese)
- [@phongddo](https://github.com/phongddo)
- [@prakhar1144](https://github.com/prakhar1144)
- [@mahiro72](https://github.com/mahiro72)
- [@ntBre](https://github.com/ntBre)
- [@LoicRiegel](https://github.com/LoicRiegel)
## 0.14.8
Released on 2025-12-04.
### Preview features
- \[`flake8-bugbear`\] Catch `yield` expressions within other statements (`B901`) ([#21200](https://github.com/astral-sh/ruff/pull/21200))
- \[`flake8-use-pathlib`\] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) ([#21440](https://github.com/astral-sh/ruff/pull/21440))
### Bug fixes
- Fix syntax error false positives for `await` outside functions ([#21763](https://github.com/astral-sh/ruff/pull/21763))
- \[`flake8-simplify`\] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) ([#21479](https://github.com/astral-sh/ruff/pull/21479))
### Documentation
- Suggest using `--output-file` option in GitLab integration ([#21706](https://github.com/astral-sh/ruff/pull/21706))
### Other changes
- [syntax-error] Default type parameter followed by non-default type parameter ([#21657](https://github.com/astral-sh/ruff/pull/21657))
### Contributors
- [@kieran-ryan](https://github.com/kieran-ryan)
- [@11happy](https://github.com/11happy)
- [@danparizher](https://github.com/danparizher)
- [@ntBre](https://github.com/ntBre)
## 0.14.7
Released on 2025-11-28.
### Preview features
- \[`flake8-bandit`\] Handle string literal bindings in suspicious-url-open-usage (`S310`) ([#21469](https://github.com/astral-sh/ruff/pull/21469))
- \[`pylint`\] Fix `PLR1708` false positives on nested functions ([#21177](https://github.com/astral-sh/ruff/pull/21177))
- \[`pylint`\] Fix suppression for empty dict without tuple key annotation (`PLE1141`) ([#21290](https://github.com/astral-sh/ruff/pull/21290))
- \[`ruff`\] Add rule `RUF066` to detect unnecessary class properties ([#21535](https://github.com/astral-sh/ruff/pull/21535))
- \[`ruff`\] Catch more dummy variable uses (`RUF052`) ([#19799](https://github.com/astral-sh/ruff/pull/19799))
### Bug fixes
- [server] Set severity for non-rule diagnostics ([#21559](https://github.com/astral-sh/ruff/pull/21559))
- \[`flake8-implicit-str-concat`\] Avoid invalid fix in (`ISC003`) ([#21517](https://github.com/astral-sh/ruff/pull/21517))
- \[`parser`\] Fix panic when parsing IPython escape command expressions ([#21480](https://github.com/astral-sh/ruff/pull/21480))
### CLI
- Show partial fixability indicator in statistics output ([#21513](https://github.com/astral-sh/ruff/pull/21513))
### Contributors
- [@mikeleppane](https://github.com/mikeleppane)
- [@senekor](https://github.com/senekor)
- [@ShaharNaveh](https://github.com/ShaharNaveh)
- [@JumboBear](https://github.com/JumboBear)
- [@prakhar1144](https://github.com/prakhar1144)
- [@tsvikas](https://github.com/tsvikas)
- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)
- [@AlexWaygood](https://github.com/AlexWaygood)
- [@MichaReiser](https://github.com/MichaReiser)
## 0.14.6
Released on 2025-11-21.
### Preview features
- \[`flake8-bandit`\] Support new PySNMP API paths (`S508`, `S509`) ([#21374](https://github.com/astral-sh/ruff/pull/21374))
### Bug fixes
- Adjust own-line comment placement between branches ([#21185](https://github.com/astral-sh/ruff/pull/21185))
- Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value ([#20418](https://github.com/astral-sh/ruff/pull/20418))
- Fix panic when formatting comments in unary expressions ([#21501](https://github.com/astral-sh/ruff/pull/21501))
- Respect `fmt: skip` for compound statements on a single line ([#20633](https://github.com/astral-sh/ruff/pull/20633))
- \[`refurb`\] Fix `FURB103` autofix ([#21454](https://github.com/astral-sh/ruff/pull/21454))
- \[`ruff`\] Fix false positive for complex conversion specifiers in `logging-eager-conversion` (`RUF065`) ([#21464](https://github.com/astral-sh/ruff/pull/21464))
### Rule changes
- \[`ruff`\] Avoid false positive on `ClassVar` reassignment (`RUF012`) ([#21478](https://github.com/astral-sh/ruff/pull/21478))
### CLI
- Render hyperlinks for lint errors ([#21514](https://github.com/astral-sh/ruff/pull/21514))
- Add a `ruff analyze` option to skip over imports in `TYPE_CHECKING` blocks ([#21472](https://github.com/astral-sh/ruff/pull/21472))
### Documentation
- Limit `eglot-format` hook to eglot-managed Python buffers ([#21459](https://github.com/astral-sh/ruff/pull/21459))
- Mention `force-exclude` in "Configuration > Python file discovery" ([#21500](https://github.com/astral-sh/ruff/pull/21500))
### Contributors
- [@ntBre](https://github.com/ntBre)
- [@dylwil3](https://github.com/dylwil3)
- [@gauthsvenkat](https://github.com/gauthsvenkat)
- [@MichaReiser](https://github.com/MichaReiser)
- [@thamer](https://github.com/thamer)
- [@Ruchir28](https://github.com/Ruchir28)
- [@thejcannon](https://github.com/thejcannon)
- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)
## 0.14.5
Released on 2025-11-13.
### Preview features
- \[`flake8-simplify`\] Apply `SIM113` when index variable is of type `int` ([#21395](https://github.com/astral-sh/ruff/pull/21395))
- \[`pydoclint`\] Fix false positive when Sphinx directives follow a "Raises" section (`DOC502`) ([#20535](https://github.com/astral-sh/ruff/pull/20535))
- \[`pydoclint`\] Support NumPy-style comma-separated parameters (`DOC102`) ([#20972](https://github.com/astral-sh/ruff/pull/20972))
- \[`refurb`\] Auto-fix annotated assignments (`FURB101`) ([#21278](https://github.com/astral-sh/ruff/pull/21278))
- \[`ruff`\] Ignore `str()` when not used for simple conversion (`RUF065`) ([#21330](https://github.com/astral-sh/ruff/pull/21330))
### Bug fixes
- Fix syntax error false positive on alternative `match` patterns ([#21362](https://github.com/astral-sh/ruff/pull/21362))
- \[`flake8-simplify`\] Fix false positive for iterable initializers with generator arguments (`SIM222`) ([#21187](https://github.com/astral-sh/ruff/pull/21187))
- \[`pyupgrade`\] Fix false positive on relative imports from local `.builtins` module (`UP029`) ([#21309](https://github.com/astral-sh/ruff/pull/21309))
- \[`pyupgrade`\] Consistently set the deprecated tag (`UP035`) ([#21396](https://github.com/astral-sh/ruff/pull/21396))
### Rule changes
- \[`refurb`\] Detect empty f-strings (`FURB105`) ([#21348](https://github.com/astral-sh/ruff/pull/21348))
### CLI
- Add option to provide a reason to `--add-noqa` ([#21294](https://github.com/astral-sh/ruff/pull/21294))
- Add upstream linter URL to `ruff linter --output-format=json` ([#21316](https://github.com/astral-sh/ruff/pull/21316))
- Add color to `--help` ([#21337](https://github.com/astral-sh/ruff/pull/21337))
### Documentation
- Add a new "Opening a PR" section to the contribution guide ([#21298](https://github.com/astral-sh/ruff/pull/21298))
- Added the PyScripter IDE to the list of "Who is using Ruff?" ([#21402](https://github.com/astral-sh/ruff/pull/21402))
- Update PyCharm setup instructions ([#21409](https://github.com/astral-sh/ruff/pull/21409))
- \[`flake8-annotations`\] Add link to `allow-star-arg-any` option (`ANN401`) ([#21326](https://github.com/astral-sh/ruff/pull/21326))
### Other changes
- \[`configuration`\] Improve error message when `line-length` exceeds `u16::MAX` ([#21329](https://github.com/astral-sh/ruff/pull/21329))
### Contributors
- [@njhearp](https://github.com/njhearp)
- [@11happy](https://github.com/11happy)
- [@hugovk](https://github.com/hugovk)
- [@Gankra](https://github.com/Gankra)
- [@ntBre](https://github.com/ntBre)
- [@pyscripter](https://github.com/pyscripter)
- [@danparizher](https://github.com/danparizher)
- [@MichaReiser](https://github.com/MichaReiser)
- [@henryiii](https://github.com/henryiii)
- [@charliecloudberry](https://github.com/charliecloudberry)
## 0.14.4
Released on 2025-11-06.

View File

@@ -1,71 +0,0 @@
# Ruff Repository
This repository contains both Ruff (a Python linter and formatter) and ty (a Python type checker). The crates follow a naming convention: `ruff_*` for Ruff-specific code and `ty_*` for ty-specific code. ty reuses several Ruff crates, including the Python parser (`ruff_python_parser`) and AST definitions (`ruff_python_ast`).
## Running Tests
Run all tests (using `nextest` for faster execution):
```sh
cargo nextest run
```
For faster test execution, use the `fast-test` profile which enables optimizations while retaining debug info:
```sh
cargo nextest run --cargo-profile fast-test
```
Run tests for a specific crate:
```sh
cargo nextest run -p ty_python_semantic
```
Run a specific mdtest (use a substring of the test name):
```sh
MDTEST_TEST_FILTER="<filter>" cargo nextest run -p ty_python_semantic mdtest
```
Update snapshots after running tests:
```sh
cargo insta accept
```
## Running Clippy
```sh
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
## Running Debug Builds
Use debug builds (not `--release`) when developing, as release builds lack debug assertions and have slower compile times.
Run Ruff:
```sh
cargo run --bin ruff -- check path/to/file.py
```
Run ty:
```sh
cargo run --bin ty -- check path/to/file.py
```
## Pull Requests
When working on ty, PR titles should start with `[ty]` and be tagged with the `ty` GitHub label.
## Development Guidelines
- All changes must be tested. If you're not testing your changes, you're not done.
- Get your tests to pass. If you didn't run the tests, your code does not work.
- Follow existing code style. Check neighboring files for patterns.
- Always run `uvx prek run -a` at the end of a task.
- Avoid writing significant amounts of new code. This is often a sign that we're missing an existing method or mechanism that could help solve the problem. Look for existing utilities first.
- Avoid falling back to patterns that require `panic!`, `unreachable!`, or `.unwrap()`. Instead, try to encode those constraints in the type system.
- Prefer let chains (`if let` combined with `&&`) over nested `if let` statements to reduce indentation and improve readability.

View File

@@ -53,12 +53,12 @@ cargo install cargo-insta
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
run Python utility commands.
You can optionally install hooks to automatically run the validation checks
You can optionally install pre-commit hooks to automatically run the validation checks
when making a commit:
```shell
uv tool install prek
prek install
uv tool install pre-commit
pre-commit install
```
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
@@ -85,7 +85,7 @@ and that it passes both the lint and test validation checks:
```shell
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
RUFF_UPDATE_SCHEMA=1 cargo test # Rust testing and updating ruff.schema.json
uvx prek run -a # Rust and Python formatting, Markdown and Python linting, etc.
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
```
These checks will run on GitHub Actions when you open your pull request, but running them locally
@@ -331,6 +331,13 @@ you addressed them.
## 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).
@@ -344,7 +351,11 @@ To preview any changes to the documentation locally:
1. Run the development server with:
```shell
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
# For contributors.
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.public.yml
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
uvx --with-requirements docs/requirements-insiders.txt -- mkdocs serve -f mkdocs.insiders.yml
```
The documentation should then be available locally at
@@ -381,7 +392,7 @@ Commit each step of this process separately for easier review.
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
- Changes should be edited to be user-facing descriptions, avoiding internal details
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `prek`
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `pre-commit`
Additionally, for minor releases:

448
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# Please update rustfmt.toml when bumping the Rust edition
edition = "2024"
rust-version = "1.90"
rust-version = "1.89"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@@ -45,7 +45,6 @@ ty = { path = "crates/ty" }
ty_combine = { path = "crates/ty_combine" }
ty_completion_eval = { path = "crates/ty_completion_eval" }
ty_ide = { path = "crates/ty_ide" }
ty_module_resolver = { path = "crates/ty_module_resolver" }
ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" }
ty_server = { path = "crates/ty_server" }
@@ -58,8 +57,8 @@ anstream = { version = "0.6.18" }
anstyle = { version = "1.0.10" }
anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" }
argfile = { version = "0.2.0" }
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 = [
@@ -71,31 +70,30 @@ camino = { version = "1.1.7" }
clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.0" }
csv = { version = "1.3.1" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
colored = { version = "3.0.0" }
compact_str = "0.9.0"
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
countme = { version = "3.0.1" }
criterion = { version = "0.8.0", default-features = false }
compact_str = "0.9.0"
criterion = { version = "0.7.0", default-features = false }
crossbeam = { version = "0.8.4" }
csv = { version = "1.3.1" }
dashmap = { version = "6.0.1" }
datatest-stable = { version = "0.3.3" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
drop_bomb = { version = "0.1.5" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
etcetera = { version = "0.11.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
get-size2 = { version = "0.7.3", features = [
getrandom = { version = "0.3.1" }
get-size2 = { version = "0.7.0", features = [
"derive",
"smallvec",
"hashbrown",
"compact-str",
"ordermap"
] }
getrandom = { version = "0.3.1" }
glob = { version = "0.3.1" }
globset = { version = "0.4.14" }
globwalk = { version = "0.9.1" }
@@ -117,8 +115,8 @@ is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" }
itertools = { version = "0.14.0" }
jiff = { version = "0.2.0" }
jod-thread = { version = "1.0.0" }
js-sys = { version = "0.3.69" }
jod-thread = { version = "1.0.0" }
libc = { version = "0.2.153" }
libcst = { version = "1.8.4", default-features = false }
log = { version = "0.4.17" }
@@ -131,7 +129,7 @@ memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
notify = { version = "8.0.0" }
ordermap = { version = "1.0.0" }
ordermap = { version = "0.5.0" }
path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }
@@ -140,8 +138,6 @@ pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" }
pyproject-toml = { version = "0.13.4" }
quick-junit = { version = "0.5.0" }
quickcheck = { version = "1.0.3", default-features = false }
quickcheck_macros = { version = "1.0.0" }
quote = { version = "1.0.23" }
rand = { version = "0.9.0" }
rayon = { version = "1.10.0" }
@@ -150,7 +146,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.git", rev = "9860ff6ca0f1f8f3a8d6b832020002790b501254", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "05a9af7f554b64b8aadc2eeb6f2caf73d0408d09", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
@@ -177,7 +173,6 @@ snapbox = { version = "0.6.0", features = [
static_assertions = "1.1.0"
strum = { version = "0.27.0", features = ["strum_macros"] }
strum_macros = { version = "0.27.0" }
supports-hyperlinks = { version = "3.1.0" }
syn = { version = "2.0.55" }
tempfile = { version = "3.9.0" }
test-case = { version = "3.3.1" }
@@ -198,9 +193,9 @@ tryfn = { version = "0.2.1" }
typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" }
unicode-ident = { version = "1.0.12" }
unicode-normalization = { version = "0.1.23" }
unicode-width = { version = "0.2.0" }
unicode_names2 = { version = "1.2.2" }
unicode-normalization = { version = "0.1.23" }
url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
walkdir = { version = "2.3.2" }
@@ -210,13 +205,8 @@ wild = { version = "2" }
zip = { version = "0.6.6", default-features = false }
[workspace.metadata.cargo-shear]
ignored = [
"getrandom",
"ruff_options_metadata",
"uuid",
"get-size2",
"ty_completion_eval",
]
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2", "ty_completion_eval"]
[workspace.lints.rust]
unsafe_code = "warn"
@@ -276,6 +266,7 @@ if_not_else = "allow"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
lto = "fat"
codegen-units = 16
@@ -290,12 +281,6 @@ codegen-units = 1
[profile.release.package.salsa]
codegen-units = 1
# Profile to build a minimally sized binary for ruff/ty
[profile.minimal-size]
inherits = "release"
opt-level = "z"
codegen-units = 1
[profile.dev.package.insta]
opt-level = 3
@@ -335,11 +320,6 @@ strip = false
debug = "full"
lto = false
# Profile for faster iteration: applies minimal optimizations for faster tests.
[profile.fast-test]
inherits = "dev"
opt-level = 1
# The profile that 'cargo dist' will build with.
[profile.dist]
inherits = "release"

View File

@@ -57,11 +57,8 @@ Ruff is extremely actively developed and used in major open-source projects like
...and [many more](#whos-using-ruff).
Ruff is backed by [Astral](https://astral.sh), the creators of
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
## Testimonials
@@ -150,8 +147,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.14.11/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.11/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.14.4/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.4/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -184,7 +181,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.14.11
rev: v0.14.4
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -4,7 +4,6 @@ extend-exclude = [
"crates/ty_vendored/vendor/**/*",
"**/resources/**/*",
"**/snapshots/**/*",
"crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs",
# Completion tests tend to have a lot of incomplete
# words naturally. It's annoying to have to make all
# of them actually words. So just ignore typos here.

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.11"
version = "0.14.4"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -12,13 +12,6 @@ license = { workspace = true }
readme = "../../README.md"
default-run = "ruff"
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[dependencies]
ruff_cache = { workspace = true }
ruff_db = { workspace = true, default-features = false, features = ["os"] }
@@ -48,7 +41,6 @@ colored = { workspace = true }
filetime = { workspace = true }
globwalk = { workspace = true }
ignore = { workspace = true }
indexmap = { workspace = true }
is-macro = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true }
@@ -69,12 +61,6 @@ tracing = { workspace = true, features = ["log"] }
walkdir = { workspace = true }
wild = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[dev-dependencies]
# Enable test rules during development
ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
@@ -90,5 +76,18 @@ ruff_python_trivia = { workspace = true }
tempfile = { workspace = true }
test-case = { workspace = true }
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[lints]
workspace = true

View File

@@ -10,7 +10,7 @@ use anyhow::bail;
use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Effects};
use clap::builder::{TypedValueParser, ValueParserFactory};
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, command};
use colored::Colorize;
use itertools::Itertools;
use path_absolutize::path_dedot;
@@ -167,7 +167,6 @@ pub enum AnalyzeCommand {
}
#[derive(Clone, Debug, clap::Parser)]
#[expect(clippy::struct_excessive_bools)]
pub struct AnalyzeGraphCommand {
/// List of files or directories to include.
#[clap(help = "List of files or directories to include [default: .]")]
@@ -194,12 +193,6 @@ pub struct AnalyzeGraphCommand {
/// Path to a virtual environment to use for resolving additional dependencies
#[arg(long)]
python: Option<PathBuf>,
/// Include imports that are only used for type checking (i.e., imports within `if TYPE_CHECKING:` blocks).
/// Use `--no-type-checking-imports` to exclude imports that are only used for type checking.
#[arg(long, overrides_with("no_type_checking_imports"))]
type_checking_imports: bool,
#[arg(long, overrides_with("type_checking_imports"), hide = true)]
no_type_checking_imports: bool,
}
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
@@ -846,10 +839,6 @@ impl AnalyzeGraphCommand {
string_imports_min_dots: self.min_dots,
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
target_version: self.target_version.map(ast::PythonVersion::from),
type_checking_imports: resolve_bool_arg(
self.type_checking_imports,
self.no_type_checking_imports,
),
..ExplicitConfigOverrides::default()
};
@@ -1346,7 +1335,6 @@ struct ExplicitConfigOverrides {
extension: Option<Vec<ExtensionPair>>,
detect_string_imports: Option<bool>,
string_imports_min_dots: Option<usize>,
type_checking_imports: Option<bool>,
}
impl ConfigurationTransformer for ExplicitConfigOverrides {
@@ -1437,9 +1425,6 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
if let Some(string_imports_min_dots) = &self.string_imports_min_dots {
config.analyze.string_imports_min_dots = Some(*string_imports_min_dots);
}
if let Some(type_checking_imports) = &self.type_checking_imports {
config.analyze.type_checking_imports = Some(*type_checking_imports);
}
config
}

View File

@@ -2,7 +2,6 @@ use crate::args::{AnalyzeGraphArgs, ConfigArguments};
use crate::resolve::resolve;
use crate::{ExitStatus, resolve_default_files};
use anyhow::Result;
use indexmap::IndexSet;
use log::{debug, warn};
use path_absolutize::CWD;
use ruff_db::system::{SystemPath, SystemPathBuf};
@@ -12,7 +11,7 @@ use ruff_linter::source_kind::SourceKind;
use ruff_linter::{warn_user, warn_user_once};
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
use rustc_hash::{FxBuildHasher, FxHashMap};
use rustc_hash::FxHashMap;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
@@ -60,34 +59,17 @@ pub(crate) fn analyze_graph(
})
.collect::<FxHashMap<_, _>>();
// Create a database from the source roots, combining configured `src` paths with detected
// package roots. Configured paths are added first so they take precedence, and duplicates
// are removed.
let mut src_roots: IndexSet<SystemPathBuf, FxBuildHasher> = IndexSet::default();
// Add configured `src` paths first (for precedence), filtering to only include existing
// directories.
src_roots.extend(
pyproject_config
.settings
.linter
.src
.iter()
.filter(|path| path.is_dir())
.filter_map(|path| SystemPathBuf::from_path_buf(path.clone()).ok()),
);
// Add detected package roots.
src_roots.extend(
package_roots
.values()
.filter_map(|package| package.as_deref())
.filter_map(|path| path.parent())
.filter_map(|path| SystemPathBuf::from_path_buf(path.to_path_buf()).ok()),
);
// Create a database from the source roots.
let src_roots = package_roots
.values()
.filter_map(|package| package.as_deref())
.filter_map(|package| package.parent())
.map(Path::to_path_buf)
.filter_map(|path| SystemPathBuf::from_path_buf(path).ok())
.collect();
let db = ModuleDb::from_src_roots(
src_roots.into_iter().collect(),
src_roots,
pyproject_config
.settings
.analyze
@@ -123,7 +105,6 @@ pub(crate) fn analyze_graph(
let settings = resolver.resolve(path);
let string_imports = settings.analyze.string_imports;
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
let type_checking_imports = settings.analyze.type_checking_imports;
// Skip excluded files.
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
@@ -186,7 +167,6 @@ pub(crate) fn analyze_graph(
&path,
package.as_deref(),
string_imports,
type_checking_imports,
)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");

View File

@@ -1305,7 +1305,7 @@ mod tests {
settings.add_filter(r"(Panicked at) [^:]+:\d+:\d+", "$1 <location>");
let _s = settings.bind_to_scope();
assert_snapshot!(str::from_utf8(&buf)?, @"
assert_snapshot!(str::from_utf8(&buf)?, @r"
io: test.py: Permission denied
--> test.py:1:1

View File

@@ -29,10 +29,10 @@ pub(crate) fn show_settings(
bail!("No files found under the given path");
};
let (settings, config_path) = resolver.resolve_with_path(&path);
let settings = resolver.resolve(&path);
writeln!(writer, "Resolved settings for: \"{}\"", path.display())?;
if let Some(settings_path) = config_path {
if let Some(settings_path) = pyproject_config.path.as_ref() {
writeln!(writer, "Settings path: \"{}\"", settings_path.display())?;
}
write!(writer, "{settings}")?;

View File

@@ -4,3 +4,4 @@ source: crates/ruff/src/commands/check.rs
/home/ferris/project/code.py:1:1: E902 Permission denied (os error 13)
/home/ferris/project/notebook.ipynb:1:1: E902 Permission denied (os error 13)
/home/ferris/project/pyproject.toml:1:1: E902 Permission denied (os error 13)

View File

@@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
use anyhow::Result;
use clap::CommandFactory;
use colored::Colorize;
use log::error;
use log::{error, warn};
use notify::{RecursiveMode, Watcher, recommended_watcher};
use args::{GlobalConfigArgs, ServerCommand};

View File

@@ -34,21 +34,9 @@ struct ExpandedStatistics<'a> {
code: Option<&'a SecondaryCode>,
name: &'static str,
count: usize,
#[serde(rename = "fixable")]
all_fixable: bool,
fixable_count: usize,
fixable: bool,
}
impl ExpandedStatistics<'_> {
fn any_fixable(&self) -> bool {
self.fixable_count > 0
}
}
/// Accumulator type for grouping diagnostics by code.
/// Format: (`code`, `representative_diagnostic`, `total_count`, `fixable_count`)
type DiagnosticGroup<'a> = (Option<&'a SecondaryCode>, &'a Diagnostic, usize, usize);
pub(crate) struct Printer {
format: OutputFormat,
log_level: LogLevel,
@@ -145,7 +133,7 @@ impl Printer {
if fixables.applicable > 0 {
writeln!(
writer,
"{fix_prefix} {} fixable with the `--fix` option.",
"{fix_prefix} {} fixable with the --fix option.",
fixables.applicable
)?;
}
@@ -268,41 +256,35 @@ impl Printer {
diagnostics: &Diagnostics,
writer: &mut dyn Write,
) -> Result<()> {
let required_applicability = self.unsafe_fixes.required_applicability();
let statistics: Vec<ExpandedStatistics> = diagnostics
.inner
.iter()
.sorted_by_key(|diagnostic| diagnostic.secondary_code())
.fold(vec![], |mut acc: Vec<DiagnosticGroup>, diagnostic| {
let is_fixable = diagnostic
.fix()
.is_some_and(|fix| fix.applies(required_applicability));
let code = diagnostic.secondary_code();
if let Some((prev_code, _prev_message, count, fixable_count)) = acc.last_mut() {
if *prev_code == code {
*count += 1;
if is_fixable {
*fixable_count += 1;
.map(|message| (message.secondary_code(), message))
.sorted_by_key(|(code, message)| (*code, message.fixable()))
.fold(
vec![],
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
if *prev_code == code {
*count += 1;
return acc;
}
return acc;
}
}
acc.push((code, diagnostic, 1, usize::from(is_fixable)));
acc
})
.iter()
.map(
|&(code, message, count, fixable_count)| ExpandedStatistics {
code,
name: message.name(),
count,
// Backward compatibility: `fixable` is true only when all violations are fixable.
// See: https://github.com/astral-sh/ruff/pull/21513
all_fixable: fixable_count == count,
fixable_count,
acc.push(((code, message), 1));
acc
},
)
.iter()
.map(|&((code, message), count)| ExpandedStatistics {
code,
name: message.name(),
count,
fixable: if let Some(fix) = message.fix() {
fix.applies(self.unsafe_fixes.required_applicability())
} else {
false
},
})
.sorted_by_key(|statistic| Reverse(statistic.count))
.collect();
@@ -326,14 +308,13 @@ impl Printer {
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
.max()
.unwrap();
let any_fixable = statistics.iter().any(ExpandedStatistics::any_fixable);
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
let all_fixable = format!("[{}] ", "*".cyan());
let partially_fixable = format!("[{}] ", "-".cyan());
let fixable = format!("[{}] ", "*".cyan());
let unfixable = "[ ] ";
// By default, we mimic Flake8's `--statistics` format.
for statistic in &statistics {
for statistic in statistics {
writeln!(
writer,
"{:>count_width$}\t{:<code_width$}\t{}{}",
@@ -345,10 +326,8 @@ impl Printer {
.red()
.bold(),
if any_fixable {
if statistic.all_fixable {
&all_fixable
} else if statistic.any_fixable() {
&partially_fixable
if statistic.fixable {
&fixable
} else {
unfixable
}

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
0.0.0

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
0.0.0 (53b0f5d92 2023-10-19)

View File

@@ -1,5 +1,6 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
0.0.0+24 (53b0f5d92 2023-10-19)

View File

@@ -1,6 +1,7 @@
---
source: crates/ruff/src/version.rs
expression: version
snapshot_kind: text
---
{
"version": "0.0.0",

View File

@@ -132,29 +132,29 @@ fn dependents() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("--direction").arg("dependents").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [],
"ruff/b.py": [
"ruff/a.py"
],
"ruff/c.py": [
"ruff/b.py"
],
"ruff/d.py": [
"ruff/c.py"
],
"ruff/e.py": [
"ruff/d.py"
]
}
assert_cmd_snapshot!(command().arg("--direction").arg("dependents").current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [],
"ruff/b.py": [
"ruff/a.py"
],
"ruff/c.py": [
"ruff/b.py"
],
"ruff/d.py": [
"ruff/c.py"
],
"ruff/e.py": [
"ruff/d.py"
]
}
----- stderr -----
"#);
----- stderr -----
"###);
});
Ok(())
@@ -184,21 +184,21 @@ fn string_detection() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
----- stderr -----
"#);
----- stderr -----
"###);
});
insta::with_settings!({
@@ -319,7 +319,7 @@ fn globs() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
@@ -340,7 +340,7 @@ fn globs() -> Result<()> {
}
----- stderr -----
"#);
"###);
});
Ok(())
@@ -368,7 +368,7 @@ fn exclude() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
@@ -381,7 +381,7 @@ fn exclude() -> Result<()> {
}
----- stderr -----
"#);
"###);
});
Ok(())
@@ -421,7 +421,7 @@ fn wildcard() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
@@ -443,7 +443,7 @@ fn wildcard() -> Result<()> {
}
----- stderr -----
"#);
"###);
});
Ok(())
@@ -639,7 +639,7 @@ fn venv() -> Result<()> {
}, {
assert_cmd_snapshot!(
command().args(["--python", "none"]).arg("packages/albatross").current_dir(&root),
@"
@r"
success: false
exit_code: 2
----- stdout -----
@@ -695,7 +695,7 @@ fn notebook_basic() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
@@ -708,122 +708,7 @@ fn notebook_basic() -> Result<()> {
}
----- stderr -----
"#);
});
Ok(())
}
/// Test that the `src` configuration option is respected.
///
/// This is useful for monorepos where there are multiple source directories that need to be
/// included in the module resolution search path.
#[test]
fn src_option() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
// Create a lib directory with a package.
root.child("lib")
.child("mylib")
.child("__init__.py")
.write_str("def helper(): pass")?;
// Create an app directory with a file that imports from mylib.
root.child("app").child("__init__.py").write_str("")?;
root.child("app")
.child("main.py")
.write_str("from mylib import helper")?;
// Without src configured, the import from mylib won't resolve.
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("app").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"app/__init__.py": [],
"app/main.py": []
}
----- stderr -----
"#);
});
// With src = ["lib"], the import should resolve.
root.child("ruff.toml").write_str(indoc::indoc! {r#"
src = ["lib"]
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("app").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"app/__init__.py": [],
"app/main.py": [
"lib/mylib/__init__.py"
]
}
----- stderr -----
"#);
});
Ok(())
}
/// Test that glob patterns in `src` are expanded.
#[test]
fn src_glob_expansion() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
// Create multiple lib directories with packages.
root.child("libs")
.child("lib_a")
.child("pkg_a")
.child("__init__.py")
.write_str("def func_a(): pass")?;
root.child("libs")
.child("lib_b")
.child("pkg_b")
.child("__init__.py")
.write_str("def func_b(): pass")?;
// Create an app that imports from both packages.
root.child("app").child("__init__.py").write_str("")?;
root.child("app")
.child("main.py")
.write_str("from pkg_a import func_a\nfrom pkg_b import func_b")?;
// Use a glob pattern to include all lib directories.
root.child("ruff.toml").write_str(indoc::indoc! {r#"
src = ["libs/*"]
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().arg("app").current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"app/__init__.py": [],
"app/main.py": [
"libs/lib_a/pkg_a/__init__.py",
"libs/lib_b/pkg_b/__init__.py"
]
}
----- stderr -----
"#);
"###);
});
Ok(())
@@ -880,7 +765,7 @@ fn notebook_with_magic() -> Result<()> {
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
@@ -893,7 +778,7 @@ fn notebook_with_magic() -> Result<()> {
}
----- stderr -----
"#);
"###);
});
Ok(())

View File

@@ -1,193 +0,0 @@
use std::process::Command;
use insta_cmd::assert_cmd_snapshot;
use crate::CliTest;
#[test]
fn type_checking_imports() -> anyhow::Result<()> {
let test = AnalyzeTest::with_files([
("ruff/__init__.py", ""),
(
"ruff/a.py",
r#"
from typing import TYPE_CHECKING
import ruff.b
if TYPE_CHECKING:
import ruff.c
"#,
),
(
"ruff/b.py",
r#"
if TYPE_CHECKING:
from ruff import c
"#,
),
("ruff/c.py", ""),
])?;
assert_cmd_snapshot!(test.command(), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py",
"ruff/c.py"
],
"ruff/b.py": [
"ruff/c.py"
],
"ruff/c.py": []
}
----- stderr -----
"#);
assert_cmd_snapshot!(
test.command()
.arg("--no-type-checking-imports"),
@r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
----- stderr -----
"#
);
Ok(())
}
#[test]
fn type_checking_imports_from_config() -> anyhow::Result<()> {
let test = AnalyzeTest::with_files([
("ruff/__init__.py", ""),
(
"ruff/a.py",
r#"
from typing import TYPE_CHECKING
import ruff.b
if TYPE_CHECKING:
import ruff.c
"#,
),
(
"ruff/b.py",
r#"
if TYPE_CHECKING:
from ruff import c
"#,
),
("ruff/c.py", ""),
(
"ruff.toml",
r#"
[analyze]
type-checking-imports = false
"#,
),
])?;
assert_cmd_snapshot!(test.command(), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [],
"ruff/c.py": []
}
----- stderr -----
"#);
test.write_file(
"ruff.toml",
r#"
[analyze]
type-checking-imports = true
"#,
)?;
assert_cmd_snapshot!(test.command(), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py",
"ruff/c.py"
],
"ruff/b.py": [
"ruff/c.py"
],
"ruff/c.py": []
}
----- stderr -----
"#
);
Ok(())
}
struct AnalyzeTest {
cli_test: CliTest,
}
impl AnalyzeTest {
pub(crate) fn new() -> anyhow::Result<Self> {
Ok(Self {
cli_test: CliTest::with_settings(|_, mut settings| {
settings.add_filter(r#"\\\\"#, "/");
settings
})?,
})
}
fn with_files<'a>(files: impl IntoIterator<Item = (&'a str, &'a str)>) -> anyhow::Result<Self> {
let case = Self::new()?;
case.write_files(files)?;
Ok(case)
}
#[expect(unused)]
fn with_file(path: impl AsRef<std::path::Path>, content: &str) -> anyhow::Result<Self> {
let fixture = Self::new()?;
fixture.write_file(path, content)?;
Ok(fixture)
}
fn command(&self) -> Command {
let mut command = self.cli_test.command();
command.arg("analyze").arg("graph").arg("--preview");
command
}
}
impl std::ops::Deref for AnalyzeTest {
type Target = CliTest;
fn deref(&self) -> &Self::Target {
&self.cli_test
}
}

View File

@@ -51,7 +51,7 @@ fn default_files() -> Result<()> {
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--check"), @"
.arg("--check"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -71,7 +71,7 @@ fn format_warn_stdin_filename_with_files() -> Result<()> {
assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--stdin-filename", "foo.py"])
.arg("foo.py")
.pass_stdin("foo = 1"), @"
.pass_stdin("foo = 1"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -87,7 +87,7 @@ fn format_warn_stdin_filename_with_files() -> Result<()> {
fn nonexistent_config_file() -> Result<()> {
let test = CliTest::new()?;
assert_cmd_snapshot!(test.format_command()
.args(["--config", "foo.toml", "."]), @"
.args(["--config", "foo.toml", "."]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -111,7 +111,7 @@ fn nonexistent_config_file() -> Result<()> {
fn config_override_rejected_if_invalid_toml() -> Result<()> {
let test = CliTest::new()?;
assert_cmd_snapshot!(test.format_command()
.args(["--config", "foo = bar", "."]), @"
.args(["--config", "foo = bar", "."]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -145,7 +145,7 @@ fn too_many_config_files() -> Result<()> {
.arg("ruff.toml")
.arg("--config")
.arg("ruff2.toml")
.arg("."), @"
.arg("."), @r"
success: false
exit_code: 2
----- stdout -----
@@ -168,7 +168,7 @@ fn config_file_and_isolated() -> Result<()> {
.arg("--isolated")
.arg("--config")
.arg("ruff.toml")
.arg("."), @"
.arg("."), @r"
success: false
exit_code: 2
----- stdout -----
@@ -390,7 +390,7 @@ fn mixed_line_endings() -> Result<()> {
assert_cmd_snapshot!(test.format_command()
.arg("--diff")
.arg("--isolated")
.arg("."), @"
.arg("."), @r"
success: true
exit_code: 0
----- stdout -----
@@ -446,7 +446,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
.arg("test.py")
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @"
.arg("."), @r"
success: false
exit_code: 1
----- stdout -----
@@ -469,7 +469,7 @@ fn deduplicate_directory_and_explicit_file() -> Result<()> {
.arg("--check")
.arg(".")
.arg("main.py"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -495,7 +495,7 @@ from module import =
assert_cmd_snapshot!(test.format_command()
.arg("--check")
.arg("--isolated")
.arg("main.py"), @"
.arg("main.py"), @r"
success: false
exit_code: 2
----- stdout -----
@@ -522,7 +522,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--check")
.arg("main.py"), @"
.arg("main.py"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -534,7 +534,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("main.py"), @"
.arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -545,7 +545,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("main.py"), @"
.arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -614,7 +614,7 @@ fn output_format_notebook() -> Result<()> {
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--preview", "--check"]).arg(path),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -672,7 +672,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-format")
.arg("main.py"), @"
.arg("main.py"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -685,7 +685,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-format")
.arg("main.py"), @"
.arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -701,7 +701,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-fix")
.arg("main.py"), @"
.arg("main.py"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -714,7 +714,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command()
.arg("--isolated")
.arg("--exit-non-zero-on-fix")
.arg("main.py"), @"
.arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -771,7 +771,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should not be formatted because of --force-exclude
.arg("test.py")
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @"
.arg("."), @r"
success: false
exit_code: 1
----- stdout -----
@@ -931,7 +931,7 @@ tab-size = 2
.pass_stdin(r"
if True:
pass
"), @"
"), @r"
success: false
exit_code: 2
----- stdout -----
@@ -1144,7 +1144,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command()
.arg("--config")
.arg("ruff.toml")
.arg("test.py"), @"
.arg("test.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -1184,7 +1184,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command()
.arg("--config")
.arg("ruff.toml")
.arg("test.py"), @"
.arg("test.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -1216,7 +1216,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command()
.arg("--config")
.arg("ruff.toml")
.arg("test.py"), @"
.arg("test.py"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -1246,7 +1246,7 @@ fn test_diff() -> Result<()> {
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff"]).args(paths),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1311,7 +1311,7 @@ fn test_diff_no_change() -> Result<()> {
let paths = [fixtures.join("unformatted.py")];
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff"]).args(paths),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1341,7 +1341,7 @@ fn test_diff_stdin_unformatted() -> Result<()> {
test.format_command()
.args(["--isolated", "--diff", "-", "--stdin-filename", "unformatted.py"])
.pass_stdin(unformatted),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1366,7 +1366,7 @@ fn test_diff_stdin_formatted() -> Result<()> {
let unformatted = fs::read(fixtures.join("formatted.py")).unwrap();
assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff", "-"]).pass_stdin(unformatted),
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -1873,7 +1873,7 @@ include = ["*.ipy"]
assert_cmd_snapshot!(test.format_command()
.args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"])
.arg("."), @"
.arg("."), @r"
success: false
exit_code: 2
----- stdout -----
@@ -1938,7 +1938,7 @@ include = ["*.ipy"]
assert_cmd_snapshot!(test.format_command()
.args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"])
.arg("."), @"
.arg("."), @r"
success: true
exit_code: 0
----- stdout -----
@@ -2021,7 +2021,7 @@ def file2(arg1, arg2,):
assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--range=1:8-1:15"])
.arg("file1.py")
.arg("file2.py"), @"
.arg("file2.py"), @r"
success: false
exit_code: 2
----- stdout -----
@@ -2068,7 +2068,7 @@ fn range_start_larger_than_end() -> Result<()> {
def foo(arg1, arg2,):
print("Shouldn't format this" )
"#), @"
"#), @r"
success: false
exit_code: 2
----- stdout -----
@@ -2168,7 +2168,7 @@ fn range_missing_line() -> Result<()> {
def foo(arg1, arg2,):
print("Should format this" )
"#), @"
"#), @r"
success: false
exit_code: 2
----- stdout -----
@@ -2192,7 +2192,7 @@ fn zero_line_number() -> Result<()> {
def foo(arg1, arg2,):
print("Should format this" )
"#), @"
"#), @r"
success: false
exit_code: 2
----- stdout -----
@@ -2217,7 +2217,7 @@ fn column_and_line_zero() -> Result<()> {
def foo(arg1, arg2,):
print("Should format this" )
"#), @"
"#), @r"
success: false
exit_code: 2
----- stdout -----
@@ -2274,7 +2274,7 @@ fn range_formatting_notebook() -> Result<()> {
"nbformat": 4,
"nbformat_minor": 5
}
"#), @"
"#), @r"
success: false
exit_code: 2
----- stdout -----
@@ -2355,7 +2355,7 @@ fn cookiecutter_globbing() -> Result<()> {
])?;
assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--diff", "."]), @"
.args(["--isolated", "--diff", "."]), @r"
success: true
exit_code: 0
----- stdout -----
@@ -2374,7 +2374,7 @@ fn stable_output_format_warning() -> Result<()> {
test.format_command()
.args(["--output-format=full", "-"])
.pass_stdin(""),
@"
@r"
success: true
exit_code: 0
----- stdout -----

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,6 @@ use std::{
};
use tempfile::TempDir;
mod analyze_graph;
mod format;
mod lint;
@@ -63,7 +62,9 @@ impl CliTest {
files: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> anyhow::Result<Self> {
let case = Self::new()?;
case.write_files(files)?;
for file in files {
case.write_file(file.0, file.1)?;
}
Ok(case)
}
@@ -152,16 +153,6 @@ impl CliTest {
Ok(())
}
pub(crate) fn write_files<'a>(
&self,
files: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> Result<()> {
for file in files {
self.write_file(file.0, file.1)?;
}
Ok(())
}
/// Returns the path to the test directory root.
pub(crate) fn root(&self) -> &Path {
&self.project_dir

View File

@@ -14,6 +14,6 @@ info:
success: false
exit_code: 1
----- stdout -----
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,endLine=2::input.py:1:1: unformatted: File would be reformatted
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,col=1,endLine=2,endColumn=1::input.py:1:1: unformatted: File would be reformatted
----- stderr -----

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:
@@ -17,6 +17,7 @@ info:
- "--fix"
- "-"
stdin: "1"
snapshot_kind: text
---
success: false
exit_code: 2

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -12,6 +12,7 @@ info:
- "--target-version"
- py39
- input.py
snapshot_kind: text
---
success: false
exit_code: 1

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -9,6 +9,7 @@ info:
- concise
- "--show-settings"
- test.py
snapshot_kind: text
---
success: true
exit_code: 0
@@ -125,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +262,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -284,6 +284,5 @@ analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -12,6 +12,7 @@ info:
- UP007
- test.py
- "-"
snapshot_kind: text
---
success: true
exit_code: 0
@@ -127,7 +128,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -263,7 +264,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -286,6 +286,5 @@ analyze.target_version = 3.11
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -13,6 +13,7 @@ info:
- UP007
- test.py
- "-"
snapshot_kind: text
---
success: true
exit_code: 0
@@ -129,7 +130,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -265,7 +266,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -288,6 +288,5 @@ analyze.target_version = 3.11
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -14,6 +14,7 @@ info:
- py310
- test.py
- "-"
snapshot_kind: text
---
success: true
exit_code: 0
@@ -129,7 +130,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -265,7 +266,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -288,6 +288,5 @@ analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -11,6 +11,7 @@ info:
- "--select"
- UP007
- foo/test.py
snapshot_kind: text
---
success: true
exit_code: 0
@@ -126,7 +127,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -262,7 +263,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -285,6 +285,5 @@ analyze.target_version = 3.11
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -11,12 +11,12 @@ info:
- "--select"
- UP007
- foo/test.py
snapshot_kind: text
---
success: true
exit_code: 0
----- stdout -----
Resolved settings for: "[TMP]/foo/test.py"
Settings path: "[TMP]/foo/pyproject.toml"
# General Settings
cache_dir = "[TMP]/foo/.ruff_cache"
@@ -127,7 +127,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -263,7 +263,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -286,6 +285,5 @@ analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -284,6 +283,5 @@ analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -284,6 +283,5 @@ analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -9,6 +9,7 @@ info:
- concise
- test.py
- "--show-settings"
snapshot_kind: text
---
success: true
exit_code: 0
@@ -125,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +262,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -284,6 +284,5 @@ analyze.target_version = 3.11
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/cli/lint.rs
source: crates/ruff/tests/lint.rs
info:
program: ruff
args:

View File

@@ -18,13 +18,13 @@ fn check_in_deleted_directory_errors() {
set_current_dir(&temp_path).unwrap();
drop(temp_dir);
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)).arg("check"), @"
success: false
exit_code: 2
----- stdout -----
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)).arg("check"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Working directory does not exist
");
----- stderr -----
ruff failed
Cause: Working directory does not exist
"###);
}

View File

@@ -97,7 +97,7 @@ impl<'a> RuffCheck<'a> {
fn stdin_success() {
let mut cmd = RuffCheck::default().args([]).build();
assert_cmd_snapshot!(cmd
.pass_stdin(""), @"
.pass_stdin(""), @r"
success: true
exit_code: 0
----- stdout -----
@@ -111,7 +111,7 @@ fn stdin_success() {
fn stdin_error() {
let mut cmd = RuffCheck::default().args([]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @"
.pass_stdin("import os\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -136,7 +136,7 @@ fn stdin_filename() {
.args(["--stdin-filename", "F401.py"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @"
.pass_stdin("import os\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -172,7 +172,7 @@ import bar # unused import
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--isolated", "--no-cache", "--select", "F401"]).current_dir(tempdir.path()), @"
.args(["check", "--isolated", "--no-cache", "--select", "F401"]).current_dir(tempdir.path()), @r"
success: false
exit_code: 1
----- stdout -----
@@ -208,7 +208,7 @@ fn check_warn_stdin_filename_with_files() {
.filename("foo.py")
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @"
.pass_stdin("import os\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -235,7 +235,7 @@ fn stdin_source_type_py() {
.args(["--stdin-filename", "TCH.py"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @"
.pass_stdin("import os\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -261,7 +261,7 @@ fn stdin_source_type_pyi() {
.args(["--stdin-filename", "TCH.pyi", "--select", "TCH"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @"
.pass_stdin("import os\n"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -294,7 +294,7 @@ fn stdin_json() {
fn stdin_fix_py() {
let mut cmd = RuffCheck::default().args(["--fix"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\nimport sys\n\nprint(sys.version)\n"), @"
.pass_stdin("import os\nimport sys\n\nprint(sys.version)\n"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -572,7 +572,7 @@ fn stdin_override_parser_ipynb() {
},
"nbformat": 4,
"nbformat_minor": 5
}"#), @"
}"#), @r"
success: false
exit_code: 1
----- stdout -----
@@ -610,7 +610,7 @@ fn stdin_override_parser_py() {
])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\n"), @"
.pass_stdin("import os\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -633,7 +633,7 @@ fn stdin_override_parser_py() {
fn stdin_fix_when_not_fixable_should_still_print_contents() {
let mut cmd = RuffCheck::default().args(["--fix"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n"), @"
.pass_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n"), @r###"
success: false
exit_code: 1
----- stdout -----
@@ -654,14 +654,14 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
Found 2 errors (1 fixed, 1 remaining).
");
"###);
}
#[test]
fn stdin_fix_when_no_issues_should_still_print_contents() {
let mut cmd = RuffCheck::default().args(["--fix"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("import sys\n\nprint(sys.version)\n"), @"
.pass_stdin("import sys\n\nprint(sys.version)\n"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -805,7 +805,7 @@ fn stdin_format_jupyter() {
fn stdin_parse_error() {
let mut cmd = RuffCheck::default().build();
assert_cmd_snapshot!(cmd
.pass_stdin("from foo import\n"), @"
.pass_stdin("from foo import\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -826,7 +826,7 @@ fn stdin_parse_error() {
fn stdin_multiple_parse_error() {
let mut cmd = RuffCheck::default().build();
assert_cmd_snapshot!(cmd
.pass_stdin("from foo import\nbar =\n"), @"
.pass_stdin("from foo import\nbar =\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -857,7 +857,7 @@ fn parse_error_not_included() {
// Parse errors are always shown
let mut cmd = RuffCheck::default().args(["--select=I"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("foo =\n"), @"
.pass_stdin("foo =\n"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -878,7 +878,7 @@ fn parse_error_not_included() {
fn full_output_preview() {
let mut cmd = RuffCheck::default().args(["--preview"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("l = 1"), @"
.pass_stdin("l = 1"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -907,7 +907,7 @@ preview = true
",
)?;
let mut cmd = RuffCheck::default().config(&pyproject_toml).build();
assert_cmd_snapshot!(cmd.pass_stdin("l = 1"), @"
assert_cmd_snapshot!(cmd.pass_stdin("l = 1"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -929,7 +929,7 @@ preview = true
fn full_output_format() {
let mut cmd = RuffCheck::default().output_format("full").build();
assert_cmd_snapshot!(cmd
.pass_stdin("l = 1"), @"
.pass_stdin("l = 1"), @r"
success: false
exit_code: 1
----- stdout -----
@@ -967,7 +967,7 @@ fn rule_f401_output_text() {
#[test]
fn rule_invalid_rule_name() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @"
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -981,7 +981,7 @@ fn rule_invalid_rule_name() {
#[test]
fn rule_invalid_rule_name_output_json() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @"
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -995,7 +995,7 @@ fn rule_invalid_rule_name_output_json() {
#[test]
fn rule_invalid_rule_name_output_text() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @"
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -1016,7 +1016,7 @@ fn show_statistics() {
.pass_stdin(r#"
def mvce(keys, values):
return {key: value for key, value in zip(keys, values)}
"#), @"
"#), @r"
success: false
exit_code: 1
----- stdout -----
@@ -1037,13 +1037,13 @@ fn show_statistics_unsafe_fixes() {
.pass_stdin(r#"
def mvce(keys, values):
return {key: value for key, value in zip(keys, values)}
"#), @"
"#), @r"
success: false
exit_code: 1
----- stdout -----
1 C416 [*] unnecessary-comprehension
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
");
@@ -1073,8 +1073,7 @@ def mvce(keys, values):
"code": "C416",
"name": "unnecessary-comprehension",
"count": 1,
"fixable": false,
"fixable_count": 0
"fixable": false
}
]
@@ -1107,8 +1106,7 @@ def mvce(keys, values):
"code": "C416",
"name": "unnecessary-comprehension",
"count": 1,
"fixable": true,
"fixable_count": 1
"fixable": true
}
]
@@ -1116,54 +1114,6 @@ def mvce(keys, values):
"#);
}
#[test]
fn show_statistics_json_partial_fix() {
let mut cmd = RuffCheck::default()
.args([
"--select",
"UP035",
"--statistics",
"--output-format",
"json",
])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("from typing import List, AsyncGenerator"), @r#"
success: false
exit_code: 1
----- stdout -----
[
{
"code": "UP035",
"name": "deprecated-import",
"count": 2,
"fixable": false,
"fixable_count": 1
}
]
----- stderr -----
"#);
}
#[test]
fn show_statistics_partial_fix() {
let mut cmd = RuffCheck::default()
.args(["--select", "UP035", "--statistics"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("from typing import List, AsyncGenerator"), @"
success: false
exit_code: 1
----- stdout -----
2 UP035 [-] deprecated-import
Found 2 errors.
[*] 1 fixable with the `--fix` option.
----- stderr -----
");
}
#[test]
fn show_statistics_syntax_errors() {
let mut cmd = RuffCheck::default()
@@ -1173,7 +1123,7 @@ fn show_statistics_syntax_errors() {
// ParseError
assert_cmd_snapshot!(
cmd.pass_stdin("x ="),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1186,7 +1136,7 @@ fn show_statistics_syntax_errors() {
// match before 3.10, UnsupportedSyntaxError
assert_cmd_snapshot!(
cmd.pass_stdin("match 2:\n case 1: ..."),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1199,7 +1149,7 @@ fn show_statistics_syntax_errors() {
// rebound comprehension variable, SemanticSyntaxError
assert_cmd_snapshot!(
cmd.pass_stdin("[x := 1 for x in range(0)]"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1216,7 +1166,7 @@ fn preview_enabled_prefix() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF9", "--output-format=concise", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1238,7 +1188,7 @@ fn preview_enabled_all() {
let mut cmd = RuffCheck::default()
.args(["--select", "ALL", "--output-format=concise", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1265,7 +1215,7 @@ fn preview_enabled_direct() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF911", "--output-format=concise", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1282,7 +1232,7 @@ fn preview_disabled_direct() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF911", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1299,7 +1249,7 @@ fn preview_disabled_prefix_empty() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF91", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1316,7 +1266,7 @@ fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
let mut cmd = RuffCheck::default()
.args(["--ignore", "RUF9", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1332,7 +1282,7 @@ fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
let mut cmd = RuffCheck::default()
.args(["--fixable", "RUF9", "--output-format=concise"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1354,7 +1304,7 @@ fn preview_group_selector() {
])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("I=42\n"), @"
.pass_stdin("I=42\n"), @r"
success: false
exit_code: 2
----- stdout -----
@@ -1379,7 +1329,7 @@ fn preview_enabled_group_ignore() {
"--output-format=concise",
])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1400,7 +1350,7 @@ fn preview_enabled_group_ignore() {
fn removed_direct() {
// Selection of a removed rule should fail
let mut cmd = RuffCheck::default().args(["--select", "RUF931"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 2
----- stdout -----
@@ -1418,7 +1368,7 @@ fn removed_direct_multiple() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF930", "--select", "RUF931"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 2
----- stdout -----
@@ -1436,7 +1386,7 @@ fn removed_indirect() {
// Selection _including_ a removed rule without matching should not fail
// nor should the rule be used
let mut cmd = RuffCheck::default().args(["--select", "RUF93"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1449,7 +1399,7 @@ fn removed_indirect() {
#[test]
fn removed_ignore_direct() {
let mut cmd = RuffCheck::default().args(["--ignore", "UP027"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1466,7 +1416,7 @@ fn removed_ignore_multiple_direct() {
let mut cmd = RuffCheck::default()
.args(["--ignore", "UP027", "--ignore", "PLR1706"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1482,7 +1432,7 @@ fn removed_ignore_multiple_direct() {
#[test]
fn removed_ignore_remapped_direct() {
let mut cmd = RuffCheck::default().args(["--ignore", "PGH001"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1498,7 +1448,7 @@ fn removed_ignore_indirect() {
// `PLR170` includes removed rules but should not select or warn
// since it is not a "direct" selection
let mut cmd = RuffCheck::default().args(["--ignore", "PLR170"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1512,7 +1462,7 @@ fn removed_ignore_indirect() {
fn redirect_direct() {
// Selection of a redirected rule directly should use the new rule and warn
let mut cmd = RuffCheck::default().args(["--select", "RUF940"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1531,7 +1481,7 @@ fn redirect_indirect() {
// Selection _including_ a redirected rule without matching should not fail
// nor should the rule be used
let mut cmd = RuffCheck::default().args(["--select", "RUF94"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1546,7 +1496,7 @@ fn redirect_prefix() {
// Selection using a redirected prefix should switch to all rules in the
// new prefix
let mut cmd = RuffCheck::default().args(["--select", "RUF96"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1565,7 +1515,7 @@ fn deprecated_direct() {
// Selection of a deprecated rule without preview enabled should still work
// but a warning should be displayed
let mut cmd = RuffCheck::default().args(["--select", "RUF920"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1586,7 +1536,7 @@ fn deprecated_multiple_direct() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--select", "RUF921"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 1
----- stdout -----
@@ -1609,7 +1559,7 @@ fn deprecated_indirect() {
// `RUF92` includes deprecated rules but should not warn
// since it is not a "direct" selection
let mut cmd = RuffCheck::default().args(["--select", "RUF92"]).build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1625,7 +1575,7 @@ fn deprecated_direct_preview_enabled() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 2
----- stdout -----
@@ -1642,7 +1592,7 @@ fn deprecated_indirect_preview_enabled() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF92", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: true
exit_code: 0
----- stdout -----
@@ -1659,7 +1609,7 @@ fn deprecated_multiple_direct_preview_enabled() {
let mut cmd = RuffCheck::default()
.args(["--select", "RUF920", "--select", "RUF921", "--preview"])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 2
----- stdout -----
@@ -1720,7 +1670,7 @@ fn unreadable_dir() -> Result<()> {
.filename(unreadable_dir.to_str().unwrap())
.args([])
.build();
assert_cmd_snapshot!(cmd, @"
assert_cmd_snapshot!(cmd, @r###"
success: true
exit_code: 0
----- stdout -----
@@ -1728,7 +1678,7 @@ fn unreadable_dir() -> Result<()> {
----- stderr -----
warning: Encountered error: Permission denied (os error 13)
");
"###);
Ok(())
}
@@ -1758,7 +1708,7 @@ fn check_input_from_argfile() -> Result<()> {
(file_a_path.display().to_string().as_str(), "/path/to/a.py"),
]}, {
assert_cmd_snapshot!(cmd
.pass_stdin(""), @"
.pass_stdin(""), @r"
success: false
exit_code: 1
----- stdout -----
@@ -1787,17 +1737,17 @@ fn missing_argfile_reports_error() {
insta::with_settings!({filters => vec![
("The system cannot find the file specified.", "No such file or directory")
]}, {
assert_cmd_snapshot!(cmd, @"
success: false
exit_code: 2
----- stdout -----
assert_cmd_snapshot!(cmd, @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to read CLI arguments from files
Cause: failed to open file `!.txt`
Cause: No such file or directory (os error 2)
");
----- stderr -----
ruff failed
Cause: Failed to read CLI arguments from files
Cause: failed to open file `!.txt`
Cause: No such file or directory (os error 2)
");
});
}
@@ -1807,7 +1757,7 @@ fn check_hints_hidden_unsafe_fixes() {
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1829,7 +1779,7 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
let mut cmd = RuffCheck::default().args(["--select", "RUF902"]).build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1849,7 +1799,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
.args(["--select", "RUF901,RUF902", "--no-unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1860,7 +1810,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
--> -:1:1
Found 2 errors.
[*] 1 fixable with the `--fix` option.
[*] 1 fixable with the --fix option.
----- stderr -----
");
@@ -1873,7 +1823,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_with_no_safe_fixes_when_disabled() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1892,7 +1842,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1903,7 +1853,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
--> -:1:1
Found 2 errors.
[*] 2 fixable with the `--fix` option.
[*] 2 fixable with the --fix option.
----- stderr -----
");
@@ -1915,7 +1865,7 @@ fn fix_applies_safe_fixes_by_default() {
.args(["--select", "RUF901,RUF902", "--fix"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1936,7 +1886,7 @@ fn fix_applies_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--fix", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -1955,7 +1905,7 @@ fn fix_does_not_apply_display_only_fixes() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1975,7 +1925,7 @@ fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1994,7 +1944,7 @@ fn fix_only_unsafe_fixes_available() {
.args(["--select", "RUF902", "--fix"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2014,7 +1964,7 @@ fn fix_only_flag_applies_safe_fixes_by_default() {
.args(["--select", "RUF901,RUF902", "--fix-only"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2031,7 +1981,7 @@ fn fix_only_flag_applies_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--fix-only", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2049,7 +1999,7 @@ fn diff_shows_safe_fixes_by_default() {
.args(["--select", "RUF901,RUF902", "--diff"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2069,7 +2019,7 @@ fn diff_shows_unsafe_fixes_with_opt_in() {
.args(["--select", "RUF901,RUF902", "--diff", "--unsafe-fixes"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2091,7 +2041,7 @@ fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2106,7 +2056,7 @@ fn diff_only_unsafe_fixes_available() {
.args(["--select", "RUF902", "--diff"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2134,7 +2084,7 @@ extend-unsafe-fixes = ["RUF901"]
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2170,7 +2120,7 @@ extend-safe-fixes = ["RUF902"]
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2208,7 +2158,7 @@ extend-safe-fixes = ["RUF902"]
.args(["--select", "RUF901,RUF902"])
.build();
assert_cmd_snapshot!(cmd,
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2248,7 +2198,7 @@ extend-safe-fixes = ["RUF9"]
.build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\nprint(str('foo'))\nisinstance(x, (int, str))\n"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2307,7 +2257,7 @@ def log(x, base) -> float:
.args(["--select", "D41"])
.build();
assert_cmd_snapshot!(cmd
.pass_stdin(stdin), @"
.pass_stdin(stdin), @r"
success: true
exit_code: 0
----- stdout -----
@@ -2360,7 +2310,7 @@ select = ["RUF017"]
let mut cmd = RuffCheck::default().config(&ruff_toml).build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = [1, 2, 3]\ny = [4, 5, 6]\nsum([x, y], [])"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2401,7 +2351,7 @@ unfixable = ["RUF"]
let mut cmd = RuffCheck::default().config(&ruff_toml).build();
assert_cmd_snapshot!(cmd
.pass_stdin("x = [1, 2, 3]\ny = [4, 5, 6]\nsum([x, y], [])"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2431,7 +2381,7 @@ fn pyproject_toml_stdin_syntax_error() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2457,7 +2407,7 @@ fn pyproject_toml_stdin_schema_error() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2484,7 +2434,7 @@ fn pyproject_toml_stdin_no_applicable_rules_selected() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project"),
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2503,7 +2453,7 @@ fn pyproject_toml_stdin_no_applicable_rules_selected_2() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project"),
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2522,7 +2472,7 @@ fn pyproject_toml_stdin_no_errors() {
assert_cmd_snapshot!(
cmd.pass_stdin(r#"[project]\nname = "ruff"\nversion = "0.0.0""#),
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2547,7 +2497,7 @@ fn pyproject_toml_stdin_schema_error_fix() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@"
@r"
success: false
exit_code: 1
----- stdout -----
@@ -2581,7 +2531,7 @@ fn pyproject_toml_stdin_schema_error_fix_only() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@"
@r"
success: true
exit_code: 0
----- stdout -----
@@ -2607,7 +2557,7 @@ fn pyproject_toml_stdin_schema_error_fix_diff() {
assert_cmd_snapshot!(
cmd.pass_stdin("[project]\nname = 1"),
@"
@r"
success: true
exit_code: 0
----- stdout -----

View File

@@ -29,7 +29,7 @@ fn check_project_include_defaults() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @"
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r"
success: true
exit_code: 0
----- stdout -----
@@ -53,7 +53,7 @@ fn check_project_respects_direct_paths() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files", "b.py"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @"
.args(["check", "--show-files", "b.py"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r"
success: true
exit_code: 0
----- stdout -----
@@ -72,7 +72,7 @@ fn check_project_respects_subdirectory_includes() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files", "subdirectory"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @"
.args(["check", "--show-files", "subdirectory"]).current_dir(Path::new("./resources/test/fixtures/include-test")), @r"
success: true
exit_code: 0
----- stdout -----
@@ -91,7 +91,7 @@ fn check_project_from_project_subdirectory_respects_includes() {
filters => TEST_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test/subdirectory")), @"
.args(["check", "--show-files"]).current_dir(Path::new("./resources/test/fixtures/include-test/subdirectory")), @r"
success: true
exit_code: 0
----- stdout -----

View File

@@ -50,56 +50,6 @@ ignore = [
Ok(())
}
#[test]
fn display_settings_from_nested_directory() -> anyhow::Result<()> {
let tempdir = TempDir::new().context("Failed to create temp directory.")?;
// Tempdir path's on macos are symlinks, which doesn't play nicely with
// our snapshot filtering.
let project_dir =
dunce::canonicalize(tempdir.path()).context("Failed to canonical tempdir path.")?;
// Root pyproject.toml.
std::fs::write(
project_dir.join("pyproject.toml"),
r#"
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F"]
"#,
)?;
// Create a subdirectory with its own pyproject.toml.
let subdir = project_dir.join("subdir");
std::fs::create_dir(&subdir)?;
std::fs::write(
subdir.join("pyproject.toml"),
r#"
[tool.ruff]
line-length = 120
[tool.ruff.lint]
select = ["E", "F", "I"]
"#,
)?;
std::fs::write(subdir.join("test.py"), r#"import os"#).context("Failed to write test.py.")?;
insta::with_settings!({filters => vec![
(&*tempdir_filter(&project_dir), "<temp_dir>/"),
(r#"\\(\w\w|\s|\.|")"#, "/$1"),
]}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(["check", "--show-settings", "subdir/test.py"])
.current_dir(&project_dir));
});
Ok(())
}
fn tempdir_filter(project_dir: &Path) -> String {
format!(r#"{}\\?/?"#, regex::escape(project_dir.to_str().unwrap()))
}

View File

@@ -3,12 +3,11 @@ source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- check
- "-"
- "--isolated"
- "--no-cache"
- "--output-format"
- json
- "--no-cache"
- "--isolated"
- "-"
- "--stdin-filename"
- F401.py
stdin: "import os\n"
@@ -52,3 +51,4 @@ exit_code: 1
}
]
----- stderr -----

View File

@@ -238,7 +238,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -374,7 +374,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
@@ -397,6 +396,5 @@ analyze.target_version = 3.7
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -1,410 +0,0 @@
---
source: crates/ruff/tests/show_settings.rs
info:
program: ruff
args:
- check
- "--show-settings"
- subdir/test.py
---
success: true
exit_code: 0
----- stdout -----
Resolved settings for: "<temp_dir>/subdir/test.py"
Settings path: "<temp_dir>/subdir/pyproject.toml"
# General Settings
cache_dir = "<temp_dir>/subdir/.ruff_cache"
fix = false
fix_only = false
output_format = full
show_fixes = false
unsafe_fixes = hint
# File Resolver Settings
file_resolver.exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"dist",
"node_modules",
"site-packages",
"venv",
]
file_resolver.extend_exclude = []
file_resolver.force_exclude = false
file_resolver.include = [
"*.py",
"*.pyi",
"*.ipynb",
"**/pyproject.toml",
]
file_resolver.extend_include = []
file_resolver.respect_gitignore = true
file_resolver.project_root = "<temp_dir>/subdir"
# Linter Settings
linter.exclude = []
linter.project_root = "<temp_dir>/subdir"
linter.rules.enabled = [
unsorted-imports (I001),
missing-required-import (I002),
mixed-spaces-and-tabs (E101),
multiple-imports-on-one-line (E401),
module-import-not-at-top-of-file (E402),
line-too-long (E501),
multiple-statements-on-one-line-colon (E701),
multiple-statements-on-one-line-semicolon (E702),
useless-semicolon (E703),
none-comparison (E711),
true-false-comparison (E712),
not-in-test (E713),
not-is-test (E714),
type-comparison (E721),
bare-except (E722),
lambda-assignment (E731),
ambiguous-variable-name (E741),
ambiguous-class-name (E742),
ambiguous-function-name (E743),
io-error (E902),
unused-import (F401),
import-shadowed-by-loop-var (F402),
undefined-local-with-import-star (F403),
late-future-import (F404),
undefined-local-with-import-star-usage (F405),
undefined-local-with-nested-import-star-usage (F406),
future-feature-not-defined (F407),
percent-format-invalid-format (F501),
percent-format-expected-mapping (F502),
percent-format-expected-sequence (F503),
percent-format-extra-named-arguments (F504),
percent-format-missing-argument (F505),
percent-format-mixed-positional-and-named (F506),
percent-format-positional-count-mismatch (F507),
percent-format-star-requires-sequence (F508),
percent-format-unsupported-format-character (F509),
string-dot-format-invalid-format (F521),
string-dot-format-extra-named-arguments (F522),
string-dot-format-extra-positional-arguments (F523),
string-dot-format-missing-arguments (F524),
string-dot-format-mixing-automatic (F525),
f-string-missing-placeholders (F541),
multi-value-repeated-key-literal (F601),
multi-value-repeated-key-variable (F602),
expressions-in-star-assignment (F621),
multiple-starred-expressions (F622),
assert-tuple (F631),
is-literal (F632),
invalid-print-syntax (F633),
if-tuple (F634),
break-outside-loop (F701),
continue-outside-loop (F702),
yield-outside-function (F704),
return-outside-function (F706),
default-except-not-last (F707),
forward-annotation-syntax-error (F722),
redefined-while-unused (F811),
undefined-name (F821),
undefined-export (F822),
undefined-local (F823),
unused-variable (F841),
unused-annotation (F842),
raise-not-implemented (F901),
]
linter.rules.should_fix = [
unsorted-imports (I001),
missing-required-import (I002),
mixed-spaces-and-tabs (E101),
multiple-imports-on-one-line (E401),
module-import-not-at-top-of-file (E402),
line-too-long (E501),
multiple-statements-on-one-line-colon (E701),
multiple-statements-on-one-line-semicolon (E702),
useless-semicolon (E703),
none-comparison (E711),
true-false-comparison (E712),
not-in-test (E713),
not-is-test (E714),
type-comparison (E721),
bare-except (E722),
lambda-assignment (E731),
ambiguous-variable-name (E741),
ambiguous-class-name (E742),
ambiguous-function-name (E743),
io-error (E902),
unused-import (F401),
import-shadowed-by-loop-var (F402),
undefined-local-with-import-star (F403),
late-future-import (F404),
undefined-local-with-import-star-usage (F405),
undefined-local-with-nested-import-star-usage (F406),
future-feature-not-defined (F407),
percent-format-invalid-format (F501),
percent-format-expected-mapping (F502),
percent-format-expected-sequence (F503),
percent-format-extra-named-arguments (F504),
percent-format-missing-argument (F505),
percent-format-mixed-positional-and-named (F506),
percent-format-positional-count-mismatch (F507),
percent-format-star-requires-sequence (F508),
percent-format-unsupported-format-character (F509),
string-dot-format-invalid-format (F521),
string-dot-format-extra-named-arguments (F522),
string-dot-format-extra-positional-arguments (F523),
string-dot-format-missing-arguments (F524),
string-dot-format-mixing-automatic (F525),
f-string-missing-placeholders (F541),
multi-value-repeated-key-literal (F601),
multi-value-repeated-key-variable (F602),
expressions-in-star-assignment (F621),
multiple-starred-expressions (F622),
assert-tuple (F631),
is-literal (F632),
invalid-print-syntax (F633),
if-tuple (F634),
break-outside-loop (F701),
continue-outside-loop (F702),
yield-outside-function (F704),
return-outside-function (F706),
default-except-not-last (F707),
forward-annotation-syntax-error (F722),
redefined-while-unused (F811),
undefined-name (F821),
undefined-export (F822),
undefined-local (F823),
unused-variable (F841),
unused-annotation (F842),
raise-not-implemented (F901),
]
linter.per_file_ignores = {}
linter.safety_table.forced_safe = []
linter.safety_table.forced_unsafe = []
linter.unresolved_target_version = none
linter.per_file_target_version = {}
linter.preview = disabled
linter.explicit_preview_rules = false
linter.extension = ExtensionMapping({})
linter.allowed_confusables = []
linter.builtins = []
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
linter.external = []
linter.ignore_init_module_imports = true
linter.logger_objects = []
linter.namespace_packages = []
linter.src = [
"<temp_dir>/subdir",
"<temp_dir>/subdir/src",
]
linter.tab_size = 4
linter.line_length = 120
linter.task_tags = [
TODO,
FIXME,
XXX,
]
linter.typing_modules = []
linter.typing_extensions = true
# Linter Plugins
linter.flake8_annotations.mypy_init_return = false
linter.flake8_annotations.suppress_dummy_args = false
linter.flake8_annotations.suppress_none_returning = false
linter.flake8_annotations.allow_star_arg_any = false
linter.flake8_annotations.ignore_fully_untyped = false
linter.flake8_bandit.hardcoded_tmp_directory = [
/tmp,
/var/tmp,
/dev/shm,
]
linter.flake8_bandit.check_typed_exception = false
linter.flake8_bandit.extend_markup_names = []
linter.flake8_bandit.allowed_markup_calls = []
linter.flake8_bugbear.extend_immutable_calls = []
linter.flake8_builtins.allowed_modules = []
linter.flake8_builtins.ignorelist = []
linter.flake8_builtins.strict_checking = false
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|,\s)\d{4})*
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
_,
gettext,
ngettext,
]
linter.flake8_implicit_str_concat.allow_multiline = true
linter.flake8_import_conventions.aliases = {
altair = alt,
holoviews = hv,
matplotlib = mpl,
matplotlib.pyplot = plt,
networkx = nx,
numpy = np,
numpy.typing = npt,
pandas = pd,
panel = pn,
plotly.express = px,
polars = pl,
pyarrow = pa,
seaborn = sns,
tensorflow = tf,
tkinter = tk,
xml.etree.ElementTree = ET,
}
linter.flake8_import_conventions.banned_aliases = {}
linter.flake8_import_conventions.banned_from = []
linter.flake8_pytest_style.fixture_parentheses = false
linter.flake8_pytest_style.parametrize_names_type = tuple
linter.flake8_pytest_style.parametrize_values_type = list
linter.flake8_pytest_style.parametrize_values_row_type = tuple
linter.flake8_pytest_style.raises_require_match_for = [
BaseException,
Exception,
ValueError,
OSError,
IOError,
EnvironmentError,
socket.error,
]
linter.flake8_pytest_style.raises_extend_require_match_for = []
linter.flake8_pytest_style.mark_parentheses = false
linter.flake8_quotes.inline_quotes = double
linter.flake8_quotes.multiline_quotes = double
linter.flake8_quotes.docstring_quotes = double
linter.flake8_quotes.avoid_escape = true
linter.flake8_self.ignore_names = [
_make,
_asdict,
_replace,
_fields,
_field_defaults,
_name_,
_value_,
]
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []
linter.flake8_type_checking.strict = false
linter.flake8_type_checking.exempt_modules = [
typing,
typing_extensions,
]
linter.flake8_type_checking.runtime_required_base_classes = []
linter.flake8_type_checking.runtime_required_decorators = []
linter.flake8_type_checking.quote_annotations = false
linter.flake8_unused_arguments.ignore_variadic_names = false
linter.isort.required_imports = []
linter.isort.combine_as_imports = false
linter.isort.force_single_line = false
linter.isort.force_sort_within_sections = false
linter.isort.detect_same_package = true
linter.isort.case_sensitive = false
linter.isort.force_wrap_aliases = false
linter.isort.force_to_top = []
linter.isort.known_modules = {}
linter.isort.order_by_type = true
linter.isort.relative_imports_order = furthest_to_closest
linter.isort.single_line_exclusions = []
linter.isort.split_on_trailing_comma = true
linter.isort.classes = []
linter.isort.constants = []
linter.isort.variables = []
linter.isort.no_lines_before = []
linter.isort.lines_after_imports = -1
linter.isort.lines_between_types = 0
linter.isort.forced_separate = []
linter.isort.section_order = [
known { type = future },
known { type = standard_library },
known { type = third_party },
known { type = first_party },
known { type = local_folder },
]
linter.isort.default_section = known { type = third_party }
linter.isort.no_sections = false
linter.isort.from_first = false
linter.isort.length_sort = false
linter.isort.length_sort_straight = false
linter.mccabe.max_complexity = 10
linter.pep8_naming.ignore_names = [
setUp,
tearDown,
setUpClass,
tearDownClass,
setUpModule,
tearDownModule,
asyncSetUp,
asyncTearDown,
setUpTestData,
failureException,
longMessage,
maxDiff,
]
linter.pep8_naming.classmethod_decorators = []
linter.pep8_naming.staticmethod_decorators = []
linter.pycodestyle.max_line_length = 120
linter.pycodestyle.max_doc_length = none
linter.pycodestyle.ignore_overlong_task_comments = false
linter.pyflakes.extend_generics = []
linter.pyflakes.allowed_unused_imports = []
linter.pylint.allow_magic_value_types = [
str,
bytes,
]
linter.pylint.allow_dunder_method_names = []
linter.pylint.max_args = 5
linter.pylint.max_positional_args = 5
linter.pylint.max_returns = 6
linter.pylint.max_bool_expr = 5
linter.pylint.max_branches = 12
linter.pylint.max_statements = 50
linter.pylint.max_public_methods = 20
linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []
formatter.unresolved_target_version = 3.10
formatter.per_file_target_version = {}
formatter.preview = disabled
formatter.line_width = 120
formatter.line_ending = auto
formatter.indent_style = space
formatter.indent_width = 4
formatter.quote_style = double
formatter.magic_trailing_comma = respect
formatter.docstring_code_format = disabled
formatter.docstring_code_line_width = dynamic
# Analyze Settings
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.10
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr -----

View File

@@ -16,7 +16,7 @@ const VERSION_FILTER: [(&str, &str); 1] = [(
fn version_basics() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @"
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -42,7 +42,7 @@ fn config_option_allowed_but_ignored() -> Result<()> {
.arg("version")
.arg("--config")
.arg(&ruff_dot_toml)
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @"
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @r"
success: true
exit_code: 0
----- stdout -----
@@ -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"]), @"
.args(["--config", "foo = bar"]), @r"
success: false
exit_code: 2
----- stdout -----
@@ -91,7 +91,7 @@ fn config_option_ignored_but_validated() {
fn isolated_option_allowed() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @"
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @r"
success: true
exit_code: 0
----- stdout -----

View File

@@ -12,6 +12,10 @@ license = "MIT OR Apache-2.0"
[lib]
[features]
default = []
testing-colors = []
[dependencies]
anstyle = { workspace = true }
memchr = { workspace = true }
@@ -19,17 +23,12 @@ unicode-width = { workspace = true }
[dev-dependencies]
ruff_annotate_snippets = { workspace = true, features = ["testing-colors"] }
anstream = { workspace = true }
serde = { workspace = true, features = ["derive"] }
snapbox = { workspace = true, features = ["diff", "term-svg", "cmd", "examples"] }
toml = { workspace = true }
tryfn = { workspace = true }
[features]
default = []
testing-colors = []
[[test]]
name = "fixtures"
harness = false

View File

@@ -31,7 +31,7 @@
//! styling.
//!
//! The above snippet has been built out of the following structure:
use crate::{Id, snippet};
use crate::snippet;
use std::cmp::{Reverse, max, min};
use std::collections::HashMap;
use std::fmt::Display;
@@ -189,7 +189,6 @@ impl DisplaySet<'_> {
}
Ok(())
}
fn format_annotation(
&self,
line_offset: usize,
@@ -200,13 +199,11 @@ impl DisplaySet<'_> {
) -> fmt::Result {
let hide_severity = annotation.annotation_type.is_none();
let color = get_annotation_style(&annotation.annotation_type, stylesheet);
let formatted_len = if let Some(id) = &annotation.id {
let id_len = id.id.len();
if hide_severity {
id_len
id.len()
} else {
2 + id_len + annotation_type_len(&annotation.annotation_type)
2 + id.len() + annotation_type_len(&annotation.annotation_type)
}
} else {
annotation_type_len(&annotation.annotation_type)
@@ -259,20 +256,9 @@ impl DisplaySet<'_> {
let annotation_type = annotation_type_str(&annotation.annotation_type);
if let Some(id) = annotation.id {
if hide_severity {
buffer.append(
line_offset,
&format!("{id} ", id = fmt_with_hyperlink(id.id, id.url, stylesheet)),
*stylesheet.error(),
);
buffer.append(line_offset, &format!("{id} "), *stylesheet.error());
} else {
buffer.append(
line_offset,
&format!(
"{annotation_type}[{id}]",
id = fmt_with_hyperlink(id.id, id.url, stylesheet)
),
*color,
);
buffer.append(line_offset, &format!("{annotation_type}[{id}]"), *color);
}
} else {
buffer.append(line_offset, annotation_type, *color);
@@ -721,7 +707,7 @@ impl DisplaySet<'_> {
let style =
get_annotation_style(&annotation.annotation_type, stylesheet);
let mut formatted_len = if let Some(id) = &annotation.annotation.id {
2 + id.id.len()
2 + id.len()
+ annotation_type_len(&annotation.annotation.annotation_type)
} else {
annotation_type_len(&annotation.annotation.annotation_type)
@@ -738,10 +724,7 @@ impl DisplaySet<'_> {
} else if formatted_len != 0 {
formatted_len += 2;
let id = match &annotation.annotation.id {
Some(id) => format!(
"[{id}]",
id = fmt_with_hyperlink(&id.id, id.url, stylesheet)
),
Some(id) => format!("[{id}]"),
None => String::new(),
};
buffer.puts(
@@ -844,7 +827,7 @@ impl DisplaySet<'_> {
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Annotation<'a> {
pub(crate) annotation_type: DisplayAnnotationType,
pub(crate) id: Option<Id<'a>>,
pub(crate) id: Option<&'a str>,
pub(crate) label: Vec<DisplayTextFragment<'a>>,
pub(crate) is_fixable: bool,
}
@@ -1157,7 +1140,7 @@ fn format_message<'m>(
fn format_title<'a>(
level: crate::Level,
id: Option<Id<'a>>,
id: Option<&'a str>,
label: &'a str,
is_fixable: bool,
) -> DisplayLine<'a> {
@@ -1175,7 +1158,7 @@ fn format_title<'a>(
fn format_footer<'a>(
level: crate::Level,
id: Option<Id<'a>>,
id: Option<&'a str>,
label: &'a str,
) -> Vec<DisplayLine<'a>> {
let mut result = vec![];
@@ -1723,7 +1706,6 @@ fn format_body<'m>(
annotation: Annotation {
annotation_type,
id: None,
label: format_label(annotation.label, None),
is_fixable: false,
},
@@ -1905,40 +1887,3 @@ fn char_width(c: char) -> Option<usize> {
unicode_width::UnicodeWidthChar::width(c)
}
}
pub(super) fn fmt_with_hyperlink<'a, T>(
content: T,
url: Option<&'a str>,
stylesheet: &Stylesheet,
) -> impl std::fmt::Display + 'a
where
T: std::fmt::Display + 'a,
{
struct FmtHyperlink<'a, T> {
content: T,
url: Option<&'a str>,
}
impl<T> std::fmt::Display for FmtHyperlink<'_, T>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(url) = self.url {
write!(f, "\x1B]8;;{url}\x1B\\")?;
}
self.content.fmt(f)?;
if self.url.is_some() {
f.write_str("\x1B]8;;\x1B\\")?;
}
Ok(())
}
}
let url = if stylesheet.hyperlink { url } else { None };
FmtHyperlink { content, url }
}

View File

@@ -76,7 +76,6 @@ impl Renderer {
}
.effects(Effects::BOLD),
none: Style::new(),
hyperlink: true,
},
..Self::plain()
}
@@ -155,11 +154,6 @@ impl Renderer {
self
}
pub const fn hyperlink(mut self, hyperlink: bool) -> Self {
self.stylesheet.hyperlink = hyperlink;
self
}
/// Set the string used for when a long line is cut.
///
/// The default is `...` (three `U+002E` characters).

View File

@@ -10,7 +10,6 @@ pub(crate) struct Stylesheet {
pub(crate) line_no: Style,
pub(crate) emphasis: Style,
pub(crate) none: Style,
pub(crate) hyperlink: bool,
}
impl Default for Stylesheet {
@@ -30,7 +29,6 @@ impl Stylesheet {
line_no: Style::new(),
emphasis: Style::new(),
none: Style::new(),
hyperlink: false,
}
}
}

View File

@@ -12,19 +12,13 @@
use std::ops::Range;
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub(crate) struct Id<'a> {
pub(crate) id: &'a str,
pub(crate) url: Option<&'a str>,
}
/// Primary structure provided for formatting
///
/// See [`Level::title`] to create a [`Message`]
#[derive(Debug)]
pub struct Message<'a> {
pub(crate) level: Level,
pub(crate) id: Option<Id<'a>>,
pub(crate) id: Option<&'a str>,
pub(crate) title: &'a str,
pub(crate) snippets: Vec<Snippet<'a>>,
pub(crate) footer: Vec<Message<'a>>,
@@ -34,12 +28,7 @@ pub struct Message<'a> {
impl<'a> Message<'a> {
pub fn id(mut self, id: &'a str) -> Self {
self.id = Some(Id { id, url: None });
self
}
pub fn id_with_url(mut self, id: &'a str, url: Option<&'a str>) -> Self {
self.id = Some(Id { id, url });
self.id = Some(id);
self
}

View File

@@ -16,80 +16,75 @@ bench = false
test = false
doctest = false
[[bench]]
name = "linter"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "lexer"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "parser"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "formatter"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "ty"
harness = false
required-features = ["instrumented"]
[[bench]]
name = "ty_walltime"
harness = false
required-features = ["walltime"]
[dependencies]
ruff_db = { workspace = true, features = ["testing"] }
ruff_linter = { workspace = true, optional = true }
ruff_python_ast = { workspace = true }
ruff_linter = { workspace = true, optional = true }
ruff_python_formatter = { workspace = true, optional = true }
ruff_python_parser = { workspace = true, optional = true }
ruff_python_trivia = { workspace = true, optional = true }
ty_project = { workspace = true, optional = true }
divan = { workspace = true, optional = true }
anyhow = { workspace = true }
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
criterion = { workspace = true, default-features = false, optional = true }
divan = { workspace = true, optional = true }
rayon = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true, optional = true }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true, optional = true }
[dev-dependencies]
rayon = { workspace = true }
rustc-hash = { workspace = true }
[lints]
workspace = true
[features]
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
# Enables the ruff instrumented benchmarks
ruff_instrumented = [
default = ["instrumented", "walltime"]
# Enables the benchmark that should only run with codspeed's instrumented runner
instrumented = [
"criterion",
"ruff_linter",
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"mimalloc",
"tikv-jemallocator",
"ty_project",
]
# Enables the ty instrumented benchmarks
ty_instrumented = ["criterion", "ty_project", "ruff_python_trivia"]
codspeed = ["codspeed-criterion-compat"]
# Enables the ty_walltime benchmarks
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
# Enables benchmark that should only run with codspeed's walltime runner.
walltime = ["ruff_db/os", "ty_project", "divan"]
[[bench]]
name = "linter"
harness = false
required-features = ["ruff_instrumented"]
[target.'cfg(target_os = "windows")'.dev-dependencies]
mimalloc = { workspace = true }
[[bench]]
name = "lexer"
harness = false
required-features = ["ruff_instrumented"]
[[bench]]
name = "parser"
harness = false
required-features = ["ruff_instrumented"]
[[bench]]
name = "formatter"
harness = false
required-features = ["ruff_instrumented"]
[[bench]]
name = "ty"
harness = false
required-features = ["ty_instrumented"]
[[bench]]
name = "ty_walltime"
harness = false
required-features = ["ty_walltime"]
[lints]
workspace = true
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
tikv-jemallocator = { workspace = true }

View File

@@ -6,8 +6,7 @@ use criterion::{
use ruff_benchmark::{
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
};
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::{Mode, lexer};
use ruff_python_parser::{Mode, TokenKind, lexer};
#[cfg(target_os = "windows")]
#[global_allocator]

View File

@@ -15,7 +15,7 @@ use ruff_db::files::{File, system_path_to_file};
use ruff_db::source::source_text;
use ruff_db::system::{InMemorySystem, MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem};
use ruff_python_ast::PythonVersion;
use ty_project::metadata::options::{AnalysisOptions, EnvironmentOptions, Options};
use ty_project::metadata::options::{EnvironmentOptions, Options};
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::watch::{ChangeEvent, ChangedKind};
use ty_project::{CheckMode, Db, ProjectDatabase, ProjectMetadata};
@@ -67,7 +67,6 @@ fn tomllib_path(file: &TestFile) -> SystemPathBuf {
SystemPathBuf::from("src").join(file.name())
}
#[expect(clippy::needless_update)]
fn setup_tomllib_case() -> Case {
let system = TestSystem::default();
let fs = system.memory_file_system().clone();
@@ -86,10 +85,6 @@ fn setup_tomllib_case() -> Case {
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
..EnvironmentOptions::default()
}),
analysis: Some(AnalysisOptions {
respect_type_ignore_comments: Some(false),
..AnalysisOptions::default()
}),
..Options::default()
});
@@ -226,7 +221,7 @@ fn setup_micro_case(code: &str) -> Case {
let file_path = "src/test.py";
fs.write_file_all(
SystemPathBuf::from(file_path),
&*ruff_python_trivia::textwrap::dedent(code),
ruff_python_trivia::textwrap::dedent(code),
)
.unwrap();
@@ -562,60 +557,6 @@ fn benchmark_many_enum_members(criterion: &mut Criterion) {
});
}
fn benchmark_many_enum_members_2(criterion: &mut Criterion) {
const NUM_ENUM_MEMBERS: usize = 48;
setup_rayon();
let mut code = "\
from enum import Enum
from typing_extensions import assert_never
class E(Enum):
"
.to_string();
for i in 0..NUM_ENUM_MEMBERS {
writeln!(&mut code, " m{i} = {i}").ok();
}
code.push_str(
"
def method(self):
match self:",
);
for i in 0..NUM_ENUM_MEMBERS {
write!(
&mut code,
"
case E.m{i}:
pass"
)
.ok();
}
write!(
&mut code,
"
case _:
assert_never(self)"
)
.ok();
criterion.bench_function("ty_micro[many_enum_members_2]", |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,
@@ -726,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
120,
110,
);
bench_project(&benchmark, criterion);
@@ -760,7 +701,7 @@ fn datetype(criterion: &mut Criterion) {
max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313,
},
4,
2,
);
bench_project(&benchmark, criterion);
@@ -776,7 +717,6 @@ criterion_group!(
benchmark_complex_constrained_attributes_2,
benchmark_complex_constrained_attributes_3,
benchmark_many_enum_members,
benchmark_many_enum_members_2,
);
criterion_group!(project, anyio, attrs, hydra, datetype);
criterion_main!(check_file, micro, project);

View File

@@ -71,8 +71,6 @@ impl Display for Benchmark<'_> {
}
}
#[track_caller]
#[expect(clippy::cast_precision_loss)]
fn check_project(db: &ProjectDatabase, project_name: &str, max_diagnostics: usize) {
let result = db.check();
let diagnostics = result.len();
@@ -81,12 +79,6 @@ fn check_project(db: &ProjectDatabase, project_name: &str, max_diagnostics: usiz
diagnostics > 1 && diagnostics <= max_diagnostics,
"Expected between 1 and {max_diagnostics} diagnostics on project '{project_name}' but got {diagnostics}",
);
if (max_diagnostics - diagnostics) as f64 / max_diagnostics as f64 > 0.10 {
tracing::warn!(
"The expected diagnostics for project `{project_name}` can be reduced: expected {max_diagnostics} but got {diagnostics}"
);
}
}
static ALTAIR: Benchmark = Benchmark::new(
@@ -109,7 +101,7 @@ static ALTAIR: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
850,
1000,
);
static COLOUR_SCIENCE: Benchmark = Benchmark::new(
@@ -128,7 +120,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
350,
600,
);
static FREQTRADE: Benchmark = Benchmark::new(
@@ -151,7 +143,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
600,
525,
);
static PANDAS: Benchmark = Benchmark::new(
@@ -171,7 +163,7 @@ static PANDAS: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
3800,
3000,
);
static PYDANTIC: Benchmark = Benchmark::new(
@@ -189,7 +181,7 @@ static PYDANTIC: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39,
},
3200,
3000,
);
static SYMPY: Benchmark = Benchmark::new(
@@ -202,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13400,
13000,
);
static TANJUN: Benchmark = Benchmark::new(
@@ -215,7 +207,7 @@ static TANJUN: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
110,
320,
);
static STATIC_FRAME: Benchmark = Benchmark::new(
@@ -231,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
1700,
900,
);
#[track_caller]
@@ -243,55 +235,30 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(sample_size = 2, sample_count = 3)]
fn altair(bencher: Bencher) {
run_single_threaded(bencher, &ALTAIR);
#[bench(args=[&ALTAIR, &FREQTRADE, &TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(sample_size = 2, sample_count = 3)]
fn freqtrade(bencher: Bencher) {
run_single_threaded(bencher, &FREQTRADE);
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(sample_size = 2, sample_count = 3)]
fn tanjun(bencher: Bencher) {
run_single_threaded(bencher, &TANJUN);
#[bench(args=[&SYMPY, &PYDANTIC], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(sample_size = 2, sample_count = 3)]
fn pydantic(bencher: Bencher) {
run_single_threaded(bencher, &PYDANTIC);
}
#[bench(sample_size = 1, sample_count = 3)]
fn static_frame(bencher: Bencher) {
run_single_threaded(bencher, &STATIC_FRAME);
}
#[bench(sample_size = 1, sample_count = 2)]
fn colour_science(bencher: Bencher) {
run_single_threaded(bencher, &COLOUR_SCIENCE);
}
#[bench(sample_size = 1, sample_count = 2)]
fn pandas(bencher: Bencher) {
run_single_threaded(bencher, &PANDAS);
}
#[bench(sample_size = 1, sample_count = 2)]
fn sympy(bencher: Bencher) {
run_single_threaded(bencher, &SYMPY);
}
#[bench(sample_size = 3, sample_count = 8)]
fn multithreaded(bencher: Bencher) {
#[bench(args=[&ALTAIR], sample_size=3, sample_count=8)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
bencher
.with_inputs(|| ALTAIR.setup_iteration())
.with_inputs(|| benchmark.setup_iteration())
.bench_local_values(|db| {
thread_pool.install(|| {
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics);
check_project(&db, benchmark.project.name, benchmark.max_diagnostics);
db
})
});

View File

@@ -1,6 +1,6 @@
use std::path::PathBuf;
#[cfg(any(feature = "ty_instrumented", feature = "ruff_instrumented"))]
#[cfg(feature = "instrumented")]
pub mod criterion;
pub mod real_world_projects;

View File

@@ -11,11 +11,11 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
filetime = { workspace = true }
glob = { workspace = true }
globset = { workspace = true }
itertools = { workspace = true }
regex = { workspace = true }
filetime = { workspace = true }
seahash = { workspace = true }
[dev-dependencies]

View File

@@ -42,18 +42,17 @@ schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
similar = { workspace = true }
supports-hyperlinks = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
zip = { workspace = true }
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
etcetera = { workspace = true, optional = true }
[target.'cfg(target_arch="wasm32")'.dependencies]
web-time = { version = "1.1.0" }
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
etcetera = { workspace = true, optional = true }
[dev-dependencies]
insta = { workspace = true, features = ["filters"] }
tempfile = { workspace = true }

View File

@@ -1,64 +0,0 @@
use std::fmt::Formatter;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
/// Signals a [`CancellationToken`] that it should be canceled.
#[derive(Debug, Clone)]
pub struct CancellationTokenSource {
cancelled: Arc<AtomicBool>,
}
impl Default for CancellationTokenSource {
fn default() -> Self {
Self::new()
}
}
impl CancellationTokenSource {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn is_cancellation_requested(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
/// Creates a new token that uses this source.
pub fn token(&self) -> CancellationToken {
CancellationToken {
cancelled: self.cancelled.clone(),
}
}
/// Requests cancellation for operations using this token.
pub fn cancel(&self) {
self.cancelled
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
/// Token signals whether an operation should be canceled.
#[derive(Debug, Clone)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
}
/// The operation was canceled by the provided [`CancellationToken`].
#[derive(Debug)]
pub struct Canceled;
impl std::error::Error for Canceled {}
impl std::fmt::Display for Canceled {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("operation was canceled")
}
}

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt::Formatter, path::Path, sync::Arc};
use std::{fmt::Formatter, path::Path, sync::Arc};
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
@@ -11,7 +11,6 @@ pub use self::render::{
ceil_char_boundary,
github::{DisplayGithubDiagnostics, GithubRenderer},
};
use crate::cancellation::CancellationToken;
use crate::{Db, files::File};
mod render;
@@ -65,8 +64,6 @@ impl Diagnostic {
id,
severity,
message: message.into_diagnostic_message(),
custom_concise_message: None,
documentation_url: None,
annotations: vec![],
subs: vec![],
fix: None,
@@ -98,44 +95,6 @@ impl Diagnostic {
diag
}
/// Adds sub diagnostics that tell the user that this is a bug in ty
/// and asks them to open an issue on GitHub.
pub fn add_bug_sub_diagnostics(&mut self, url_encoded_title: &str) {
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
"This indicates a bug in ty.",
));
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format_args!(
"If you could open an issue at https://github.com/astral-sh/ty/issues/new?title={url_encoded_title}, we'd be very appreciative!"
),
));
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!(
"Platform: {os} {arch}",
os = std::env::consts::OS,
arch = std::env::consts::ARCH
),
));
if let Some(version) = crate::program_version() {
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!("Version: {version}"),
));
}
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!(
"Args: {args:?}",
args = std::env::args().collect::<Vec<_>>()
),
));
}
/// Add an annotation to this diagnostic.
///
/// Annotations for a diagnostic are optional, but if any are added,
@@ -205,8 +164,28 @@ impl Diagnostic {
/// Returns the primary message for this diagnostic.
///
/// A diagnostic always has a message, but it may be empty.
///
/// NOTE: At present, this routine will return the first primary
/// annotation's message as the primary message when the main diagnostic
/// message is empty. This is meant to facilitate an incremental migration
/// in ty over to the new diagnostic data model. (The old data model
/// didn't distinguish between messages on the entire diagnostic and
/// messages attached to a particular span.)
pub fn primary_message(&self) -> &str {
self.inner.message.as_str()
if !self.inner.message.as_str().is_empty() {
return self.inner.message.as_str();
}
// FIXME: As a special case, while we're migrating ty
// to the new diagnostic data model, we'll look for a primary
// message from the primary annotation. This is because most
// ty diagnostics are created with an empty diagnostic
// message and instead attach the message to the annotation.
// Fixing this will require touching basically every diagnostic
// in ty, so we do it this way for now to match the old
// semantics. ---AG
self.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default()
}
/// Introspects this diagnostic and returns what kind of "primary" message
@@ -218,35 +197,35 @@ impl Diagnostic {
/// contains *essential* information or context for understanding the
/// diagnostic.
///
/// The reason why we don't just always return both the main diagnostic
/// message and the primary annotation message is because this was written
/// in the midst of an incremental migration of ty over to the new
/// diagnostic data model. At time of writing, diagnostics were still
/// constructed in the old model where the main diagnostic message and the
/// primary annotation message were not distinguished from each other. So
/// for now, we carefully return what kind of messages this diagnostic
/// contains. In effect, if this diagnostic has a non-empty main message
/// *and* a non-empty primary annotation message, then the diagnostic is
/// 100% using the new diagnostic data model and we can format things
/// appropriately.
///
/// The type returned implements the `std::fmt::Display` trait. In most
/// cases, just converting it to a string (or printing it) will do what
/// you want.
pub fn concise_message(&self) -> ConciseMessage<'_> {
if let Some(custom_message) = &self.inner.custom_concise_message {
return ConciseMessage::Custom(custom_message.as_str());
}
let main = self.inner.message.as_str();
let annotation = self
.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default();
if annotation.is_empty() {
ConciseMessage::MainDiagnostic(main)
} else {
ConciseMessage::Both { main, annotation }
match (main.is_empty(), annotation.is_empty()) {
(false, true) => ConciseMessage::MainDiagnostic(main),
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
(false, false) => ConciseMessage::Both { main, annotation },
(true, true) => ConciseMessage::Empty,
}
}
/// Set a custom message for the concise formatting of this diagnostic.
///
/// This overrides the default behavior of generating a concise message
/// from the main diagnostic message and the primary annotation.
pub fn set_concise_message(&mut self, message: impl IntoDiagnosticMessage) {
Arc::make_mut(&mut self.inner).custom_concise_message =
Some(message.into_diagnostic_message());
}
/// Returns the severity of this diagnostic.
///
/// Note that this may be different than the severity of sub-diagnostics.
@@ -360,13 +339,6 @@ impl Diagnostic {
Arc::make_mut(&mut self.inner).fix = Some(fix);
}
/// If `fix` is `Some`, set the fix for this diagnostic.
pub fn set_optional_fix(&mut self, fix: Option<Fix>) {
if let Some(fix) = fix {
self.set_fix(fix);
}
}
/// Remove the fix for this diagnostic.
pub fn remove_fix(&mut self) {
Arc::make_mut(&mut self.inner).fix = None;
@@ -384,14 +356,6 @@ impl Diagnostic {
.is_some_and(|fix| fix.applies(config.fix_applicability))
}
pub fn documentation_url(&self) -> Option<&str> {
self.inner.documentation_url.as_deref()
}
pub fn set_documentation_url(&mut self, url: Option<String>) {
Arc::make_mut(&mut self.inner).documentation_url = url;
}
/// Returns the offset of the parent statement for this diagnostic if it exists.
///
/// This is primarily used for checking noqa/secondary code suppressions.
@@ -449,6 +413,11 @@ impl Diagnostic {
self.id().is_invalid_syntax()
}
/// Returns the message body to display to the user.
pub fn body(&self) -> &str {
self.primary_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
@@ -460,6 +429,28 @@ impl Diagnostic {
.map(|sub| sub.inner.message.as_str())
}
/// Returns the URL for the rule documentation, if it exists.
pub fn to_ruff_url(&self) -> Option<String> {
match self.id() {
DiagnosticId::Panic
| DiagnosticId::Io
| DiagnosticId::InvalidSyntax
| DiagnosticId::RevealedType
| DiagnosticId::UnknownRule
| DiagnosticId::InvalidGlob
| DiagnosticId::EmptyInclude
| DiagnosticId::UnnecessaryOverridesSection
| DiagnosticId::UselessOverridesSection
| DiagnosticId::DeprecatedSetting
| DiagnosticId::Unformatted
| DiagnosticId::InvalidCliOption
| DiagnosticId::InternalError => None,
DiagnosticId::Lint(lint_name) => {
Some(format!("{}/rules/{lint_name}", env!("CARGO_PKG_HOMEPAGE")))
}
}
}
/// Returns the filename for the message.
///
/// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`.
@@ -539,10 +530,8 @@ impl Diagnostic {
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
struct DiagnosticInner {
id: DiagnosticId,
documentation_url: Option<String>,
severity: Severity,
message: DiagnosticMessage,
custom_concise_message: Option<DiagnosticMessage>,
annotations: Vec<Annotation>,
subs: Vec<SubDiagnostic>,
fix: Option<Fix>,
@@ -694,6 +683,18 @@ impl SubDiagnostic {
/// contains *essential* information or context for understanding the
/// diagnostic.
///
/// The reason why we don't just always return both the main diagnostic
/// message and the primary annotation message is because this was written
/// in the midst of an incremental migration of ty over to the new
/// diagnostic data model. At time of writing, diagnostics were still
/// constructed in the old model where the main diagnostic message and the
/// primary annotation message were not distinguished from each other. So
/// for now, we carefully return what kind of messages this diagnostic
/// contains. In effect, if this diagnostic has a non-empty main message
/// *and* a non-empty primary annotation message, then the diagnostic is
/// 100% using the new diagnostic data model and we can format things
/// appropriately.
///
/// The type returned implements the `std::fmt::Display` trait. In most
/// cases, just converting it to a string (or printing it) will do what
/// you want.
@@ -703,10 +704,11 @@ impl SubDiagnostic {
.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default();
if annotation.is_empty() {
ConciseMessage::MainDiagnostic(main)
} else {
ConciseMessage::Both { main, annotation }
match (main.is_empty(), annotation.is_empty()) {
(false, true) => ConciseMessage::MainDiagnostic(main),
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
(false, false) => ConciseMessage::Both { main, annotation },
(true, true) => ConciseMessage::Empty,
}
}
}
@@ -876,10 +878,6 @@ impl Annotation {
pub fn hide_snippet(&mut self, yes: bool) {
self.hide_snippet = yes;
}
pub fn is_primary(&self) -> bool {
self.is_primary
}
}
/// Tags that can be associated with an annotation.
@@ -1057,13 +1055,6 @@ impl DiagnosticId {
matches!(self, DiagnosticId::Lint(_))
}
pub const fn as_lint(&self) -> Option<LintName> {
match self {
DiagnosticId::Lint(name) => Some(*name),
_ => None,
}
}
/// Returns `true` if this `DiagnosticId` represents a lint with the given name.
pub fn is_lint_named(&self, name: &str) -> bool {
matches!(self, DiagnosticId::Lint(self_name) if self_name == name)
@@ -1353,8 +1344,6 @@ pub struct DisplayDiagnosticConfig {
show_fix_diff: bool,
/// The lowest applicability that should be shown when reporting diagnostics.
fix_applicability: Applicability,
cancellation_token: Option<CancellationToken>,
}
impl DisplayDiagnosticConfig {
@@ -1428,20 +1417,6 @@ impl DisplayDiagnosticConfig {
pub fn fix_applicability(&self) -> Applicability {
self.fix_applicability
}
pub fn with_cancellation_token(
mut self,
token: Option<CancellationToken>,
) -> DisplayDiagnosticConfig {
self.cancellation_token = token;
self
}
pub fn is_canceled(&self) -> bool {
self.cancellation_token
.as_ref()
.is_some_and(|token| token.is_cancelled())
}
}
impl Default for DisplayDiagnosticConfig {
@@ -1455,7 +1430,6 @@ impl Default for DisplayDiagnosticConfig {
show_fix_status: false,
show_fix_diff: false,
fix_applicability: Applicability::Safe,
cancellation_token: None,
}
}
}
@@ -1524,21 +1498,28 @@ pub enum DiagnosticFormat {
pub enum ConciseMessage<'a> {
/// A diagnostic contains a non-empty main message and an empty
/// primary annotation message.
///
/// This strongly suggests that the diagnostic is using the
/// "new" data model.
MainDiagnostic(&'a str),
/// A diagnostic contains an empty main message and a non-empty
/// primary annotation message.
///
/// This strongly suggests that the diagnostic is using the
/// "old" data model.
PrimaryAnnotation(&'a str),
/// A diagnostic contains a non-empty main message and a non-empty
/// primary annotation message.
///
/// This strongly suggests that the diagnostic is using the
/// "new" data model.
Both { main: &'a str, annotation: &'a str },
/// A custom concise message has been provided.
Custom(&'a str),
}
impl<'a> ConciseMessage<'a> {
pub fn to_str(&self) -> Cow<'a, str> {
match self {
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
}
}
/// A diagnostic contains an empty main message and an empty
/// primary annotation message.
///
/// This indicates that the diagnostic is probably using the old
/// model.
Empty,
}
impl std::fmt::Display for ConciseMessage<'_> {
@@ -1547,26 +1528,17 @@ impl std::fmt::Display for ConciseMessage<'_> {
ConciseMessage::MainDiagnostic(main) => {
write!(f, "{main}")
}
ConciseMessage::PrimaryAnnotation(annotation) => {
write!(f, "{annotation}")
}
ConciseMessage::Both { main, annotation } => {
write!(f, "{main}: {annotation}")
}
ConciseMessage::Custom(message) => {
write!(f, "{message}")
}
ConciseMessage::Empty => Ok(()),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for ConciseMessage<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
/// A diagnostic message string.
///
/// This is, for all intents and purposes, equivalent to a `Box<str>`.

View File

@@ -205,7 +205,6 @@ impl<'a> Resolved<'a> {
struct ResolvedDiagnostic<'a> {
level: AnnotateLevel,
id: Option<String>,
documentation_url: Option<String>,
message: String,
annotations: Vec<ResolvedAnnotation<'a>>,
is_fixable: bool,
@@ -241,12 +240,12 @@ impl<'a> ResolvedDiagnostic<'a> {
// `DisplaySet::format_annotation` for both cases, but this is a small hack to improve
// the formatting of syntax errors for now. This should also be kept consistent with the
// concise formatting.
diag.secondary_code().map_or_else(
Some(diag.secondary_code().map_or_else(
|| format!("{id}:", id = diag.inner.id),
|code| code.to_string(),
)
))
} else {
diag.inner.id.to_string()
Some(diag.inner.id.to_string())
};
let level = if config.hide_severity {
@@ -257,8 +256,7 @@ impl<'a> ResolvedDiagnostic<'a> {
ResolvedDiagnostic {
level,
id: Some(id),
documentation_url: diag.documentation_url().map(ToString::to_string),
id,
message: diag.inner.message.as_str().to_string(),
annotations,
is_fixable: config.show_fix_status && diag.has_applicable_fix(config),
@@ -289,7 +287,6 @@ impl<'a> ResolvedDiagnostic<'a> {
ResolvedDiagnostic {
level: diag.inner.severity.to_annotate(),
id: None,
documentation_url: None,
message: diag.inner.message.as_str().to_string(),
annotations,
is_fixable: false,
@@ -388,7 +385,6 @@ impl<'a> ResolvedDiagnostic<'a> {
RenderableDiagnostic {
level: self.level,
id: self.id.as_deref(),
documentation_url: self.documentation_url.as_deref(),
message: &self.message,
snippets_by_input,
is_fixable: self.is_fixable,
@@ -489,7 +485,6 @@ struct RenderableDiagnostic<'r> {
/// An ID is always present for top-level diagnostics and always absent for
/// sub-diagnostics.
id: Option<&'r str>,
documentation_url: Option<&'r str>,
/// The message emitted with the diagnostic, before any snippets are
/// rendered.
message: &'r str,
@@ -524,7 +519,7 @@ impl RenderableDiagnostic<'_> {
.is_fixable(self.is_fixable)
.lineno_offset(self.header_offset);
if let Some(id) = self.id {
message = message.id_with_url(id, self.documentation_url);
message = message.id(id);
}
message.snippets(snippets)
}
@@ -1284,7 +1279,7 @@ watermelon
let diag = env.err().primary("animals", "5", "5", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1308,7 +1303,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
warning[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1328,7 +1323,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
info[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1355,7 +1350,7 @@ watermelon
let diag = builder.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1374,7 +1369,7 @@ watermelon
let diag = builder.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1395,7 +1390,7 @@ watermelon
let diag = env.err().primary("non-ascii", "5", "5", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> non-ascii:5:1
|
@@ -1414,7 +1409,7 @@ watermelon
let diag = env.err().primary("non-ascii", "2:4", "2:8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> non-ascii:2:2
|
@@ -1438,7 +1433,7 @@ watermelon
env.context(1);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1455,7 +1450,7 @@ watermelon
env.context(0);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1470,7 +1465,7 @@ watermelon
env.context(2);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1487,7 +1482,7 @@ watermelon
env.context(2);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:11:1
|
@@ -1504,7 +1499,7 @@ watermelon
env.context(200);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -1537,7 +1532,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1581,7 +1576,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1606,7 +1601,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1634,7 +1629,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1662,7 +1657,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1687,7 +1682,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1718,7 +1713,7 @@ watermelon
// window.
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:1:1
|
@@ -1756,7 +1751,7 @@ watermelon
let diag = env.err().primary("spacey-animals", "8", "8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:8:1
|
@@ -1773,7 +1768,7 @@ watermelon
let diag = env.err().primary("spacey-animals", "12", "12", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:12:1
|
@@ -1791,7 +1786,7 @@ watermelon
let diag = env.err().primary("spacey-animals", "13", "13", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:13:1
|
@@ -1831,7 +1826,7 @@ watermelon
// instead of special casing the snippet assembly.
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> spacey-animals:3:1
|
@@ -1860,7 +1855,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1897,7 +1892,7 @@ watermelon
);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1934,7 +1929,7 @@ watermelon
);
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1962,7 +1957,7 @@ watermelon
diag.sub(env.sub_warn().primary("fruits", "3", "3", "").build());
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -1998,7 +1993,7 @@ watermelon
diag.sub(env.sub_warn().primary("animals", "11", "11", "").build());
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -2037,7 +2032,7 @@ watermelon
diag.sub(env.sub_warn().primary("fruits", "3", "3", "").build());
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -2085,7 +2080,7 @@ watermelon
diag.sub(env.sub_warn().secondary("animals", "3", "3", "").build());
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:3:1
|
@@ -2121,7 +2116,7 @@ watermelon
let diag = env.err().primary("animals", "5", "6", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2144,7 +2139,7 @@ watermelon
let diag = env.err().primary("animals", "5", "7:0", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2164,7 +2159,7 @@ watermelon
let diag = env.err().primary("animals", "5", "7:1", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2184,7 +2179,7 @@ watermelon
let diag = env.err().primary("animals", "5:3", "8:8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:4
|
@@ -2206,7 +2201,7 @@ watermelon
let diag = env.err().secondary("animals", "5:3", "8:8", "").build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:4
|
@@ -2238,7 +2233,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:4:1
|
@@ -2267,7 +2262,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:4:1
|
@@ -2298,7 +2293,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2333,7 +2328,7 @@ watermelon
// better using only ASCII art.
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2361,7 +2356,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2393,7 +2388,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:3
|
@@ -2415,7 +2410,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:3
|
@@ -2448,7 +2443,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:8:1
|
@@ -2488,7 +2483,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:5:1
|
@@ -2532,7 +2527,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> fruits:1:1
|
@@ -2567,7 +2562,7 @@ watermelon
.build();
insta::assert_snapshot!(
env.render(&diag),
@"
@r"
error[test-diagnostic]: main diagnostic message
--> animals:11:1
|
@@ -2881,12 +2876,6 @@ watermelon
self.diag.help(message);
self
}
/// Set the documentation URL for the diagnostic.
pub(super) fn documentation_url(mut self, url: impl Into<String>) -> DiagnosticBuilder<'e> {
self.diag.set_documentation_url(Some(url.into()));
self
}
}
/// A helper builder for tersely populating a `SubDiagnostic`.
@@ -3001,7 +2990,6 @@ def fibonacci(n):
TextSize::from(10),
))))
.noqa_offset(TextSize::from(7))
.documentation_url("https://docs.astral.sh/ruff/rules/unused-import")
.build(),
env.builder(
"unused-variable",
@@ -3016,13 +3004,11 @@ def fibonacci(n):
TextSize::from(99),
)))
.noqa_offset(TextSize::from(94))
.documentation_url("https://docs.astral.sh/ruff/rules/unused-variable")
.build(),
env.builder("undefined-name", Severity::Error, "Undefined name `a`")
.primary("undef.py", "1:3", "1:4", "")
.secondary_code("F821")
.noqa_offset(TextSize::from(3))
.documentation_url("https://docs.astral.sh/ruff/rules/undefined-name")
.build(),
];
@@ -3137,7 +3123,6 @@ if call(foo
TextSize::from(19),
))))
.noqa_offset(TextSize::from(16))
.documentation_url("https://docs.astral.sh/ruff/rules/unused-import")
.build(),
env.builder(
"unused-import",
@@ -3152,7 +3137,6 @@ if call(foo
TextSize::from(40),
))))
.noqa_offset(TextSize::from(35))
.documentation_url("https://docs.astral.sh/ruff/rules/unused-import")
.build(),
env.builder(
"unused-variable",
@@ -3167,7 +3151,6 @@ if call(foo
TextSize::from(104),
))))
.noqa_offset(TextSize::from(98))
.documentation_url("https://docs.astral.sh/ruff/rules/unused-variable")
.build(),
];

Some files were not shown because too many files have changed in this diff Show More