Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Waygood
1a30934c33 [ty] Cleanup various APIs 2025-10-30 16:52:13 -04:00
2445 changed files with 35081 additions and 336575 deletions

View File

@@ -5,4 +5,4 @@ rustup component add clippy rustfmt
cargo install cargo-insta cargo install cargo-insta
cargo fetch 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_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
crates/ruff_python_parser/resources/inline linguist-generated=true 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 ruff.schema.json -diff linguist-generated=true text=auto eol=lf
ty.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 # ty
/crates/ty* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager /crates/ty* @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ruff_db/ @carljm @MichaReiser @sharkdp @dcreager /crates/ruff_db/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_project/ @carljm @MichaReiser @sharkdp @dcreager @Gankra /crates/ty_project/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_ide/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager @Gankra /crates/ty_server/ @carljm @MichaReiser @sharkdp @dcreager
/crates/ty_server/ @carljm @MichaReiser @sharkdp @dcreager @Gankra
/crates/ty/ @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 /scripts/ty_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp @dcreager
/crates/ty_python_semantic/ @carljm @AlexWaygood @sharkdp @dcreager /crates/ty_python_semantic @carljm @AlexWaygood @sharkdp @dcreager
/crates/ty_module_resolver/ @carljm @MichaReiser @AlexWaygood @Gankra

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. # to verify the correctness of the syntax in our GitHub Actions workflows.
self-hosted-runner: self-hosted-runner:
# Various runners we use that aren't recognized out-of-the-box by actionlint: # Various runners we use that aren't recognized out-of-the-box by actionlint:
labels: labels:
- depot-ubuntu-24.04-4
- depot-ubuntu-latest-8 - depot-ubuntu-latest-8
- depot-ubuntu-22.04-16 - depot-ubuntu-22.04-16
- depot-ubuntu-22.04-32 - depot-ubuntu-22.04-32
- depot-windows-2022-16 - depot-windows-2022-16
- depot-ubuntu-22.04-arm-4
- github-windows-2025-x86_64-8 - github-windows-2025-x86_64-8
- github-windows-2025-x86_64-16 - github-windows-2025-x86_64-16
- codspeed-macro - 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. # Enable off-by-default rules.
[rules] [rules]
possibly-unresolved-reference = "warn" possibly-unresolved-reference = "warn"
possibly-missing-import = "warn" unused-ignore-comment = "warn"
division-by-zero = "warn" division-by-zero = "warn"
unsupported-dynamic-base = "warn"

View File

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

View File

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

View File

@@ -20,12 +20,6 @@ on:
env: env:
RUFF_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ruff 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: jobs:
docker-build: docker-build:
name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }} name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }}
@@ -39,12 +33,12 @@ jobs:
- linux/amd64 - linux/amd64
- linux/arm64 - linux/arm64
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
submodules: recursive submodules: recursive
persist-credentials: false 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 - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
@@ -69,7 +63,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with: with:
images: ${{ env.RUFF_BASE_IMG }} 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 # 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:}" touch "/tmp/digests/${digest#sha256:}"
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: digests-${{ env.PLATFORM_TUPLE }} name: digests-${{ env.PLATFORM_TUPLE }}
path: /tmp/digests/* path: /tmp/digests/*
@@ -119,17 +113,17 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with: with:
path: /tmp/digests path: /tmp/digests
pattern: digests-* pattern: digests-*
merge-multiple: true 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 - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with: with:
images: ${{ env.RUFF_BASE_IMG }} 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 # 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 - debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian - buildpack-deps:bookworm,bookworm,debian
steps: 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 - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
@@ -225,7 +219,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
# ghcr.io prefers index level annotations # ghcr.io prefers index level annotations
env: env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index DOCKER_METADATA_ANNOTATIONS_LEVELS: index
@@ -262,17 +256,17 @@ jobs:
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with: with:
path: /tmp/digests path: /tmp/digests
pattern: digests-* pattern: digests-*
merge-multiple: true 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 - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
env: env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index DOCKER_METADATA_ANNOTATIONS_LEVELS: index
with: 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

File diff suppressed because it is too large Load Diff

View File

@@ -31,15 +31,15 @@ jobs:
# Don't run the cron job on forks: # Don't run the cron job on forks:
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }} if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- name: "Install Rust toolchain" - name: "Install Rust toolchain"
run: rustup show run: rustup show
- name: "Install mold" - name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 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 - name: Build ruff
# A debug build means the script runs slower once it gets started, # 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 # 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 name: Create an issue if the daily fuzz surfaced any bugs
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: fuzz 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: permissions:
issues: write issues: write
steps: steps:

View File

@@ -6,11 +6,6 @@ on:
pull_request: pull_request:
paths: paths:
- "crates/ty*/**" - "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db" - "crates/ruff_db"
- "crates/ruff_python_ast" - "crates/ruff_python_ast"
- "crates/ruff_python_parser" - "crates/ruff_python_parser"
@@ -41,18 +36,17 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }} runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
path: ruff path: ruff
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Install the latest version of uv - name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with: with:
shared-key: "mypy-primer"
workspaces: "ruff" workspaces: "ruff"
- name: Install Rust toolchain - name: Install Rust toolchain
@@ -61,38 +55,41 @@ jobs:
- name: Run mypy_primer - name: Run mypy_primer
env: env:
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
CLICOLOR_FORCE: "1"
DIFF_FILE: mypy_primer.diff DIFF_FILE: mypy_primer.diff
run: | run: |
cd ruff cd ruff
scripts/mypy_primer.sh scripts/mypy_primer.sh
echo ${{ github.event.number }} > ../pr-number
# 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 - name: Upload diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: mypy_primer_diff name: mypy_primer_diff
path: mypy_primer.diff path: mypy_primer.diff
- name: Upload pr-number
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number
memory_usage: memory_usage:
name: Run memory statistics name: Run memory statistics
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }} runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
path: ruff path: ruff
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Install the latest version of uv - name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with: with:
workspaces: "ruff" workspaces: "ruff"
shared-key: "mypy-primer"
- name: Install Rust toolchain - name: Install Rust toolchain
run: rustup show run: rustup show
@@ -108,58 +105,7 @@ jobs:
scripts/mypy_primer.sh scripts/mypy_primer.sh
- name: Upload diff - name: Upload diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: mypy_primer_memory_diff name: mypy_primer_memory_diff
path: 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

@@ -0,0 +1,122 @@
name: PR comment (mypy_primer)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [Run mypy_primer]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The mypy_primer workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download mypy_primer results"
id: download-mypy_primer_diff
if: steps.pr-number.outputs.pr-number
with:
name: mypy_primer_diff
workflow: mypy_primer.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/mypy_primer_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download mypy_primer memory results"
id: download-mypy_primer_memory_diff
if: steps.pr-number.outputs.pr-number
with:
name: mypy_primer_memory_diff
workflow: mypy_primer.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/mypy_primer_memory_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-mypy_primer_diff.outputs.found_artifact == 'true' && steps.download-mypy_primer_memory_diff.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious mypy_primer results that symlink to a secret
# file on this runner
if [[ -L pr/mypy_primer_diff/mypy_primer.diff ]] || [[ -L pr/mypy_primer_memory_diff/mypy_primer_memory.diff ]]
then
echo "Error: mypy_primer.diff and mypy_primer_memory.diff cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment mypy_primer -->' >> comment.txt
echo '## `mypy_primer` results' >> comment.txt
if [ -s "pr/mypy_primer_diff/mypy_primer.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Changes were detected when running on open source projects</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/mypy_primer_diff/mypy_primer.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No ecosystem changes detected ✅' >> comment.txt
fi
if [ -s "pr/mypy_primer_memory_diff/mypy_primer_memory.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Memory usage changes were detected when running on open source projects</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/mypy_primer_memory_diff/mypy_primer_memory.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No memory usage changes detected ✅' >> comment.txt
fi
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment mypy_primer -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

88
.github/workflows/pr-comment.yaml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Ecosystem check comment
on:
workflow_run:
workflows: [CI]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ecosystem workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download pull request number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download ecosystem results"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
name: ecosystem-result
workflow: ci.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/ecosystem
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
run: |
# Guard against malicious ecosystem results that symlink to a secret
# file on this runner
if [[ -L pr/ecosystem/ecosystem-result ]]
then
echo "Error: ecosystem-result cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment ecosystem -->' >> comment.txt
echo '## `ruff-ecosystem` results' >> comment.txt
cat pr/ecosystem/ecosystem-result >> comment.txt
echo "" >> comment.txt
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment ecosystem -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

View File

@@ -17,19 +17,18 @@ on:
required: true required: true
type: string type: string
permissions:
contents: read
jobs: jobs:
mkdocs: mkdocs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
ref: ${{ inputs.ref }} ref: ${{ inputs.ref }}
persist-credentials: true persist-credentials: true
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: 3.12 python-version: 3.12
@@ -60,12 +59,23 @@ jobs:
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV" echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
echo "timestamp=$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" - name: "Install Rust toolchain"
run: rustup show 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" - name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: pip install -r docs/requirements.txt run: pip install -r docs/requirements.txt
- name: "Copy README File" - name: "Copy README File"
@@ -73,8 +83,13 @@ jobs:
python scripts/transform_readme.py --target mkdocs python scripts/transform_readme.py --target mkdocs
python scripts/generate_mkdocs.py 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" - 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" - name: "Clone docs repo"
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs

View File

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

View File

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

View File

@@ -30,18 +30,18 @@ jobs:
env: env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }} CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: "Install Rust toolchain" - name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version: 24 node-version: 22
package-manager-cache: false package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0 - uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies" - name: "Install Node dependencies"
run: npm ci --ignore-scripts run: npm ci
working-directory: playground working-directory: playground
- name: "Run TypeScript checks" - name: "Run TypeScript checks"
run: npm run check 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 # Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish
# job within `cargo-dist`. # job within `cargo-dist`.
name: "Publish wasm" name: "Build and publish wasm"
on: on:
workflow_dispatch:
workflow_call: workflow_call:
inputs: inputs:
plan: plan:
required: true required: true
type: string type: string
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs: jobs:
publish: ruff_wasm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -22,19 +29,31 @@ jobs:
target: [web, bundler, nodejs] target: [web, bundler, nodejs]
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
name: artifacts-wasm-${{ matrix.target }} persist-credentials: false
path: pkg - name: "Install Rust toolchain"
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 run: rustup target add wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
with: 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@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
registry-url: "https://registry.npmjs.org" registry-url: "https://registry.npmjs.org"
- name: "Publish (dry-run)" - name: "Publish (dry-run)"
if: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }} 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" - name: "Publish"
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} 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: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

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

View File

@@ -16,7 +16,8 @@ name: Sync typeshed
# 3. Once the Windows worker is done, a MacOS worker: # 3. Once the Windows worker is done, a MacOS worker:
# a. Checks out the branch created by the Linux 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 # 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 # 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 # 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 # 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
@@ -61,12 +62,12 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout Ruff name: Checkout Ruff
with: with:
path: ruff path: ruff
persist-credentials: true persist-credentials: true
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout typeshed name: Checkout typeshed
with: with:
repository: python/typeshed repository: python/typeshed
@@ -76,7 +77,7 @@ jobs:
run: | run: |
git config --global user.name typeshedbot git config --global user.name typeshedbot
git config --global user.email '<>' git config --global user.email '<>'
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- name: Sync typeshed stubs - name: Sync typeshed stubs
run: | run: |
rm -rf "ruff/${VENDORED_TYPESHED}" rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -125,12 +126,12 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout Ruff name: Checkout Ruff
with: with:
persist-credentials: true persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}} ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- name: Setup git - name: Setup git
run: | run: |
git config --global user.name typeshedbot git config --global user.name typeshedbot
@@ -164,12 +165,12 @@ jobs:
contents: write contents: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: Checkout Ruff name: Checkout Ruff
with: with:
persist-credentials: true persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}} ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- name: Setup git - name: Setup git
run: | run: |
git config --global user.name typeshedbot git config --global user.name typeshedbot
@@ -197,6 +198,37 @@ jobs:
run: | run: |
rm "${VENDORED_TYPESHED}/pyproject.toml" rm "${VENDORED_TYPESHED}/pyproject.toml"
git commit -am "Remove pyproject.toml file" 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@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
with:
tool: cargo-nextest
- name: "Install cargo insta"
if: ${{ success() }}
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
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 - name: Push changes upstream and create a PR
if: ${{ success() }} if: ${{ success() }}
run: | run: |
@@ -208,7 +240,7 @@ jobs:
name: Create an issue if the typeshed sync failed name: Create an issue if the typeshed sync failed
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [sync, docstrings-windows, docstrings-macos-and-pr] 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: permissions:
issues: write issues: write
steps: steps:

View File

@@ -4,13 +4,7 @@ permissions: {}
on: on:
pull_request: pull_request:
# The default for `pull_request` is to trigger on `synchronize`, `opened` and `reopened`. types: [labeled]
# We also add `labeled` here so that the workflow triggers when a label is initially added.
types:
- labeled
- synchronize
- opened
- reopened
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
@@ -23,29 +17,30 @@ env:
RUSTUP_MAX_RETRIES: 10 RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
REF_NAME: ${{ github.ref_name }} REF_NAME: ${{ github.ref_name }}
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs: jobs:
ty-ecosystem-analyzer: ty-ecosystem-analyzer:
name: Compute diagnostic diff name: Compute diagnostic diff
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }} runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
if: contains( github.event.pull_request.labels.*.name, 'ecosystem-analyzer') if: contains(github.event.label.name, 'ecosystem-analyzer')
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
path: ruff path: ruff
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Install the latest version of uv - name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with: 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: with:
workspaces: "ruff" workspaces: "ruff"
lookup-only: false lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain - name: Install Rust toolchain
run: rustup show run: rustup show
@@ -72,7 +67,7 @@ jobs:
cd .. 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 \ ecosystem-analyzer \
--repository ruff \ --repository ruff \
@@ -117,30 +112,45 @@ jobs:
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY" cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
# NOTE: astral-sh-bot uses this artifact to post comments on PRs. echo ${{ github.event.number }} > pr-number
# Make sure to update the bot if you rename the artifact.
- name: "Upload full report" - name: "Deploy to Cloudflare Pages"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
with: id: deploy
name: full-report uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
path: dist/ with:
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 - name: Upload comment
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: comment.md name: comment.md
path: comment.md path: comment.md
- name: Upload pr-number
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number
- name: Upload diagnostics diff - name: Upload diagnostics diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: diff.html name: diff.html
path: dist/diff.html path: dist/diff.html
- name: Upload timing diff - name: Upload timing diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: timing.html name: timing.html
path: dist/timing.html path: dist/timing.html

View File

@@ -0,0 +1,85 @@
name: PR comment (ty ecosystem-analyzer)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [ty ecosystem-analyzer]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ty ecosystem-analyzer workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download comment.md"
id: download-comment
if: steps.pr-number.outputs.pr-number
with:
name: comment.md
workflow: ty-ecosystem-analyzer.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/comment
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-comment.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious ty ecosystem-analyzer results that symlink to a secret
# file on this runner
if [[ -L pr/comment/comment.md ]]
then
echo "Error: comment.md cannot be a symlink"
exit 1
fi
# Note: this identifier is used to find the comment to update on subsequent runs
echo '<!-- generated-comment ty ecosystem-analyzer -->' > comment.md
echo >> comment.md
cat pr/comment/comment.md >> comment.md
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.md >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment ty ecosystem-analyzer -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.md
edit-mode: replace

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 name: ty ecosystem-report
permissions: {} permissions: {}
@@ -18,6 +14,7 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10 RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
jobs: jobs:
ty-ecosystem-report: ty-ecosystem-report:
@@ -25,21 +22,21 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }} runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
path: ruff path: ruff
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Install the latest version of uv - name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with: 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: with:
workspaces: "ruff" workspaces: "ruff"
lookup-only: false lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain - name: Install Rust toolchain
run: rustup show run: rustup show
@@ -55,7 +52,7 @@ jobs:
cd .. 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 \ ecosystem-analyzer \
--verbose \ --verbose \
@@ -73,10 +70,11 @@ jobs:
ecosystem-diagnostics.json \ ecosystem-diagnostics.json \
--output dist/index.html --output dist/index.html
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report. - name: "Deploy to Cloudflare Pages"
# Make sure to update the bot if you rename the artifact. if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
- name: "Upload ecosystem report" id: deploy
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with: with:
name: full-report apiToken: ${{ secrets.CF_API_TOKEN }}
path: dist/ 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: pull_request:
paths: paths:
- "crates/ty*/**" - "crates/ty*/**"
- "!crates/ty_ide/**"
- "!crates/ty_server/**"
- "!crates/ty_test/**"
- "!crates/ty_completion_eval/**"
- "!crates/ty_wasm/**"
- "crates/ruff_db" - "crates/ruff_db"
- "crates/ruff_python_ast" - "crates/ruff_python_ast"
- "crates/ruff_python_parser" - "crates/ruff_python_parser"
@@ -29,7 +24,7 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10 RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
CONFORMANCE_SUITE_COMMIT: 9f6d8ced7cd1c8d92687a4e9c96d7716452e471e CONFORMANCE_SUITE_COMMIT: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
jobs: jobs:
typing_conformance: typing_conformance:
@@ -37,20 +32,20 @@ jobs:
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }} runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
path: ruff path: ruff
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
repository: python/typing repository: python/typing
ref: ${{ env.CONFORMANCE_SUITE_COMMIT }} ref: ${{ env.CONFORMANCE_SUITE_COMMIT }}
path: typing path: typing
persist-credentials: false persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with: with:
workspaces: "ruff" workspaces: "ruff"
@@ -59,6 +54,9 @@ jobs:
- name: Compute diagnostic diff - name: Compute diagnostic diff
shell: bash shell: bash
env:
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
TY_MAX_PARALLELISM: 1
run: | run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff" RUFF_DIR="$GITHUB_WORKSPACE/ruff"
@@ -96,20 +94,23 @@ jobs:
touch typing_conformance_diagnostics.diff touch typing_conformance_diagnostics.diff
fi fi
echo ${{ github.event.number }} > pr-number
echo "${CONFORMANCE_SUITE_COMMIT}" > conformance-suite-commit echo "${CONFORMANCE_SUITE_COMMIT}" > conformance-suite-commit
# 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 - name: Upload diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: typing_conformance_diagnostics_diff name: typing_conformance_diagnostics_diff
path: typing_conformance_diagnostics.diff path: typing_conformance_diagnostics.diff
# NOTE: astral-sh-bot uses this artifact to post comments on PRs. - name: Upload pr-number
# Make sure to update the bot if you rename the artifact. uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number
- name: Upload conformance suite commit - name: Upload conformance suite commit
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: conformance-suite-commit name: conformance-suite-commit
path: conformance-suite-commit path: conformance-suite-commit

View File

@@ -0,0 +1,112 @@
name: PR comment (typing_conformance)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [Run typing conformance]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The typing_conformance workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download typing conformance suite commit
with:
name: conformance-suite-commit
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download typing_conformance results"
id: download-typing_conformance_diff
if: steps.pr-number.outputs.pr-number
with:
name: typing_conformance_diagnostics_diff
workflow: typing_conformance.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/typing_conformance_diagnostics_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-typing_conformance_diff.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious typing_conformance results that symlink to a secret
# file on this runner
if [[ -L pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff ]]
then
echo "Error: typing_conformance_diagnostics.diff cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment typing_conformance_diagnostics_diff -->' >> comment.txt
if [[ -f conformance-suite-commit ]]
then
echo "## Diagnostic diff on [typing conformance tests](https://github.com/python/typing/tree/$(<conformance-suite-commit)/conformance)" >> comment.txt
else
echo "conformance-suite-commit file not found"
echo "## Diagnostic diff on typing conformance tests" >> comment.txt
fi
if [ -s "pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Changes were detected when running ty on typing conformance tests</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No changes detected when running ty on typing conformance tests ✅' >> comment.txt
fi
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment typing_conformance_diagnostics_diff -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

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

@@ -0,0 +1,26 @@
# 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:
dangerous-triggers:
ignore:
- pr-comment.yaml
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: repos:
# Priority 0: Read-only hooks; hooks that modify disjoint file types.
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0 rev: v5.0.0
hooks: hooks:
- id: check-merge-conflict - id: check-merge-conflict
priority: 0
- repo: https://github.com/abravalheri/validate-pyproject - repo: https://github.com/abravalheri/validate-pyproject
rev: v0.24.1 rev: v0.24.1
hooks: hooks:
- id: validate-pyproject - 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 - repo: https://github.com/executablebooks/mdformat
rev: 1.0.0 rev: 0.7.22
hooks: hooks:
- id: mdformat - id: mdformat
language: python # means renovate will also update `additional_dependencies` language: python # means renovate will also update `additional_dependencies`
additional_dependencies: additional_dependencies:
- mdformat-mkdocs==5.0.0 - mdformat-mkdocs==4.0.0
- mdformat-footnote==0.1.2 - mdformat-footnote==0.1.1
exclude: | exclude: |
(?x)^( (?x)^(
docs/formatter/black\.md docs/formatter/black\.md
| docs/\w+\.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 - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.47.0 rev: v0.45.0
hooks: hooks:
- id: markdownlint-fix - id: markdownlint-fix
exclude: | exclude: |
@@ -113,11 +54,9 @@ repos:
docs/formatter/black\.md docs/formatter/black\.md
| docs/\w+\.md | docs/\w+\.md
)$ )$
priority: 1
# Priority 2: blacken-docs runs after markdownlint-fix (both modify markdown).
- repo: https://github.com/adamchainz/blacken-docs - repo: https://github.com/adamchainz/blacken-docs
rev: 1.20.0 rev: 1.19.1
hooks: hooks:
- id: blacken-docs - id: blacken-docs
language: python # means renovate will also update `additional_dependencies` language: python # means renovate will also update `additional_dependencies`
@@ -128,27 +67,74 @@ repos:
.*?invalid(_.+)*_syntax\.md .*?invalid(_.+)*_syntax\.md
)$ )$
additional_dependencies: additional_dependencies:
- black==25.12.0 - black==25.1.0
priority: 2
- 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. # `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`. # Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
- repo: https://github.com/rhysd/actionlint - repo: https://github.com/rhysd/actionlint
rev: v1.7.10 rev: v1.7.7
hooks: hooks:
- id: actionlint - id: actionlint
stages: stages:
# This hook is disabled by default, since it's quite slow. # 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 all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`.
# To run *just* this hook, use `uvx prek run -a actionlint --hook-stage=manual`. # To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`.
- manual - manual
args: args:
- "-ignore=SC2129" # ignorable stylistic lint from shellcheck - "-ignore=SC2129" # ignorable stylistic lint from shellcheck
- "-ignore=SC2016" # another shellcheck lint: seems to have false positives? - "-ignore=SC2016" # another shellcheck lint: seems to have false positives?
language: golang # means renovate will also update `additional_dependencies`
additional_dependencies: additional_dependencies:
# actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions # 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, # and checks these with shellcheck. This is arguably its most useful feature,
# but the integration only works if shellcheck is installed # but the integration only works if shellcheck is installed
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1" - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
priority: 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", "rust-analyzer.check.command": "clippy",
"search.exclude": { "search.exclude": {
"**/*.snap": true "**/*.snap": true
}, }
"ty.diagnosticMode": "openFilesOnly"
} }

View File

@@ -1,414 +1,5 @@
# Changelog # 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.
### Preview features
- [formatter] Allow newlines after function headers without docstrings ([#21110](https://github.com/astral-sh/ruff/pull/21110))
- [formatter] Avoid extra parentheses for long `match` patterns with `as` captures ([#21176](https://github.com/astral-sh/ruff/pull/21176))
- \[`refurb`\] Expand fix safety for keyword arguments and `Decimal`s (`FURB164`) ([#21259](https://github.com/astral-sh/ruff/pull/21259))
- \[`refurb`\] Preserve argument ordering in autofix (`FURB103`) ([#20790](https://github.com/astral-sh/ruff/pull/20790))
### Bug fixes
- [server] Fix missing diagnostics for notebooks ([#21156](https://github.com/astral-sh/ruff/pull/21156))
- \[`flake8-bugbear`\] Ignore non-NFKC attribute names in `B009` and `B010` ([#21131](https://github.com/astral-sh/ruff/pull/21131))
- \[`refurb`\] Fix false negative for underscores before sign in `Decimal` constructor (`FURB157`) ([#21190](https://github.com/astral-sh/ruff/pull/21190))
- \[`ruff`\] Fix false positives on starred arguments (`RUF057`) ([#21256](https://github.com/astral-sh/ruff/pull/21256))
### Rule changes
- \[`airflow`\] extend deprecated argument `concurrency` in `airflow..DAG` (`AIR301`) ([#21220](https://github.com/astral-sh/ruff/pull/21220))
### Documentation
- Improve `extend` docs ([#21135](https://github.com/astral-sh/ruff/pull/21135))
- \[`flake8-comprehensions`\] Fix typo in `C416` documentation ([#21184](https://github.com/astral-sh/ruff/pull/21184))
- Revise Ruff setup instructions for Zed editor ([#20935](https://github.com/astral-sh/ruff/pull/20935))
### Other changes
- Make `ruff analyze graph` work with jupyter notebooks ([#21161](https://github.com/astral-sh/ruff/pull/21161))
### Contributors
- [@chirizxc](https://github.com/chirizxc)
- [@Lee-W](https://github.com/Lee-W)
- [@musicinmybrain](https://github.com/musicinmybrain)
- [@MichaReiser](https://github.com/MichaReiser)
- [@tjkuson](https://github.com/tjkuson)
- [@danparizher](https://github.com/danparizher)
- [@renovate](https://github.com/renovate)
- [@ntBre](https://github.com/ntBre)
- [@gauthsvenkat](https://github.com/gauthsvenkat)
- [@LoicRiegel](https://github.com/LoicRiegel)
## 0.14.3
Released on 2025-10-30.
### Preview features
- Respect `--output-format` with `--watch` ([#21097](https://github.com/astral-sh/ruff/pull/21097))
- \[`pydoclint`\] Fix false positive on explicit exception re-raising (`DOC501`, `DOC502`) ([#21011](https://github.com/astral-sh/ruff/pull/21011))
- \[`pyflakes`\] Revert to stable behavior if imports for module lie in alternate branches for `F401` ([#20878](https://github.com/astral-sh/ruff/pull/20878))
- \[`pylint`\] Implement `stop-iteration-return` (`PLR1708`) ([#20733](https://github.com/astral-sh/ruff/pull/20733))
- \[`ruff`\] Add support for additional eager conversion patterns (`RUF065`) ([#20657](https://github.com/astral-sh/ruff/pull/20657))
### Bug fixes
- Fix finding keyword range for clause header after statement ending with semicolon ([#21067](https://github.com/astral-sh/ruff/pull/21067))
- Fix syntax error false positive on nested alternative patterns ([#21104](https://github.com/astral-sh/ruff/pull/21104))
- \[`ISC001`\] Fix panic when string literals are unclosed ([#21034](https://github.com/astral-sh/ruff/pull/21034))
- \[`flake8-django`\] Apply `DJ001` to annotated fields ([#20907](https://github.com/astral-sh/ruff/pull/20907))
- \[`flake8-pyi`\] Fix `PYI034` to not trigger on metaclasses (`PYI034`) ([#20881](https://github.com/astral-sh/ruff/pull/20881))
- \[`flake8-type-checking`\] Fix `TC003` false positive with `future-annotations` ([#21125](https://github.com/astral-sh/ruff/pull/21125))
- \[`pyflakes`\] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) ([#20564](https://github.com/astral-sh/ruff/pull/20564))
- \[`pyupgrade`\] Fix false positive for `TypeVar` with default on Python \<3.13 (`UP046`,`UP047`) ([#21045](https://github.com/astral-sh/ruff/pull/21045))
### Rule changes
- Add missing docstring sections to the numpy list ([#20931](https://github.com/astral-sh/ruff/pull/20931))
- \[`airflow`\] Extend `airflow.models..Param` check (`AIR311`) ([#21043](https://github.com/astral-sh/ruff/pull/21043))
- \[`airflow`\] Warn that `airflow....DAG.create_dagrun` has been removed (`AIR301`) ([#21093](https://github.com/astral-sh/ruff/pull/21093))
- \[`refurb`\] Preserve digit separators in `Decimal` constructor (`FURB157`) ([#20588](https://github.com/astral-sh/ruff/pull/20588))
### Server
- Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics ([#21105](https://github.com/astral-sh/ruff/pull/21105))
### Documentation
- \[`flake8-bandit`\] Fix correct example for `S308` ([#21128](https://github.com/astral-sh/ruff/pull/21128))
### Other changes
- Clearer error message when `line-length` goes beyond threshold ([#21072](https://github.com/astral-sh/ruff/pull/21072))
### Contributors
- [@danparizher](https://github.com/danparizher)
- [@jvacek](https://github.com/jvacek)
- [@ntBre](https://github.com/ntBre)
- [@augustelalande](https://github.com/augustelalande)
- [@prakhar1144](https://github.com/prakhar1144)
- [@TaKO8Ki](https://github.com/TaKO8Ki)
- [@dylwil3](https://github.com/dylwil3)
- [@fatelei](https://github.com/fatelei)
- [@ShaharNaveh](https://github.com/ShaharNaveh)
- [@Lee-W](https://github.com/Lee-W)
## 0.14.2 ## 0.14.2
Released on 2025-10-23. Released on 2025-10-23.

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 You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
run Python utility commands. 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: when making a commit:
```shell ```shell
uv tool install prek uv tool install pre-commit
prek install pre-commit install
``` ```
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`), 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 ```shell
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
RUFF_UPDATE_SCHEMA=1 cargo test # Rust testing and updating ruff.schema.json 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 These checks will run on GitHub Actions when you open your pull request, but running them locally
@@ -280,57 +280,15 @@ Note that plugin-specific configuration options are defined in their own modules
Finally, regenerate the documentation and generated code with `cargo dev generate-all`. Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
### Opening a PR
After you finish your changes, the next step is to open a PR. By default, two
sections will be filled into the PR body: the summary and the test plan.
#### The summary
The summary is intended to give us as maintainers information about your PR.
This should typically include a link to the relevant issue(s) you're addressing
in your PR, as well as a summary of the issue and your approach to fixing it. If
you have any questions about your approach or design, or if you considered
alternative approaches, that can also be helpful to include.
AI can be helpful in generating both the code and summary of your PR, but a
successful contribution should still be carefully reviewed by you and the
summary editorialized before submitting a PR. A great summary is thorough but
also succinct and gives us the context we need to review your PR.
You can find examples of excellent issues and PRs by searching for the
[`great writeup`](https://github.com/astral-sh/ruff/issues?q=label%3A%22great%20writeup%22)
label.
#### The test plan
The test plan is likely to be shorter than the summary and can be as simple as
"Added new snapshot tests for `RUF123`," at least for rule bugs. For LSP or some
types of CLI changes, in particular, it can also be helpful to include
screenshots or recordings of your change in action.
#### Ecosystem report
After opening the PR, an ecosystem report will be run as part of CI. This shows
a diff of linter and formatter behavior before and after the changes in your PR.
Going through these changes and reporting your findings in the PR summary or an
additional comment help us to review your PR more efficiently. It's also a great
way to find new test cases to incorporate into your PR if you identify any
issues.
#### PR status
To help us know when your PR is ready for review again, please either move your
PR back to a draft while working on it (marking it ready for review afterwards
will ping the previous reviewers) or explicitly re-request a review. This helps
us to avoid re-reviewing a PR while you're still working on it and also to
prioritize PRs that are definitely ready for review.
You can also thumbs-up or mark as resolved any comments we leave to let us know
you addressed them.
## MkDocs ## 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: To preview any changes to the documentation locally:
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install). 1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
@@ -344,7 +302,11 @@ To preview any changes to the documentation locally:
1. Run the development server with: 1. Run the development server with:
```shell ```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 The documentation should then be available locally at
@@ -381,7 +343,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 - 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 - 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: Additionally, for minor releases:

774
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package] [workspace.package]
# Please update rustfmt.toml when bumping the Rust edition # Please update rustfmt.toml when bumping the Rust edition
edition = "2024" edition = "2024"
rust-version = "1.90" rust-version = "1.88"
homepage = "https://docs.astral.sh/ruff" homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff" documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/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_combine = { path = "crates/ty_combine" }
ty_completion_eval = { path = "crates/ty_completion_eval" } ty_completion_eval = { path = "crates/ty_completion_eval" }
ty_ide = { path = "crates/ty_ide" } ty_ide = { path = "crates/ty_ide" }
ty_module_resolver = { path = "crates/ty_module_resolver" }
ty_project = { path = "crates/ty_project", default-features = false } ty_project = { path = "crates/ty_project", default-features = false }
ty_python_semantic = { path = "crates/ty_python_semantic" } ty_python_semantic = { path = "crates/ty_python_semantic" }
ty_server = { path = "crates/ty_server" } ty_server = { path = "crates/ty_server" }
@@ -58,8 +57,8 @@ anstream = { version = "0.6.18" }
anstyle = { version = "1.0.10" } anstyle = { version = "1.0.10" }
anyhow = { version = "1.0.80" } anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" } arc-swap = { version = "1.7.1" }
argfile = { version = "0.2.0" }
assert_fs = { version = "1.1.0" } assert_fs = { version = "1.1.0" }
argfile = { version = "0.2.0" }
bincode = { version = "2.0.0" } bincode = { version = "2.0.0" }
bitflags = { version = "2.5.0" } bitflags = { version = "2.5.0" }
bitvec = { version = "1.0.1", default-features = false, features = [ 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 = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" } clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.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 } codspeed-criterion-compat = { version = "4.0.4", default-features = false }
colored = { version = "3.0.0" } colored = { version = "3.0.0" }
compact_str = "0.9.0"
console_error_panic_hook = { version = "0.1.7" } console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" } console_log = { version = "1.0.0" }
countme = { version = "3.0.1" } 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" } crossbeam = { version = "0.8.4" }
csv = { version = "1.3.1" }
dashmap = { version = "6.0.1" } dashmap = { version = "6.0.1" }
datatest-stable = { version = "0.3.3" } dir-test = { version = "0.4.0" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
drop_bomb = { version = "0.1.5" }
dunce = { version = "1.0.5" } dunce = { version = "1.0.5" }
etcetera = { version = "0.11.0" } drop_bomb = { version = "0.1.5" }
etcetera = { version = "0.10.0" }
fern = { version = "0.7.0" } fern = { version = "0.7.0" }
filetime = { version = "0.2.23" } 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", "derive",
"smallvec", "smallvec",
"hashbrown", "hashbrown",
"compact-str", "compact-str",
"ordermap"
] } ] }
getrandom = { version = "0.3.1" }
glob = { version = "0.3.1" } glob = { version = "0.3.1" }
globset = { version = "0.4.14" } globset = { version = "0.4.14" }
globwalk = { version = "0.9.1" } globwalk = { version = "0.9.1" }
@@ -105,7 +103,7 @@ hashbrown = { version = "0.16.0", default-features = false, features = [
"inline-more", "inline-more",
] } ] }
heck = "0.5.0" heck = "0.5.0"
ignore = { version = "0.4.24" } ignore = { version = "0.4.22" }
imara-diff = { version = "0.1.5" } imara-diff = { version = "0.1.5" }
imperative = { version = "1.0.4" } imperative = { version = "1.0.4" }
indexmap = { version = "2.6.0" } indexmap = { version = "2.6.0" }
@@ -117,8 +115,8 @@ is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" } is-wsl = { version = "0.4.0" }
itertools = { version = "0.14.0" } itertools = { version = "0.14.0" }
jiff = { version = "0.2.0" } jiff = { version = "0.2.0" }
jod-thread = { version = "1.0.0" }
js-sys = { version = "0.3.69" } js-sys = { version = "0.3.69" }
jod-thread = { version = "1.0.0" }
libc = { version = "0.2.153" } libc = { version = "0.2.153" }
libcst = { version = "1.8.4", default-features = false } libcst = { version = "1.8.4", default-features = false }
log = { version = "0.4.17" } log = { version = "0.4.17" }
@@ -126,12 +124,12 @@ lsp-server = { version = "0.7.6" }
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [ lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
"proposed", "proposed",
] } ] }
matchit = { version = "0.9.0" } matchit = { version = "0.8.1" }
memchr = { version = "2.7.1" } memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" } mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" } natord = { version = "1.0.9" }
notify = { version = "8.0.0" } notify = { version = "8.0.0" }
ordermap = { version = "1.0.0" } ordermap = { version = "0.5.0" }
path-absolutize = { version = "3.1.1" } path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" } path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" } pathdiff = { version = "0.2.1" }
@@ -140,8 +138,6 @@ pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" } proc-macro2 = { version = "1.0.79" }
pyproject-toml = { version = "0.13.4" } pyproject-toml = { version = "0.13.4" }
quick-junit = { version = "0.5.0" } 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" } quote = { version = "1.0.23" }
rand = { version = "0.9.0" } rand = { version = "0.9.0" }
rayon = { version = "1.10.0" } rayon = { version = "1.10.0" }
@@ -150,7 +146,7 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" } rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" } rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml` # 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 = "cdd0b85516a52c18b8a6d17a2279a96ed6c3e198", default-features = false, features = [
"compact_str", "compact_str",
"macros", "macros",
"salsa_unstable", "salsa_unstable",
@@ -177,7 +173,6 @@ snapbox = { version = "0.6.0", features = [
static_assertions = "1.1.0" static_assertions = "1.1.0"
strum = { version = "0.27.0", features = ["strum_macros"] } strum = { version = "0.27.0", features = ["strum_macros"] }
strum_macros = { version = "0.27.0" } strum_macros = { version = "0.27.0" }
supports-hyperlinks = { version = "3.1.0" }
syn = { version = "2.0.55" } syn = { version = "2.0.55" }
tempfile = { version = "3.9.0" } tempfile = { version = "3.9.0" }
test-case = { version = "3.3.1" } test-case = { version = "3.3.1" }
@@ -198,9 +193,9 @@ tryfn = { version = "0.2.1" }
typed-arena = { version = "2.0.2" } typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" } unic-ucd-category = { version = "0.9" }
unicode-ident = { version = "1.0.12" } unicode-ident = { version = "1.0.12" }
unicode-normalization = { version = "0.1.23" }
unicode-width = { version = "0.2.0" } unicode-width = { version = "0.2.0" }
unicode_names2 = { version = "1.2.2" } unicode_names2 = { version = "1.2.2" }
unicode-normalization = { version = "0.1.23" }
url = { version = "2.5.0" } url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] } uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
walkdir = { version = "2.3.2" } walkdir = { version = "2.3.2" }
@@ -210,13 +205,8 @@ wild = { version = "2" }
zip = { version = "0.6.6", default-features = false } zip = { version = "0.6.6", default-features = false }
[workspace.metadata.cargo-shear] [workspace.metadata.cargo-shear]
ignored = [ ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2", "ty_completion_eval"]
"getrandom",
"ruff_options_metadata",
"uuid",
"get-size2",
"ty_completion_eval",
]
[workspace.lints.rust] [workspace.lints.rust]
unsafe_code = "warn" 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. # Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow" large_stack_arrays = "allow"
[profile.release] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 16 codegen-units = 16
@@ -290,12 +281,6 @@ codegen-units = 1
[profile.release.package.salsa] [profile.release.package.salsa]
codegen-units = 1 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] [profile.dev.package.insta]
opt-level = 3 opt-level = 3
@@ -335,11 +320,6 @@ strip = false
debug = "full" debug = "full"
lto = false 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. # The profile that 'cargo dist' will build with.
[profile.dist] [profile.dist]
inherits = "release" 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). ...and [many more](#whos-using-ruff).
Ruff is backed by [Astral](https://astral.sh), the creators of Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty). or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
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 ## Testimonials
@@ -150,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version. # For a specific version.
curl -LsSf https://astral.sh/ruff/0.14.11/install.sh | sh curl -LsSf https://astral.sh/ruff/0.14.2/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.11/install.ps1 | iex" powershell -c "irm https://astral.sh/ruff/0.14.2/install.ps1 | iex"
``` ```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff), 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 ```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.14.11 rev: v0.14.2
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff-check - id: ruff-check
@@ -494,7 +491,6 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PyTorch](https://github.com/pytorch/pytorch) - [PyTorch](https://github.com/pytorch/pytorch)
- [Pydantic](https://github.com/pydantic/pydantic) - [Pydantic](https://github.com/pydantic/pydantic)
- [Pylint](https://github.com/PyCQA/pylint) - [Pylint](https://github.com/PyCQA/pylint)
- [PyScripter](https://github.com/pyscripter/pyscripter)
- [PyVista](https://github.com/pyvista/pyvista) - [PyVista](https://github.com/pyvista/pyvista)
- [Reflex](https://github.com/reflex-dev/reflex) - [Reflex](https://github.com/reflex-dev/reflex)
- [River](https://github.com/online-ml/river) - [River](https://github.com/online-ml/river)

View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ruff" name = "ruff"
version = "0.14.11" version = "0.14.2"
publish = true publish = true
authors = { workspace = true } authors = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
@@ -12,13 +12,6 @@ license = { workspace = true }
readme = "../../README.md" readme = "../../README.md"
default-run = "ruff" default-run = "ruff"
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[dependencies] [dependencies]
ruff_cache = { workspace = true } ruff_cache = { workspace = true }
ruff_db = { workspace = true, default-features = false, features = ["os"] } ruff_db = { workspace = true, default-features = false, features = ["os"] }
@@ -48,7 +41,6 @@ colored = { workspace = true }
filetime = { workspace = true } filetime = { workspace = true }
globwalk = { workspace = true } globwalk = { workspace = true }
ignore = { workspace = true } ignore = { workspace = true }
indexmap = { workspace = true }
is-macro = { workspace = true } is-macro = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
jiff = { workspace = true } jiff = { workspace = true }
@@ -69,12 +61,6 @@ tracing = { workspace = true, features = ["log"] }
walkdir = { workspace = true } walkdir = { workspace = true }
wild = { 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] [dev-dependencies]
# Enable test rules during development # Enable test rules during development
ruff_linter = { workspace = true, features = ["clap", "test-rules"] } ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
@@ -90,5 +76,18 @@ ruff_python_trivia = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
test-case = { 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] [lints]
workspace = true workspace = true

View File

@@ -7,10 +7,8 @@ use std::sync::Arc;
use crate::commands::completions::config::{OptionString, OptionStringParser}; use crate::commands::completions::config::{OptionString, OptionStringParser};
use anyhow::bail; use anyhow::bail;
use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Effects};
use clap::builder::{TypedValueParser, ValueParserFactory}; use clap::builder::{TypedValueParser, ValueParserFactory};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, command};
use colored::Colorize; use colored::Colorize;
use itertools::Itertools; use itertools::Itertools;
use path_absolutize::path_dedot; use path_absolutize::path_dedot;
@@ -80,13 +78,6 @@ impl GlobalConfigArgs {
} }
} }
// Configures Clap v3-style help menu colors
const STYLES: Styles = Styles::styled()
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
.usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
.literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
.placeholder(AnsiColor::Cyan.on_default());
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command( #[command(
author, author,
@@ -95,7 +86,6 @@ const STYLES: Styles = Styles::styled()
after_help = "For help with a specific command, see: `ruff help <command>`." after_help = "For help with a specific command, see: `ruff help <command>`."
)] )]
#[command(version)] #[command(version)]
#[command(styles = STYLES)]
pub struct Args { pub struct Args {
#[command(subcommand)] #[command(subcommand)]
pub(crate) command: Command, pub(crate) command: Command,
@@ -167,7 +157,6 @@ pub enum AnalyzeCommand {
} }
#[derive(Clone, Debug, clap::Parser)] #[derive(Clone, Debug, clap::Parser)]
#[expect(clippy::struct_excessive_bools)]
pub struct AnalyzeGraphCommand { pub struct AnalyzeGraphCommand {
/// List of files or directories to include. /// List of files or directories to include.
#[clap(help = "List of files or directories to include [default: .]")] #[clap(help = "List of files or directories to include [default: .]")]
@@ -194,12 +183,6 @@ pub struct AnalyzeGraphCommand {
/// Path to a virtual environment to use for resolving additional dependencies /// Path to a virtual environment to use for resolving additional dependencies
#[arg(long)] #[arg(long)]
python: Option<PathBuf>, 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 // The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
@@ -422,13 +405,8 @@ pub struct CheckCommand {
)] )]
pub statistics: bool, pub statistics: bool,
/// Enable automatic additions of `noqa` directives to failing lines. /// Enable automatic additions of `noqa` directives to failing lines.
/// Optionally provide a reason to append after the codes.
#[arg( #[arg(
long, long,
value_name = "REASON",
default_missing_value = "",
num_args = 0..=1,
require_equals = true,
// conflicts_with = "add_noqa", // conflicts_with = "add_noqa",
conflicts_with = "show_files", conflicts_with = "show_files",
conflicts_with = "show_settings", conflicts_with = "show_settings",
@@ -440,7 +418,7 @@ pub struct CheckCommand {
conflicts_with = "fix", conflicts_with = "fix",
conflicts_with = "diff", conflicts_with = "diff",
)] )]
pub add_noqa: Option<String>, pub add_noqa: bool,
/// See the files Ruff will be run against with the current settings. /// See the files Ruff will be run against with the current settings.
#[arg( #[arg(
long, long,
@@ -846,10 +824,6 @@ impl AnalyzeGraphCommand {
string_imports_min_dots: self.min_dots, string_imports_min_dots: self.min_dots,
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from), preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
target_version: self.target_version.map(ast::PythonVersion::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() ..ExplicitConfigOverrides::default()
}; };
@@ -1073,7 +1047,7 @@ Possible choices:
/// etc.). /// etc.).
#[expect(clippy::struct_excessive_bools)] #[expect(clippy::struct_excessive_bools)]
pub struct CheckArguments { pub struct CheckArguments {
pub add_noqa: Option<String>, pub add_noqa: bool,
pub diff: bool, pub diff: bool,
pub exit_non_zero_on_fix: bool, pub exit_non_zero_on_fix: bool,
pub exit_zero: bool, pub exit_zero: bool,
@@ -1346,7 +1320,6 @@ struct ExplicitConfigOverrides {
extension: Option<Vec<ExtensionPair>>, extension: Option<Vec<ExtensionPair>>,
detect_string_imports: Option<bool>, detect_string_imports: Option<bool>,
string_imports_min_dots: Option<usize>, string_imports_min_dots: Option<usize>,
type_checking_imports: Option<bool>,
} }
impl ConfigurationTransformer for ExplicitConfigOverrides { impl ConfigurationTransformer for ExplicitConfigOverrides {
@@ -1437,9 +1410,6 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
if let Some(string_imports_min_dots) = &self.string_imports_min_dots { if let Some(string_imports_min_dots) = &self.string_imports_min_dots {
config.analyze.string_imports_min_dots = Some(*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 config
} }

View File

@@ -21,7 +21,6 @@ pub(crate) fn add_noqa(
files: &[PathBuf], files: &[PathBuf],
pyproject_config: &PyprojectConfig, pyproject_config: &PyprojectConfig,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
reason: Option<&str>,
) -> Result<usize> { ) -> Result<usize> {
// Collect all the files to check. // Collect all the files to check.
let start = Instant::now(); let start = Instant::now();
@@ -77,14 +76,7 @@ pub(crate) fn add_noqa(
return None; return None;
} }
}; };
match add_noqa_to_path( match add_noqa_to_path(path, package, &source_kind, source_type, &settings.linter) {
path,
package,
&source_kind,
source_type,
&settings.linter,
reason,
) {
Ok(count) => Some(count), Ok(count) => Some(count),
Err(e) => { Err(e) => {
error!("Failed to add noqa to {}: {e}", path.display()); error!("Failed to add noqa to {}: {e}", path.display());

View File

@@ -2,17 +2,15 @@ use crate::args::{AnalyzeGraphArgs, ConfigArguments};
use crate::resolve::resolve; use crate::resolve::resolve;
use crate::{ExitStatus, resolve_default_files}; use crate::{ExitStatus, resolve_default_files};
use anyhow::Result; use anyhow::Result;
use indexmap::IndexSet;
use log::{debug, warn}; use log::{debug, warn};
use path_absolutize::CWD; use path_absolutize::CWD;
use ruff_db::system::{SystemPath, SystemPathBuf}; use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports}; use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
use ruff_linter::package::PackageRoot; use ruff_linter::package::PackageRoot;
use ruff_linter::source_kind::SourceKind;
use ruff_linter::{warn_user, warn_user_once}; use ruff_linter::{warn_user, warn_user_once};
use ruff_python_ast::{PySourceType, SourceType}; use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path}; 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::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -60,34 +58,17 @@ pub(crate) fn analyze_graph(
}) })
.collect::<FxHashMap<_, _>>(); .collect::<FxHashMap<_, _>>();
// Create a database from the source roots, combining configured `src` paths with detected // Create a database from the source roots.
// package roots. Configured paths are added first so they take precedence, and duplicates let src_roots = package_roots
// are removed. .values()
let mut src_roots: IndexSet<SystemPathBuf, FxBuildHasher> = IndexSet::default(); .filter_map(|package| package.as_deref())
.filter_map(|package| package.parent())
// Add configured `src` paths first (for precedence), filtering to only include existing .map(Path::to_path_buf)
// directories. .filter_map(|path| SystemPathBuf::from_path_buf(path).ok())
src_roots.extend( .collect();
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()),
);
let db = ModuleDb::from_src_roots( let db = ModuleDb::from_src_roots(
src_roots.into_iter().collect(), src_roots,
pyproject_config pyproject_config
.settings .settings
.analyze .analyze
@@ -123,7 +104,6 @@ pub(crate) fn analyze_graph(
let settings = resolver.resolve(path); let settings = resolver.resolve(path);
let string_imports = settings.analyze.string_imports; let string_imports = settings.analyze.string_imports;
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned(); let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
let type_checking_imports = settings.analyze.type_checking_imports;
// Skip excluded files. // Skip excluded files.
if (settings.file_resolver.force_exclude || !resolved_file.is_root()) if (settings.file_resolver.force_exclude || !resolved_file.is_root())
@@ -147,6 +127,10 @@ pub(crate) fn analyze_graph(
}, },
Some(language) => PySourceType::from(language), Some(language) => PySourceType::from(language),
}; };
if matches!(source_type, PySourceType::Ipynb) {
debug!("Ignoring Jupyter notebook: {}", path.display());
continue;
}
// Convert to system paths. // Convert to system paths.
let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else { let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else {
@@ -163,35 +147,13 @@ pub(crate) fn analyze_graph(
let root = root.clone(); let root = root.clone();
let result = inner_result.clone(); let result = inner_result.clone();
scope.spawn(move |_| { scope.spawn(move |_| {
// Extract source code (handles both .py and .ipynb files)
let source_kind = match SourceKind::from_path(path.as_std_path(), source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => {
debug!("Skipping non-Python notebook: {path}");
return;
}
Err(err) => {
warn!("Failed to read source for {path}: {err}");
return;
}
};
let source_code = source_kind.source_code();
// Identify any imports via static analysis. // Identify any imports via static analysis.
let mut imports = ModuleImports::detect( let mut imports =
&db, ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
source_code, .unwrap_or_else(|err| {
source_type, warn!("Failed to generate import map for {path}: {err}");
&path, ModuleImports::default()
package.as_deref(), });
string_imports,
type_checking_imports,
)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");
ModuleImports::default()
});
debug!("Discovered {} imports for {}", imports.len(), path); debug!("Discovered {} imports for {}", imports.len(), path);

View File

@@ -370,7 +370,7 @@ pub(crate) fn format_source(
let line_index = LineIndex::from_source_text(unformatted); let line_index = LineIndex::from_source_text(unformatted);
let byte_range = range.to_text_range(unformatted, &line_index); let byte_range = range.to_text_range(unformatted, &line_index);
format_range(unformatted, byte_range, options).map(|formatted_range| { format_range(unformatted, byte_range, options).map(|formatted_range| {
let mut formatted = unformatted.clone(); let mut formatted = unformatted.to_string();
formatted.replace_range( formatted.replace_range(
std::ops::Range::<usize>::from(formatted_range.source_range()), std::ops::Range::<usize>::from(formatted_range.source_range()),
formatted_range.as_code(), formatted_range.as_code(),
@@ -1305,7 +1305,7 @@ mod tests {
settings.add_filter(r"(Panicked at) [^:]+:\d+:\d+", "$1 <location>"); settings.add_filter(r"(Panicked at) [^:]+:\d+:\d+", "$1 <location>");
let _s = settings.bind_to_scope(); let _s = settings.bind_to_scope();
assert_snapshot!(str::from_utf8(&buf)?, @" assert_snapshot!(str::from_utf8(&buf)?, @r"
io: test.py: Permission denied io: test.py: Permission denied
--> test.py:1:1 --> test.py:1:1

View File

@@ -16,8 +16,6 @@ struct LinterInfo {
prefix: &'static str, prefix: &'static str,
name: &'static str, name: &'static str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
url: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
categories: Option<Vec<LinterCategoryInfo>>, categories: Option<Vec<LinterCategoryInfo>>,
} }
@@ -52,7 +50,6 @@ pub(crate) fn linter(format: HelpFormat) -> Result<()> {
.map(|linter_info| LinterInfo { .map(|linter_info| LinterInfo {
prefix: linter_info.common_prefix(), prefix: linter_info.common_prefix(),
name: linter_info.name(), name: linter_info.name(),
url: linter_info.url(),
categories: linter_info.upstream_categories().map(|cats| { categories: linter_info.upstream_categories().map(|cats| {
cats.iter() cats.iter()
.map(|c| LinterCategoryInfo { .map(|c| LinterCategoryInfo {

View File

@@ -29,10 +29,10 @@ pub(crate) fn show_settings(
bail!("No files found under the given path"); 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())?; 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())?; writeln!(writer, "Settings path: \"{}\"", settings_path.display())?;
} }
write!(writer, "{settings}")?; 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/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/notebook.ipynb:1:1: E902 Permission denied (os error 13)
/home/ferris/project/pyproject.toml: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 anyhow::Result;
use clap::CommandFactory; use clap::CommandFactory;
use colored::Colorize; use colored::Colorize;
use log::error; use log::{error, warn};
use notify::{RecursiveMode, Watcher, recommended_watcher}; use notify::{RecursiveMode, Watcher, recommended_watcher};
use args::{GlobalConfigArgs, ServerCommand}; use args::{GlobalConfigArgs, ServerCommand};
@@ -319,20 +319,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
warn_user!("Detected debug build without --no-cache."); warn_user!("Detected debug build without --no-cache.");
} }
if let Some(reason) = &cli.add_noqa { if cli.add_noqa {
if !fix_mode.is_generate() { if !fix_mode.is_generate() {
warn_user!("--fix is incompatible with --add-noqa."); warn_user!("--fix is incompatible with --add-noqa.");
} }
if reason.contains(['\n', '\r']) {
return Err(anyhow::anyhow!(
"--add-noqa <reason> cannot contain newline characters"
));
}
let reason_opt = (!reason.is_empty()).then_some(reason.as_str());
let modifications = let modifications =
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments, reason_opt)?; commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
if modifications > 0 && config_arguments.log_level >= LogLevel::Default { if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
let s = if modifications == 1 { "" } else { "s" }; let s = if modifications == 1 { "" } else { "s" };
#[expect(clippy::print_stderr)] #[expect(clippy::print_stderr)]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -132,29 +132,29 @@ fn dependents() -> Result<()> {
insta::with_settings!({ insta::with_settings!({
filters => INSTA_FILTERS.to_vec(), filters => INSTA_FILTERS.to_vec(),
}, { }, {
assert_cmd_snapshot!(command().arg("--direction").arg("dependents").current_dir(&root), @r#" assert_cmd_snapshot!(command().arg("--direction").arg("dependents").current_dir(&root), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
{ {
"ruff/__init__.py": [], "ruff/__init__.py": [],
"ruff/a.py": [], "ruff/a.py": [],
"ruff/b.py": [ "ruff/b.py": [
"ruff/a.py" "ruff/a.py"
], ],
"ruff/c.py": [ "ruff/c.py": [
"ruff/b.py" "ruff/b.py"
], ],
"ruff/d.py": [ "ruff/d.py": [
"ruff/c.py" "ruff/c.py"
], ],
"ruff/e.py": [ "ruff/e.py": [
"ruff/d.py" "ruff/d.py"
] ]
} }
----- stderr ----- ----- stderr -----
"#); "###);
}); });
Ok(()) Ok(())
@@ -184,21 +184,21 @@ fn string_detection() -> Result<()> {
insta::with_settings!({ insta::with_settings!({
filters => INSTA_FILTERS.to_vec(), filters => INSTA_FILTERS.to_vec(),
}, { }, {
assert_cmd_snapshot!(command().current_dir(&root), @r#" assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
{ {
"ruff/__init__.py": [], "ruff/__init__.py": [],
"ruff/a.py": [ "ruff/a.py": [
"ruff/b.py" "ruff/b.py"
], ],
"ruff/b.py": [], "ruff/b.py": [],
"ruff/c.py": [] "ruff/c.py": []
} }
----- stderr ----- ----- stderr -----
"#); "###);
}); });
insta::with_settings!({ insta::with_settings!({
@@ -319,7 +319,7 @@ fn globs() -> Result<()> {
insta::with_settings!({ insta::with_settings!({
filters => INSTA_FILTERS.to_vec(), filters => INSTA_FILTERS.to_vec(),
}, { }, {
assert_cmd_snapshot!(command().current_dir(&root), @r#" assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -340,7 +340,7 @@ fn globs() -> Result<()> {
} }
----- stderr ----- ----- stderr -----
"#); "###);
}); });
Ok(()) Ok(())
@@ -368,7 +368,7 @@ fn exclude() -> Result<()> {
insta::with_settings!({ insta::with_settings!({
filters => INSTA_FILTERS.to_vec(), filters => INSTA_FILTERS.to_vec(),
}, { }, {
assert_cmd_snapshot!(command().current_dir(&root), @r#" assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -381,7 +381,7 @@ fn exclude() -> Result<()> {
} }
----- stderr ----- ----- stderr -----
"#); "###);
}); });
Ok(()) Ok(())
@@ -421,7 +421,7 @@ fn wildcard() -> Result<()> {
insta::with_settings!({ insta::with_settings!({
filters => INSTA_FILTERS.to_vec(), filters => INSTA_FILTERS.to_vec(),
}, { }, {
assert_cmd_snapshot!(command().current_dir(&root), @r#" assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -443,7 +443,7 @@ fn wildcard() -> Result<()> {
} }
----- stderr ----- ----- stderr -----
"#); "###);
}); });
Ok(()) Ok(())
@@ -639,7 +639,7 @@ fn venv() -> Result<()> {
}, { }, {
assert_cmd_snapshot!( assert_cmd_snapshot!(
command().args(["--python", "none"]).arg("packages/albatross").current_dir(&root), command().args(["--python", "none"]).arg("packages/albatross").current_dir(&root),
@" @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -653,248 +653,3 @@ fn venv() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn notebook_basic() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
def helper():
pass
"#})?;
// Create a basic notebook with a simple import
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ruff.a import helper"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"notebook.ipynb": [
"ruff/a.py"
],
"ruff/__init__.py": [],
"ruff/a.py": []
}
----- 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(())
}
#[test]
fn notebook_with_magic() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
def helper():
pass
"#})?;
// Create a notebook with IPython magic commands and imports
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ruff.a import helper"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"notebook.ipynb": [
"ruff/a.py"
],
"ruff/__init__.py": [],
"ruff/a.py": []
}
----- 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() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("--check"), @" .arg("--check"), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -71,7 +71,7 @@ fn format_warn_stdin_filename_with_files() -> Result<()> {
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--stdin-filename", "foo.py"]) .args(["--isolated", "--stdin-filename", "foo.py"])
.arg("foo.py") .arg("foo.py")
.pass_stdin("foo = 1"), @" .pass_stdin("foo = 1"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -87,7 +87,7 @@ fn format_warn_stdin_filename_with_files() -> Result<()> {
fn nonexistent_config_file() -> Result<()> { fn nonexistent_config_file() -> Result<()> {
let test = CliTest::new()?; let test = CliTest::new()?;
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--config", "foo.toml", "."]), @" .args(["--config", "foo.toml", "."]), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -111,7 +111,7 @@ fn nonexistent_config_file() -> Result<()> {
fn config_override_rejected_if_invalid_toml() -> Result<()> { fn config_override_rejected_if_invalid_toml() -> Result<()> {
let test = CliTest::new()?; let test = CliTest::new()?;
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--config", "foo = bar", "."]), @" .args(["--config", "foo = bar", "."]), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -145,7 +145,7 @@ fn too_many_config_files() -> Result<()> {
.arg("ruff.toml") .arg("ruff.toml")
.arg("--config") .arg("--config")
.arg("ruff2.toml") .arg("ruff2.toml")
.arg("."), @" .arg("."), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -168,7 +168,7 @@ fn config_file_and_isolated() -> Result<()> {
.arg("--isolated") .arg("--isolated")
.arg("--config") .arg("--config")
.arg("ruff.toml") .arg("ruff.toml")
.arg("."), @" .arg("."), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -390,7 +390,7 @@ fn mixed_line_endings() -> Result<()> {
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--diff") .arg("--diff")
.arg("--isolated") .arg("--isolated")
.arg("."), @" .arg("."), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -446,7 +446,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude // Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
.arg("test.py") .arg("test.py")
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options // Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @" .arg("."), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -469,7 +469,7 @@ fn deduplicate_directory_and_explicit_file() -> Result<()> {
.arg("--check") .arg("--check")
.arg(".") .arg(".")
.arg("main.py"), .arg("main.py"),
@" @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -495,7 +495,7 @@ from module import =
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--check") .arg("--check")
.arg("--isolated") .arg("--isolated")
.arg("main.py"), @" .arg("main.py"), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -522,7 +522,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("--check") .arg("--check")
.arg("main.py"), @" .arg("main.py"), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -534,7 +534,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("main.py"), @" .arg("main.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -545,7 +545,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("main.py"), @" .arg("main.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -614,7 +614,7 @@ fn output_format_notebook() -> Result<()> {
assert_cmd_snapshot!( assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--preview", "--check"]).arg(path), test.format_command().args(["--isolated", "--preview", "--check"]).arg(path),
@" @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -672,7 +672,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("--exit-non-zero-on-format") .arg("--exit-non-zero-on-format")
.arg("main.py"), @" .arg("main.py"), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -685,7 +685,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("--exit-non-zero-on-format") .arg("--exit-non-zero-on-format")
.arg("main.py"), @" .arg("main.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -701,7 +701,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("--exit-non-zero-on-fix") .arg("--exit-non-zero-on-fix")
.arg("main.py"), @" .arg("main.py"), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -714,7 +714,7 @@ if __name__ == "__main__":
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--isolated") .arg("--isolated")
.arg("--exit-non-zero-on-fix") .arg("--exit-non-zero-on-fix")
.arg("main.py"), @" .arg("main.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -771,7 +771,7 @@ OTHER = "OTHER"
// Explicitly pass test.py, should not be formatted because of --force-exclude // Explicitly pass test.py, should not be formatted because of --force-exclude
.arg("test.py") .arg("test.py")
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options // Format all other files in the directory, should respect the `exclude` and `format.exclude` options
.arg("."), @" .arg("."), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -931,7 +931,7 @@ tab-size = 2
.pass_stdin(r" .pass_stdin(r"
if True: if True:
pass pass
"), @" "), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -1144,7 +1144,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--config") .arg("--config")
.arg("ruff.toml") .arg("ruff.toml")
.arg("test.py"), @" .arg("test.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -1184,7 +1184,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--config") .arg("--config")
.arg("ruff.toml") .arg("ruff.toml")
.arg("test.py"), @" .arg("test.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -1216,7 +1216,7 @@ def say_hy(name: str):
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.arg("--config") .arg("--config")
.arg("ruff.toml") .arg("ruff.toml")
.arg("test.py"), @" .arg("test.py"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -1246,7 +1246,7 @@ fn test_diff() -> Result<()> {
assert_cmd_snapshot!( assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff"]).args(paths), test.format_command().args(["--isolated", "--diff"]).args(paths),
@" @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -1311,7 +1311,7 @@ fn test_diff_no_change() -> Result<()> {
let paths = [fixtures.join("unformatted.py")]; let paths = [fixtures.join("unformatted.py")];
assert_cmd_snapshot!( assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff"]).args(paths), test.format_command().args(["--isolated", "--diff"]).args(paths),
@" @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -1341,7 +1341,7 @@ fn test_diff_stdin_unformatted() -> Result<()> {
test.format_command() test.format_command()
.args(["--isolated", "--diff", "-", "--stdin-filename", "unformatted.py"]) .args(["--isolated", "--diff", "-", "--stdin-filename", "unformatted.py"])
.pass_stdin(unformatted), .pass_stdin(unformatted),
@" @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@@ -1366,7 +1366,7 @@ fn test_diff_stdin_formatted() -> Result<()> {
let unformatted = fs::read(fixtures.join("formatted.py")).unwrap(); let unformatted = fs::read(fixtures.join("formatted.py")).unwrap();
assert_cmd_snapshot!( assert_cmd_snapshot!(
test.format_command().args(["--isolated", "--diff", "-"]).pass_stdin(unformatted), test.format_command().args(["--isolated", "--diff", "-"]).pass_stdin(unformatted),
@" @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -1873,7 +1873,7 @@ include = ["*.ipy"]
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--config", "ruff.toml"]) .args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"]) .args(["--extension", "ipy:ipynb"])
.arg("."), @" .arg("."), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -1938,7 +1938,7 @@ include = ["*.ipy"]
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--config", "ruff.toml"]) .args(["--config", "ruff.toml"])
.args(["--extension", "ipy:ipynb"]) .args(["--extension", "ipy:ipynb"])
.arg("."), @" .arg("."), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -2021,7 +2021,7 @@ def file2(arg1, arg2,):
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--range=1:8-1:15"]) .args(["--isolated", "--range=1:8-1:15"])
.arg("file1.py") .arg("file1.py")
.arg("file2.py"), @" .arg("file2.py"), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -2068,7 +2068,7 @@ fn range_start_larger_than_end() -> Result<()> {
def foo(arg1, arg2,): def foo(arg1, arg2,):
print("Shouldn't format this" ) print("Shouldn't format this" )
"#), @" "#), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -2168,7 +2168,7 @@ fn range_missing_line() -> Result<()> {
def foo(arg1, arg2,): def foo(arg1, arg2,):
print("Should format this" ) print("Should format this" )
"#), @" "#), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -2192,7 +2192,7 @@ fn zero_line_number() -> Result<()> {
def foo(arg1, arg2,): def foo(arg1, arg2,):
print("Should format this" ) print("Should format this" )
"#), @" "#), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -2217,7 +2217,7 @@ fn column_and_line_zero() -> Result<()> {
def foo(arg1, arg2,): def foo(arg1, arg2,):
print("Should format this" ) print("Should format this" )
"#), @" "#), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -2274,7 +2274,7 @@ fn range_formatting_notebook() -> Result<()> {
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 5 "nbformat_minor": 5
} }
"#), @" "#), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -2355,7 +2355,7 @@ fn cookiecutter_globbing() -> Result<()> {
])?; ])?;
assert_cmd_snapshot!(test.format_command() assert_cmd_snapshot!(test.format_command()
.args(["--isolated", "--diff", "."]), @" .args(["--isolated", "--diff", "."]), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -2374,7 +2374,7 @@ fn stable_output_format_warning() -> Result<()> {
test.format_command() test.format_command()
.args(["--output-format=full", "-"]) .args(["--output-format=full", "-"])
.pass_stdin(""), .pass_stdin(""),
@" @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -14,6 +14,6 @@ info:
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- 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 ----- ----- stderr -----

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ fn check_project_include_defaults() {
filters => TEST_FILTERS.to_vec() filters => TEST_FILTERS.to_vec()
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) 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 success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -53,7 +53,7 @@ fn check_project_respects_direct_paths() {
filters => TEST_FILTERS.to_vec() filters => TEST_FILTERS.to_vec()
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) 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 success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -72,7 +72,7 @@ fn check_project_respects_subdirectory_includes() {
filters => TEST_FILTERS.to_vec() filters => TEST_FILTERS.to_vec()
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) 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 success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -91,7 +91,7 @@ fn check_project_from_project_subdirectory_respects_includes() {
filters => TEST_FILTERS.to_vec() filters => TEST_FILTERS.to_vec()
}, { }, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) 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 success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----

View File

@@ -50,56 +50,6 @@ ignore = [
Ok(()) 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 { fn tempdir_filter(project_dir: &Path) -> String {
format!(r#"{}\\?/?"#, regex::escape(project_dir.to_str().unwrap())) format!(r#"{}\\?/?"#, regex::escape(project_dir.to_str().unwrap()))
} }

View File

@@ -3,12 +3,11 @@ source: crates/ruff/tests/integration_test.rs
info: info:
program: ruff program: ruff
args: args:
- check - "-"
- "--isolated"
- "--no-cache"
- "--output-format" - "--output-format"
- json - json
- "--no-cache"
- "--isolated"
- "-"
- "--stdin-filename" - "--stdin-filename"
- F401.py - F401.py
stdin: "import os\n" stdin: "import os\n"
@@ -52,3 +51,4 @@ exit_code: 1
} }
] ]
----- stderr ----- ----- 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.author = none
linter.flake8_copyright.min_file_size = 0 linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0 linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [ linter.flake8_gettext.functions_names = [
_, _,
gettext, gettext,
ngettext, ngettext,
@@ -374,7 +374,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5 linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings # Formatter Settings
formatter.exclude = [] formatter.exclude = []
@@ -397,6 +396,5 @@ analyze.target_version = 3.7
analyze.string_imports = disabled analyze.string_imports = disabled
analyze.extension = ExtensionMapping({}) analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {} analyze.include_dependencies = {}
analyze.type_checking_imports = true
----- stderr ----- ----- 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() { fn version_basics() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, { insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!( assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @" Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -42,7 +42,7 @@ fn config_option_allowed_but_ignored() -> Result<()> {
.arg("version") .arg("version")
.arg("--config") .arg("--config")
.arg(&ruff_dot_toml) .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 success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@@ -60,7 +60,7 @@ fn config_option_ignored_but_validated() {
assert_cmd_snapshot!( assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)) Command::new(get_cargo_bin(BIN_NAME))
.arg("version") .arg("version")
.args(["--config", "foo = bar"]), @" .args(["--config", "foo = bar"]), @r"
success: false success: false
exit_code: 2 exit_code: 2
----- stdout ----- ----- stdout -----
@@ -91,7 +91,7 @@ fn config_option_ignored_but_validated() {
fn isolated_option_allowed() { fn isolated_option_allowed() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, { insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!( 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 success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----

View File

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

View File

@@ -31,7 +31,7 @@
//! styling. //! styling.
//! //!
//! The above snippet has been built out of the following structure: //! 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::cmp::{Reverse, max, min};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
@@ -189,7 +189,6 @@ impl DisplaySet<'_> {
} }
Ok(()) Ok(())
} }
fn format_annotation( fn format_annotation(
&self, &self,
line_offset: usize, line_offset: usize,
@@ -200,13 +199,11 @@ impl DisplaySet<'_> {
) -> fmt::Result { ) -> fmt::Result {
let hide_severity = annotation.annotation_type.is_none(); let hide_severity = annotation.annotation_type.is_none();
let color = get_annotation_style(&annotation.annotation_type, stylesheet); let color = get_annotation_style(&annotation.annotation_type, stylesheet);
let formatted_len = if let Some(id) = &annotation.id { let formatted_len = if let Some(id) = &annotation.id {
let id_len = id.id.len();
if hide_severity { if hide_severity {
id_len id.len()
} else { } else {
2 + id_len + annotation_type_len(&annotation.annotation_type) 2 + id.len() + annotation_type_len(&annotation.annotation_type)
} }
} else { } else {
annotation_type_len(&annotation.annotation_type) annotation_type_len(&annotation.annotation_type)
@@ -259,20 +256,9 @@ impl DisplaySet<'_> {
let annotation_type = annotation_type_str(&annotation.annotation_type); let annotation_type = annotation_type_str(&annotation.annotation_type);
if let Some(id) = annotation.id { if let Some(id) = annotation.id {
if hide_severity { if hide_severity {
buffer.append( buffer.append(line_offset, &format!("{id} "), *stylesheet.error());
line_offset,
&format!("{id} ", id = fmt_with_hyperlink(id.id, id.url, stylesheet)),
*stylesheet.error(),
);
} else { } else {
buffer.append( buffer.append(line_offset, &format!("{annotation_type}[{id}]"), *color);
line_offset,
&format!(
"{annotation_type}[{id}]",
id = fmt_with_hyperlink(id.id, id.url, stylesheet)
),
*color,
);
} }
} else { } else {
buffer.append(line_offset, annotation_type, *color); buffer.append(line_offset, annotation_type, *color);
@@ -721,7 +707,7 @@ impl DisplaySet<'_> {
let style = let style =
get_annotation_style(&annotation.annotation_type, stylesheet); get_annotation_style(&annotation.annotation_type, stylesheet);
let mut formatted_len = if let Some(id) = &annotation.annotation.id { 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) + annotation_type_len(&annotation.annotation.annotation_type)
} else { } else {
annotation_type_len(&annotation.annotation.annotation_type) annotation_type_len(&annotation.annotation.annotation_type)
@@ -738,10 +724,7 @@ impl DisplaySet<'_> {
} else if formatted_len != 0 { } else if formatted_len != 0 {
formatted_len += 2; formatted_len += 2;
let id = match &annotation.annotation.id { let id = match &annotation.annotation.id {
Some(id) => format!( Some(id) => format!("[{id}]"),
"[{id}]",
id = fmt_with_hyperlink(&id.id, id.url, stylesheet)
),
None => String::new(), None => String::new(),
}; };
buffer.puts( buffer.puts(
@@ -844,7 +827,7 @@ impl DisplaySet<'_> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub(crate) struct Annotation<'a> { pub(crate) struct Annotation<'a> {
pub(crate) annotation_type: DisplayAnnotationType, 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) label: Vec<DisplayTextFragment<'a>>,
pub(crate) is_fixable: bool, pub(crate) is_fixable: bool,
} }
@@ -1157,7 +1140,7 @@ fn format_message<'m>(
fn format_title<'a>( fn format_title<'a>(
level: crate::Level, level: crate::Level,
id: Option<Id<'a>>, id: Option<&'a str>,
label: &'a str, label: &'a str,
is_fixable: bool, is_fixable: bool,
) -> DisplayLine<'a> { ) -> DisplayLine<'a> {
@@ -1175,7 +1158,7 @@ fn format_title<'a>(
fn format_footer<'a>( fn format_footer<'a>(
level: crate::Level, level: crate::Level,
id: Option<Id<'a>>, id: Option<&'a str>,
label: &'a str, label: &'a str,
) -> Vec<DisplayLine<'a>> { ) -> Vec<DisplayLine<'a>> {
let mut result = vec![]; let mut result = vec![];
@@ -1723,7 +1706,6 @@ fn format_body<'m>(
annotation: Annotation { annotation: Annotation {
annotation_type, annotation_type,
id: None, id: None,
label: format_label(annotation.label, None), label: format_label(annotation.label, None),
is_fixable: false, is_fixable: false,
}, },
@@ -1905,40 +1887,3 @@ fn char_width(c: char) -> Option<usize> {
unicode_width::UnicodeWidthChar::width(c) 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), .effects(Effects::BOLD),
none: Style::new(), none: Style::new(),
hyperlink: true,
}, },
..Self::plain() ..Self::plain()
} }
@@ -155,11 +154,6 @@ impl Renderer {
self 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. /// Set the string used for when a long line is cut.
/// ///
/// The default is `...` (three `U+002E` characters). /// The default is `...` (three `U+002E` characters).

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,7 @@ use criterion::{
use ruff_benchmark::{ use ruff_benchmark::{
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN, LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
}; };
use ruff_python_ast::token::TokenKind; use ruff_python_parser::{Mode, TokenKind, lexer};
use ruff_python_parser::{Mode, lexer};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[global_allocator] #[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::source::source_text;
use ruff_db::system::{InMemorySystem, MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem}; use ruff_db::system::{InMemorySystem, MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem};
use ruff_python_ast::PythonVersion; 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::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::watch::{ChangeEvent, ChangedKind}; use ty_project::watch::{ChangeEvent, ChangedKind};
use ty_project::{CheckMode, Db, ProjectDatabase, ProjectMetadata}; use ty_project::{CheckMode, Db, ProjectDatabase, ProjectMetadata};
@@ -67,7 +67,6 @@ fn tomllib_path(file: &TestFile) -> SystemPathBuf {
SystemPathBuf::from("src").join(file.name()) SystemPathBuf::from("src").join(file.name())
} }
#[expect(clippy::needless_update)]
fn setup_tomllib_case() -> Case { fn setup_tomllib_case() -> Case {
let system = TestSystem::default(); let system = TestSystem::default();
let fs = system.memory_file_system().clone(); let fs = system.memory_file_system().clone();
@@ -86,10 +85,6 @@ fn setup_tomllib_case() -> Case {
python_version: Some(RangedValue::cli(PythonVersion::PY312)), python_version: Some(RangedValue::cli(PythonVersion::PY312)),
..EnvironmentOptions::default() ..EnvironmentOptions::default()
}), }),
analysis: Some(AnalysisOptions {
respect_type_ignore_comments: Some(false),
..AnalysisOptions::default()
}),
..Options::default() ..Options::default()
}); });
@@ -226,7 +221,7 @@ fn setup_micro_case(code: &str) -> Case {
let file_path = "src/test.py"; let file_path = "src/test.py";
fs.write_file_all( fs.write_file_all(
SystemPathBuf::from(file_path), SystemPathBuf::from(file_path),
&*ruff_python_trivia::textwrap::dedent(code), ruff_python_trivia::textwrap::dedent(code),
) )
.unwrap(); .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> { struct ProjectBenchmark<'a> {
project: InstalledProject<'a>, project: InstalledProject<'a>,
fs: MemoryFileSystem, fs: MemoryFileSystem,
@@ -726,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313, python_version: PythonVersion::PY313,
}, },
120, 110,
); );
bench_project(&benchmark, criterion); bench_project(&benchmark, criterion);
@@ -760,7 +701,7 @@ fn datetype(criterion: &mut Criterion) {
max_dep_date: "2025-07-04", max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313, python_version: PythonVersion::PY313,
}, },
4, 2,
); );
bench_project(&benchmark, criterion); bench_project(&benchmark, criterion);
@@ -776,7 +717,6 @@ criterion_group!(
benchmark_complex_constrained_attributes_2, benchmark_complex_constrained_attributes_2,
benchmark_complex_constrained_attributes_3, benchmark_complex_constrained_attributes_3,
benchmark_many_enum_members, benchmark_many_enum_members,
benchmark_many_enum_members_2,
); );
criterion_group!(project, anyio, attrs, hydra, datetype); criterion_group!(project, anyio, attrs, hydra, datetype);
criterion_main!(check_file, micro, project); criterion_main!(check_file, micro, project);

View File

@@ -71,22 +71,17 @@ impl Display for Benchmark<'_> {
} }
} }
#[track_caller] fn check_project(db: &ProjectDatabase, max_diagnostics: usize) {
#[expect(clippy::cast_precision_loss)]
fn check_project(db: &ProjectDatabase, project_name: &str, max_diagnostics: usize) {
let result = db.check(); let result = db.check();
let diagnostics = result.len(); let diagnostics = result.len();
assert!( assert!(
diagnostics > 1 && diagnostics <= max_diagnostics, diagnostics > 1 && diagnostics <= max_diagnostics,
"Expected between 1 and {max_diagnostics} diagnostics on project '{project_name}' but got {diagnostics}", "Expected between {} and {} diagnostics but got {}",
1,
max_diagnostics,
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( static ALTAIR: Benchmark = Benchmark::new(
@@ -109,7 +104,7 @@ static ALTAIR: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312, python_version: PythonVersion::PY312,
}, },
850, 1000,
); );
static COLOUR_SCIENCE: Benchmark = Benchmark::new( static COLOUR_SCIENCE: Benchmark = Benchmark::new(
@@ -128,7 +123,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310, python_version: PythonVersion::PY310,
}, },
350, 600,
); );
static FREQTRADE: Benchmark = Benchmark::new( static FREQTRADE: Benchmark = Benchmark::new(
@@ -151,7 +146,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312, python_version: PythonVersion::PY312,
}, },
600, 400,
); );
static PANDAS: Benchmark = Benchmark::new( static PANDAS: Benchmark = Benchmark::new(
@@ -171,7 +166,7 @@ static PANDAS: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312, python_version: PythonVersion::PY312,
}, },
3800, 3000,
); );
static PYDANTIC: Benchmark = Benchmark::new( static PYDANTIC: Benchmark = Benchmark::new(
@@ -189,7 +184,7 @@ static PYDANTIC: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39, python_version: PythonVersion::PY39,
}, },
3200, 1000,
); );
static SYMPY: Benchmark = Benchmark::new( static SYMPY: Benchmark = Benchmark::new(
@@ -202,7 +197,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312, python_version: PythonVersion::PY312,
}, },
13400, 13000,
); );
static TANJUN: Benchmark = Benchmark::new( static TANJUN: Benchmark = Benchmark::new(
@@ -215,7 +210,7 @@ static TANJUN: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17", max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312, python_version: PythonVersion::PY312,
}, },
110, 320,
); );
static STATIC_FRAME: Benchmark = Benchmark::new( static STATIC_FRAME: Benchmark = Benchmark::new(
@@ -231,7 +226,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09", max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311, python_version: PythonVersion::PY311,
}, },
1700, 800,
); );
#[track_caller] #[track_caller]
@@ -239,59 +234,34 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
bencher bencher
.with_inputs(|| benchmark.setup_iteration()) .with_inputs(|| benchmark.setup_iteration())
.bench_local_refs(|db| { .bench_local_refs(|db| {
check_project(db, benchmark.project.name, benchmark.max_diagnostics); check_project(db, benchmark.max_diagnostics);
}); });
} }
#[bench(sample_size = 2, sample_count = 3)] #[bench(args=[&ALTAIR, &FREQTRADE, &PYDANTIC, &TANJUN], sample_size=2, sample_count=3)]
fn altair(bencher: Bencher) { fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, &ALTAIR); run_single_threaded(bencher, benchmark);
} }
#[bench(sample_size = 2, sample_count = 3)] #[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
fn freqtrade(bencher: Bencher) { fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, &FREQTRADE); run_single_threaded(bencher, benchmark);
} }
#[bench(sample_size = 2, sample_count = 3)] #[bench(args=[&SYMPY], sample_size=1, sample_count=2)]
fn tanjun(bencher: Bencher) { fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, &TANJUN); run_single_threaded(bencher, benchmark);
} }
#[bench(sample_size = 2, sample_count = 3)] #[bench(args=[&PYDANTIC], sample_size=3, sample_count=8)]
fn pydantic(bencher: Bencher) { fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
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) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap(); let thread_pool = ThreadPoolBuilder::new().build().unwrap();
bencher bencher
.with_inputs(|| ALTAIR.setup_iteration()) .with_inputs(|| benchmark.setup_iteration())
.bench_local_values(|db| { .bench_local_values(|db| {
thread_pool.install(|| { thread_pool.install(|| {
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics); check_project(&db, benchmark.max_diagnostics);
db db
}) })
}); });
@@ -315,7 +285,7 @@ fn main() {
// branch when looking up the ingredient index. // branch when looking up the ingredient index.
{ {
let db = TANJUN.setup_iteration(); let db = TANJUN.setup_iteration();
check_project(&db, TANJUN.project.name, TANJUN.max_diagnostics); check_project(&db, TANJUN.max_diagnostics);
} }
divan::main(); divan::main();

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