Compare commits
1 Commits
charlie/fu
...
david/self
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a6bfca0ed |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -22,7 +22,6 @@ crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
|
||||
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
||||
|
||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
|
||||
|
||||
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
ty.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
|
||||
7
.github/actionlint.yaml
vendored
7
.github/actionlint.yaml
vendored
@@ -4,17 +4,10 @@
|
||||
self-hosted-runner:
|
||||
# Various runners we use that aren't recognized out-of-the-box by actionlint:
|
||||
labels:
|
||||
- depot-ubuntu-24.04-4
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-22.04-16
|
||||
- depot-ubuntu-22.04-32
|
||||
- depot-windows-2022-16
|
||||
- depot-ubuntu-22.04-arm-4
|
||||
- github-windows-2025-x86_64-8
|
||||
- github-windows-2025-x86_64-16
|
||||
- codspeed-macro
|
||||
|
||||
paths:
|
||||
".github/workflows/mypy_primer.yaml":
|
||||
ignore:
|
||||
- 'condition "false" is always evaluated to false. remove the if: section'
|
||||
|
||||
1
.github/mypy-primer-ty.toml
vendored
1
.github/mypy-primer-ty.toml
vendored
@@ -4,6 +4,5 @@
|
||||
# Enable off-by-default rules.
|
||||
[rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
possibly-missing-import = "warn"
|
||||
unused-ignore-comment = "warn"
|
||||
division-by-zero = "warn"
|
||||
|
||||
15
.github/renovate.json5
vendored
15
.github/renovate.json5
vendored
@@ -2,11 +2,12 @@
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
dependencyDashboard: true,
|
||||
suppressNotifications: ["prEditedNotification"],
|
||||
extends: ["github>astral-sh/renovate-config"],
|
||||
extends: ["config:recommended"],
|
||||
labels: ["internal"],
|
||||
schedule: ["before 4am on Monday"],
|
||||
semanticCommits: "disabled",
|
||||
separateMajorMinor: false,
|
||||
prHourlyLimit: 10,
|
||||
enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "pip_requirements", "npm"],
|
||||
cargo: {
|
||||
// See https://docs.renovatebot.com/configuration-options/#rangestrategy
|
||||
@@ -15,7 +16,7 @@
|
||||
pep621: {
|
||||
// The default for this package manager is to only search for `pyproject.toml` files
|
||||
// found at the repository root: https://docs.renovatebot.com/modules/manager/pep621/#file-matching
|
||||
managerFilePatterns: ["^(python|scripts)/.*pyproject\\.toml$"],
|
||||
fileMatch: ["^(python|scripts)/.*pyproject\\.toml$"],
|
||||
},
|
||||
pip_requirements: {
|
||||
// The default for this package manager is to run on all requirements.txt files:
|
||||
@@ -33,7 +34,7 @@
|
||||
npm: {
|
||||
// The default for this package manager is to only search for `package.json` files
|
||||
// found at the repository root: https://docs.renovatebot.com/modules/manager/npm/#file-matching
|
||||
managerFilePatterns: ["^playground/.*package\\.json$"],
|
||||
fileMatch: ["^playground/.*package\\.json$"],
|
||||
},
|
||||
"pre-commit": {
|
||||
enabled: true,
|
||||
@@ -75,6 +76,14 @@
|
||||
matchManagers: ["cargo"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
// `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"],
|
||||
|
||||
62
.github/workflows/build-binaries.yml
vendored
62
.github/workflows/build-binaries.yml
vendored
@@ -39,11 +39,11 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
@@ -68,11 +68,11 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
target: x86_64
|
||||
args: --release --locked --out dist
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-macos-x86_64
|
||||
path: dist
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-macos-x86_64
|
||||
path: |
|
||||
@@ -110,11 +110,11 @@ jobs:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: arm64
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-aarch64-apple-darwin
|
||||
path: dist
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-aarch64-apple-darwin
|
||||
path: |
|
||||
@@ -166,11 +166,11 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
arch: x64
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: ${{ matrix.platform.arch }}
|
||||
@@ -192,7 +192,7 @@ jobs:
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
@@ -219,11 +219,11 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- i686-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -260,7 +260,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-${{ matrix.target }}
|
||||
path: |
|
||||
@@ -296,11 +296,11 @@ jobs:
|
||||
arch: riscv64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -327,7 +327,7 @@ jobs:
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
@@ -361,11 +361,11 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- i686-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
@@ -389,7 +389,7 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -407,7 +407,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-${{ matrix.target }}
|
||||
path: |
|
||||
@@ -427,11 +427,11 @@ jobs:
|
||||
arch: armv7
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: "Prep README.md"
|
||||
@@ -456,7 +456,7 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
@@ -474,7 +474,7 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: artifacts-${{ matrix.platform.target }}
|
||||
path: |
|
||||
|
||||
30
.github/workflows/build-docker.yml
vendored
30
.github/workflows/build-docker.yml
vendored
@@ -20,12 +20,6 @@ on:
|
||||
env:
|
||||
RUFF_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ruff
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# TODO(zanieb): Ideally, this would be `read` on dry-run but that will require
|
||||
# significant changes to the workflow.
|
||||
packages: write # zizmor: ignore[excessive-permissions]
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }}
|
||||
@@ -39,12 +33,12 @@ jobs:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
@@ -69,7 +63,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ${{ env.RUFF_BASE_IMG }}
|
||||
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
|
||||
@@ -102,7 +96,7 @@ jobs:
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_TUPLE }}
|
||||
path: /tmp/digests/*
|
||||
@@ -119,17 +113,17 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ${{ env.RUFF_BASE_IMG }}
|
||||
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
|
||||
@@ -173,7 +167,7 @@ jobs:
|
||||
- debian:bookworm-slim,bookworm-slim,debian-slim
|
||||
- buildpack-deps:bookworm,bookworm,debian
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
@@ -225,7 +219,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
# ghcr.io prefers index level annotations
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
||||
@@ -262,17 +256,17 @@ jobs:
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
|
||||
with:
|
||||
|
||||
590
.github/workflows/ci.yaml
vendored
590
.github/workflows/ci.yaml
vendored
File diff suppressed because it is too large
Load Diff
8
.github/workflows/daily_fuzz.yaml
vendored
8
.github/workflows/daily_fuzz.yaml
vendored
@@ -31,15 +31,15 @@ jobs:
|
||||
# Don't run the cron job on forks:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
name: Create an issue if the daily fuzz surfaced any bugs
|
||||
runs-on: ubuntu-latest
|
||||
needs: fuzz
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
84
.github/workflows/mypy_primer.yaml
vendored
84
.github/workflows/mypy_primer.yaml
vendored
@@ -6,11 +6,6 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "!crates/ty_ide/**"
|
||||
- "!crates/ty_server/**"
|
||||
- "!crates/ty_test/**"
|
||||
- "!crates/ty_completion_eval/**"
|
||||
- "!crates/ty_wasm/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
@@ -41,18 +36,17 @@ jobs:
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
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:
|
||||
shared-key: "mypy-primer"
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -61,38 +55,41 @@ jobs:
|
||||
- name: Run mypy_primer
|
||||
env:
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
CLICOLOR_FORCE: "1"
|
||||
DIFF_FILE: mypy_primer.diff
|
||||
run: |
|
||||
cd ruff
|
||||
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
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mypy_primer_diff
|
||||
path: mypy_primer.diff
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
memory_usage:
|
||||
name: Run memory statistics
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
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:
|
||||
workspaces: "ruff"
|
||||
shared-key: "mypy-primer"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
@@ -108,58 +105,7 @@ jobs:
|
||||
scripts/mypy_primer.sh
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
path: mypy_primer_memory.diff
|
||||
|
||||
# Runs mypy twice against the same ty version to catch any non-deterministic behavior (ideally).
|
||||
# The job is disabled for now because there are some non-deterministic diagnostics.
|
||||
mypy_primer_same_revision:
|
||||
name: Run mypy_primer on same revision
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
# TODO: Enable once we fixed the non-deterministic diagnostics
|
||||
if: false
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- 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
|
||||
|
||||
122
.github/workflows/mypy_primer_comment.yaml
vendored
Normal file
122
.github/workflows/mypy_primer_comment.yaml
vendored
Normal 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
88
.github/workflows/pr-comment.yaml
vendored
Normal 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
|
||||
29
.github/workflows/publish-docs.yml
vendored
29
.github/workflows/publish-docs.yml
vendored
@@ -17,19 +17,18 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
mkdocs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: true
|
||||
|
||||
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
@@ -60,12 +59,23 @@ jobs:
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: pip install -r docs/requirements-insiders.txt
|
||||
|
||||
- name: "Install dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: pip install -r docs/requirements.txt
|
||||
|
||||
- name: "Copy README File"
|
||||
@@ -73,8 +83,13 @@ jobs:
|
||||
python scripts/transform_readme.py --target mkdocs
|
||||
python scripts/generate_mkdocs.py
|
||||
|
||||
- name: "Build Insiders docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
|
||||
- name: "Build docs"
|
||||
run: mkdocs build --strict -f mkdocs.yml
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
- name: "Clone docs repo"
|
||||
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||
|
||||
11
.github/workflows/publish-playground.yml
vendored
11
.github/workflows/publish-playground.yml
vendored
@@ -26,18 +26,19 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
node-version: 22
|
||||
cache: "npm" # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
- name: "Install Node dependencies"
|
||||
run: npm ci --ignore-scripts
|
||||
run: npm ci
|
||||
working-directory: playground
|
||||
- name: "Run TypeScript checks"
|
||||
run: npm run check
|
||||
|
||||
4
.github/workflows/publish-pypi.yml
vendored
4
.github/workflows/publish-pypi.yml
vendored
@@ -22,8 +22,8 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
|
||||
10
.github/workflows/publish-ty-playground.yml
vendored
10
.github/workflows/publish-ty-playground.yml
vendored
@@ -30,18 +30,18 @@ jobs:
|
||||
env:
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
node-version: 22
|
||||
cache: "npm" # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
- name: "Install Node dependencies"
|
||||
run: npm ci --ignore-scripts
|
||||
run: npm ci
|
||||
working-directory: playground
|
||||
- name: "Run TypeScript checks"
|
||||
run: npm run check
|
||||
|
||||
6
.github/workflows/publish-wasm.yml
vendored
6
.github/workflows/publish-wasm.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
target: [web, bundler, nodejs]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -45,9 +45,9 @@ jobs:
|
||||
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@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 22
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: "Publish (dry-run)"
|
||||
if: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
|
||||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -68,9 +68,9 @@ jobs:
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
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
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/dist
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: artifacts-plan-dist-manifest
|
||||
path: plan-dist-manifest.json
|
||||
@@ -123,19 +123,19 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: artifacts-build-global
|
||||
path: |
|
||||
@@ -166,27 +166,27 @@ jobs:
|
||||
- custom-build-binaries
|
||||
- custom-build-docker
|
||||
- build-global-artifacts
|
||||
# Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
|
||||
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
|
||||
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
|
||||
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:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: "depot-ubuntu-latest-4"
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
# Overwrite the previous copy
|
||||
name: artifacts-dist-manifest
|
||||
@@ -250,13 +250,13 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
- name: "Download GitHub Artifacts"
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: artifacts
|
||||
|
||||
50
.github/workflows/sync_typeshed.yaml
vendored
50
.github/workflows/sync_typeshed.yaml
vendored
@@ -16,7 +16,8 @@ name: Sync typeshed
|
||||
# 3. Once the Windows worker is done, a MacOS worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||
# c. Formats the code again
|
||||
# c. Attempts to update any snapshots that might have changed
|
||||
# (this sub-step is allowed to fail)
|
||||
# d. Commits the changes and pushes them to the same upstream branch
|
||||
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||
@@ -61,12 +62,12 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
path: ruff
|
||||
persist-credentials: true
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout typeshed
|
||||
with:
|
||||
repository: python/typeshed
|
||||
@@ -76,7 +77,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -125,12 +126,12 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -164,12 +165,12 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -197,6 +198,37 @@ jobs:
|
||||
run: |
|
||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||
git commit -am "Remove pyproject.toml file"
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Install Rust toolchain"
|
||||
if: ${{ success() }}
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
if: ${{ success() }}
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@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
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
@@ -208,7 +240,7 @@ jobs:
|
||||
name: Create an issue if the typeshed sync failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
58
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
58
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -4,13 +4,7 @@ permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
# The default for `pull_request` is to trigger on `synchronize`, `opened` and `reopened`.
|
||||
# We also add `labeled` here so that the workflow triggers when a label is initially added.
|
||||
types:
|
||||
- labeled
|
||||
- synchronize
|
||||
- opened
|
||||
- reopened
|
||||
types: [labeled]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
@@ -23,26 +17,27 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
if: contains( github.event.pull_request.labels.*.name, 'ecosystem-analyzer')
|
||||
if: contains(github.event.label.name, 'ecosystem-analyzer')
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
with:
|
||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
@@ -72,7 +67,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
@@ -117,30 +112,45 @@ jobs:
|
||||
|
||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: "Upload full report"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: full-report
|
||||
path: dist/
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
- name: "Append deployment URL"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
env:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: Upload comment
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: comment.md
|
||||
path: comment.md
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- name: Upload diagnostics diff
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: diff.html
|
||||
path: dist/diff.html
|
||||
|
||||
- name: Upload timing diff
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: timing.html
|
||||
path: dist/timing.html
|
||||
|
||||
85
.github/workflows/ty-ecosystem-analyzer_comment.yaml
vendored
Normal file
85
.github/workflows/ty-ecosystem-analyzer_comment.yaml
vendored
Normal 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
|
||||
30
.github/workflows/ty-ecosystem-report.yaml
vendored
30
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -1,7 +1,3 @@
|
||||
# This workflow is a cron job that generates a report describing
|
||||
# all diagnostics ty emits across the whole ecosystem. The report
|
||||
# is uploaded to https://ty-ecosystem-ext.pages.dev/ on a weekly basis.
|
||||
|
||||
name: ty ecosystem-report
|
||||
|
||||
permissions: {}
|
||||
@@ -18,6 +14,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-report:
|
||||
@@ -25,21 +22,21 @@ jobs:
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||
with:
|
||||
enable-cache: true
|
||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
lookup-only: false
|
||||
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
@@ -55,7 +52,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@908758da02a73ef3f3308e1dbb2248510029bbe4"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
@@ -73,10 +70,11 @@ jobs:
|
||||
ecosystem-diagnostics.json \
|
||||
--output dist/index.html
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: "Upload ecosystem report"
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
name: full-report
|
||||
path: dist/
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}
|
||||
|
||||
31
.github/workflows/typing_conformance.yaml
vendored
31
.github/workflows/typing_conformance.yaml
vendored
@@ -6,11 +6,6 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "!crates/ty_ide/**"
|
||||
- "!crates/ty_server/**"
|
||||
- "!crates/ty_test/**"
|
||||
- "!crates/ty_completion_eval/**"
|
||||
- "!crates/ty_wasm/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
@@ -29,7 +24,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
CONFORMANCE_SUITE_COMMIT: 9f6d8ced7cd1c8d92687a4e9c96d7716452e471e
|
||||
CONFORMANCE_SUITE_COMMIT: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
|
||||
|
||||
jobs:
|
||||
typing_conformance:
|
||||
@@ -37,20 +32,20 @@ jobs:
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
repository: python/typing
|
||||
ref: ${{ env.CONFORMANCE_SUITE_COMMIT }}
|
||||
path: typing
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
@@ -59,6 +54,9 @@ jobs:
|
||||
|
||||
- name: Compute diagnostic diff
|
||||
shell: bash
|
||||
env:
|
||||
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
|
||||
TY_MAX_PARALLELISM: 1
|
||||
run: |
|
||||
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
|
||||
|
||||
@@ -96,20 +94,23 @@ jobs:
|
||||
touch typing_conformance_diagnostics.diff
|
||||
fi
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
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
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: typing_conformance_diagnostics_diff
|
||||
path: typing_conformance_diagnostics.diff
|
||||
|
||||
# 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 pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- name: Upload conformance suite commit
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: conformance-suite-commit
|
||||
path: conformance-suite-commit
|
||||
|
||||
112
.github/workflows/typing_conformance_comment.yaml
vendored
Normal file
112
.github/workflows/typing_conformance_comment.yaml
vendored
Normal 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
26
.github/zizmor.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
||||
# https://woodruffw.github.io/zizmor/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
|
||||
@@ -22,7 +22,7 @@ exclude: |
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
|
||||
@@ -46,7 +46,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.47.0
|
||||
rev: v0.45.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: |
|
||||
@@ -56,7 +56,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: 1.20.0
|
||||
rev: 1.19.1
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
language: python # means renovate will also update `additional_dependencies`
|
||||
@@ -67,10 +67,10 @@ repos:
|
||||
.*?invalid(_.+)*_syntax\.md
|
||||
)$
|
||||
additional_dependencies:
|
||||
- black==25.12.0
|
||||
- black==25.1.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.40.0
|
||||
rev: v1.34.0
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -84,7 +84,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.10
|
||||
rev: v0.12.7
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff-check
|
||||
@@ -94,7 +94,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.7.4
|
||||
rev: v3.6.2
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
@@ -102,19 +102,19 @@ repos:
|
||||
# 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
|
||||
rev: v1.15.2
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.36.0
|
||||
rev: 0.33.2
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
|
||||
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.7.9
|
||||
rev: v1.7.7
|
||||
hooks:
|
||||
- id: actionlint
|
||||
stages:
|
||||
@@ -129,9 +129,12 @@ repos:
|
||||
# actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions
|
||||
# and checks these with shellcheck. This is arguably its most useful feature,
|
||||
# but the integration only works if shellcheck is installed
|
||||
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1"
|
||||
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.11.0.1
|
||||
rev: v0.10.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
ci:
|
||||
skip: [cargo-fmt, dev-generate-all]
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -5,6 +5,5 @@
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
"search.exclude": {
|
||||
"**/*.snap": true
|
||||
},
|
||||
"ty.diagnosticMode": "openFilesOnly"
|
||||
}
|
||||
}
|
||||
|
||||
399
CHANGELOG.md
399
CHANGELOG.md
@@ -1,404 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
Released on 2025-10-23.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-gettext`\] Resolve qualified names and built-in bindings (`INT001`, `INT002`, `INT003`) ([#19045](https://github.com/astral-sh/ruff/pull/19045))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid reusing nested, interpolated quotes before Python 3.12 ([#20930](https://github.com/astral-sh/ruff/pull/20930))
|
||||
- Catch syntax errors in nested interpolations before Python 3.12 ([#20949](https://github.com/astral-sh/ruff/pull/20949))
|
||||
- \[`fastapi`\] Handle ellipsis defaults in `FAST002` autofix ([#20810](https://github.com/astral-sh/ruff/pull/20810))
|
||||
- \[`flake8-simplify`\] Skip `SIM911` when unknown arguments are present ([#20697](https://github.com/astral-sh/ruff/pull/20697))
|
||||
- \[`pyupgrade`\] Always parenthesize assignment expressions in fix for `f-string` (`UP032`) ([#21003](https://github.com/astral-sh/ruff/pull/21003))
|
||||
- \[`pyupgrade`\] Fix `UP032` conversion for decimal ints with underscores ([#21022](https://github.com/astral-sh/ruff/pull/21022))
|
||||
- \[`fastapi`\] Skip autofix for keyword and `__debug__` path params (`FAST003`) ([#20960](https://github.com/astral-sh/ruff/pull/20960))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-bugbear`\] Skip `B905` and `B912` for fewer than two iterables and no starred arguments ([#20998](https://github.com/astral-sh/ruff/pull/20998))
|
||||
- \[`ruff`\] Use `DiagnosticTag` for more `pyflakes` and `pandas` rules ([#20801](https://github.com/astral-sh/ruff/pull/20801))
|
||||
|
||||
### CLI
|
||||
|
||||
- Improve JSON output from `ruff rule` ([#20168](https://github.com/astral-sh/ruff/pull/20168))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add source to testimonial ([#20971](https://github.com/astral-sh/ruff/pull/20971))
|
||||
- Document when a rule was added ([#21035](https://github.com/astral-sh/ruff/pull/21035))
|
||||
|
||||
### Other changes
|
||||
|
||||
- [syntax-errors] Name is parameter and global ([#20426](https://github.com/astral-sh/ruff/pull/20426))
|
||||
- [syntax-errors] Alternative `match` patterns bind different names ([#20682](https://github.com/astral-sh/ruff/pull/20682))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@hengky-kurniawan-1](https://github.com/hengky-kurniawan-1)
|
||||
- [@ShalokShalom](https://github.com/ShalokShalom)
|
||||
- [@robsdedude](https://github.com/robsdedude)
|
||||
- [@LoicRiegel](https://github.com/LoicRiegel)
|
||||
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@11happy](https://github.com/11happy)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
|
||||
## 0.14.1
|
||||
|
||||
Released on 2025-10-16.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
### 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
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The documentation uses Material for MkDocs Insiders, which is closed-source software.
|
||||
> This means only members of the Astral organization can preview the documentation exactly as it
|
||||
> will appear in production.
|
||||
> Outside contributors can still preview the documentation, but there will be some differences. Consult [the Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/insiders/benefits/#features) for which features are exclusively available in the insiders version.
|
||||
|
||||
To preview any changes to the documentation locally:
|
||||
|
||||
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
|
||||
@@ -344,7 +302,11 @@ To preview any changes to the documentation locally:
|
||||
1. Run the development server with:
|
||||
|
||||
```shell
|
||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
|
||||
# For contributors.
|
||||
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.public.yml
|
||||
|
||||
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
|
||||
uvx --with-requirements docs/requirements-insiders.txt -- mkdocs serve -f mkdocs.insiders.yml
|
||||
```
|
||||
|
||||
The documentation should then be available locally at
|
||||
|
||||
729
Cargo.lock
generated
729
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# Please update rustfmt.toml when bumping the Rust edition
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
rust-version = "1.88"
|
||||
homepage = "https://docs.astral.sh/ruff"
|
||||
documentation = "https://docs.astral.sh/ruff"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
@@ -45,7 +45,6 @@ ty = { path = "crates/ty" }
|
||||
ty_combine = { path = "crates/ty_combine" }
|
||||
ty_completion_eval = { path = "crates/ty_completion_eval" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_module_resolver = { path = "crates/ty_module_resolver" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
@@ -79,17 +78,17 @@ console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
compact_str = "0.9.0"
|
||||
criterion = { version = "0.8.0", default-features = false }
|
||||
criterion = { version = "0.7.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
datatest-stable = { version = "0.3.3" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
etcetera = { version = "0.11.0" }
|
||||
etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.7.3", features = [
|
||||
get-size2 = { version = "0.7.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -104,7 +103,7 @@ hashbrown = { version = "0.16.0", default-features = false, features = [
|
||||
"inline-more",
|
||||
] }
|
||||
heck = "0.5.0"
|
||||
ignore = { version = "0.4.24" }
|
||||
ignore = { version = "0.4.22" }
|
||||
imara-diff = { version = "0.1.5" }
|
||||
imperative = { version = "1.0.4" }
|
||||
indexmap = { version = "2.6.0" }
|
||||
@@ -125,12 +124,12 @@ lsp-server = { version = "0.7.6" }
|
||||
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
|
||||
"proposed",
|
||||
] }
|
||||
matchit = { version = "0.9.0" }
|
||||
matchit = { version = "0.8.1" }
|
||||
memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "8.0.0" }
|
||||
ordermap = { version = "1.0.0" }
|
||||
ordermap = { version = "0.5.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
@@ -138,8 +137,6 @@ pep440_rs = { version = "0.7.1" }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.13.4" }
|
||||
quickcheck = { version = "1.0.3", default-features = false}
|
||||
quickcheck_macros = { version = "1.0.0" }
|
||||
quick-junit = { version = "0.5.0" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.9.0" }
|
||||
@@ -149,7 +146,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "309c249088fdeef0129606fa34ec2eefc74736ff", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "d38145c29574758de7ffbe8a13cd4584c3b09161", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
@@ -176,7 +173,6 @@ snapbox = { version = "0.6.0", features = [
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.27.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.27.0" }
|
||||
supports-hyperlinks = { version = "3.1.0" }
|
||||
syn = { version = "2.0.55" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
@@ -275,12 +271,6 @@ large_stack_arrays = "allow"
|
||||
lto = "fat"
|
||||
codegen-units = 16
|
||||
|
||||
# Profile to build a minimally sized binary for ruff/ty
|
||||
[profile.minimal-size]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
||||
# Some crates don't change as much but benefit more from
|
||||
# more expensive optimization passes, so we selectively
|
||||
# decrease codegen-units in some cases.
|
||||
|
||||
14
README.md
14
README.md
@@ -57,11 +57,8 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
|
||||
...and [many more](#whos-using-ruff).
|
||||
|
||||
Ruff is backed by [Astral](https://astral.sh), the creators of
|
||||
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
|
||||
|
||||
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
|
||||
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
||||
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
## Testimonials
|
||||
|
||||
@@ -150,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.14.10/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.10/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.1/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.1/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -184,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.10
|
||||
rev: v0.14.1
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- 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)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Pylint](https://github.com/PyCQA/pylint)
|
||||
- [PyScripter](https://github.com/pyscripter/pyscripter)
|
||||
- [PyVista](https://github.com/pyvista/pyvista)
|
||||
- [Reflex](https://github.com/reflex-dev/reflex)
|
||||
- [River](https://github.com/online-ml/river)
|
||||
|
||||
@@ -4,7 +4,6 @@ extend-exclude = [
|
||||
"crates/ty_vendored/vendor/**/*",
|
||||
"**/resources/**/*",
|
||||
"**/snapshots/**/*",
|
||||
"crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs",
|
||||
# Completion tests tend to have a lot of incomplete
|
||||
# words naturally. It's annoying to have to make all
|
||||
# of them actually words. So just ignore typos here.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.10"
|
||||
version = "0.14.1"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -7,10 +7,8 @@ use std::sync::Arc;
|
||||
|
||||
use crate::commands::completions::config::{OptionString, OptionStringParser};
|
||||
use anyhow::bail;
|
||||
use clap::builder::Styles;
|
||||
use clap::builder::styling::{AnsiColor, Effects};
|
||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand, command};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use path_absolutize::path_dedot;
|
||||
@@ -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)]
|
||||
#[command(
|
||||
author,
|
||||
@@ -95,7 +86,6 @@ const STYLES: Styles = Styles::styled()
|
||||
after_help = "For help with a specific command, see: `ruff help <command>`."
|
||||
)]
|
||||
#[command(version)]
|
||||
#[command(styles = STYLES)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub(crate) command: Command,
|
||||
@@ -167,7 +157,6 @@ pub enum AnalyzeCommand {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::Parser)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct AnalyzeGraphCommand {
|
||||
/// List of files or directories to include.
|
||||
#[clap(help = "List of files or directories to include [default: .]")]
|
||||
@@ -194,12 +183,6 @@ pub struct AnalyzeGraphCommand {
|
||||
/// Path to a virtual environment to use for resolving additional dependencies
|
||||
#[arg(long)]
|
||||
python: Option<PathBuf>,
|
||||
/// Include imports that are only used for type checking (i.e., imports within `if TYPE_CHECKING:` blocks).
|
||||
/// Use `--no-type-checking-imports` to exclude imports that are only used for type checking.
|
||||
#[arg(long, overrides_with("no_type_checking_imports"))]
|
||||
type_checking_imports: bool,
|
||||
#[arg(long, overrides_with("type_checking_imports"), hide = true)]
|
||||
no_type_checking_imports: bool,
|
||||
}
|
||||
|
||||
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
||||
@@ -422,13 +405,8 @@ pub struct CheckCommand {
|
||||
)]
|
||||
pub statistics: bool,
|
||||
/// Enable automatic additions of `noqa` directives to failing lines.
|
||||
/// Optionally provide a reason to append after the codes.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "REASON",
|
||||
default_missing_value = "",
|
||||
num_args = 0..=1,
|
||||
require_equals = true,
|
||||
// conflicts_with = "add_noqa",
|
||||
conflicts_with = "show_files",
|
||||
conflicts_with = "show_settings",
|
||||
@@ -440,7 +418,7 @@ pub struct CheckCommand {
|
||||
conflicts_with = "fix",
|
||||
conflicts_with = "diff",
|
||||
)]
|
||||
pub add_noqa: Option<String>,
|
||||
pub add_noqa: bool,
|
||||
/// See the files Ruff will be run against with the current settings.
|
||||
#[arg(
|
||||
long,
|
||||
@@ -846,10 +824,6 @@ impl AnalyzeGraphCommand {
|
||||
string_imports_min_dots: self.min_dots,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
target_version: self.target_version.map(ast::PythonVersion::from),
|
||||
type_checking_imports: resolve_bool_arg(
|
||||
self.type_checking_imports,
|
||||
self.no_type_checking_imports,
|
||||
),
|
||||
..ExplicitConfigOverrides::default()
|
||||
};
|
||||
|
||||
@@ -1073,7 +1047,7 @@ Possible choices:
|
||||
/// etc.).
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct CheckArguments {
|
||||
pub add_noqa: Option<String>,
|
||||
pub add_noqa: bool,
|
||||
pub diff: bool,
|
||||
pub exit_non_zero_on_fix: bool,
|
||||
pub exit_zero: bool,
|
||||
@@ -1346,7 +1320,6 @@ struct ExplicitConfigOverrides {
|
||||
extension: Option<Vec<ExtensionPair>>,
|
||||
detect_string_imports: Option<bool>,
|
||||
string_imports_min_dots: Option<usize>,
|
||||
type_checking_imports: Option<bool>,
|
||||
}
|
||||
|
||||
impl ConfigurationTransformer for ExplicitConfigOverrides {
|
||||
@@ -1437,9 +1410,6 @@ impl ConfigurationTransformer for ExplicitConfigOverrides {
|
||||
if let Some(string_imports_min_dots) = &self.string_imports_min_dots {
|
||||
config.analyze.string_imports_min_dots = Some(*string_imports_min_dots);
|
||||
}
|
||||
if let Some(type_checking_imports) = &self.type_checking_imports {
|
||||
config.analyze.type_checking_imports = Some(*type_checking_imports);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ pub(crate) fn add_noqa(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
config_arguments: &ConfigArguments,
|
||||
reason: Option<&str>,
|
||||
) -> Result<usize> {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
@@ -77,14 +76,7 @@ pub(crate) fn add_noqa(
|
||||
return None;
|
||||
}
|
||||
};
|
||||
match add_noqa_to_path(
|
||||
path,
|
||||
package,
|
||||
&source_kind,
|
||||
source_type,
|
||||
&settings.linter,
|
||||
reason,
|
||||
) {
|
||||
match add_noqa_to_path(path, package, &source_kind, source_type, &settings.linter) {
|
||||
Ok(count) => Some(count),
|
||||
Err(e) => {
|
||||
error!("Failed to add noqa to {}: {e}", path.display());
|
||||
|
||||
@@ -7,7 +7,6 @@ use path_absolutize::CWD;
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::{warn_user, warn_user_once};
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
|
||||
@@ -105,7 +104,6 @@ pub(crate) fn analyze_graph(
|
||||
let settings = resolver.resolve(path);
|
||||
let string_imports = settings.analyze.string_imports;
|
||||
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();
|
||||
let type_checking_imports = settings.analyze.type_checking_imports;
|
||||
|
||||
// Skip excluded files.
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
@@ -129,6 +127,10 @@ pub(crate) fn analyze_graph(
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
if matches!(source_type, PySourceType::Ipynb) {
|
||||
debug!("Ignoring Jupyter notebook: {}", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert to system paths.
|
||||
let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else {
|
||||
@@ -145,35 +147,13 @@ pub(crate) fn analyze_graph(
|
||||
let root = root.clone();
|
||||
let result = inner_result.clone();
|
||||
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.
|
||||
let mut imports = ModuleImports::detect(
|
||||
&db,
|
||||
source_code,
|
||||
source_type,
|
||||
&path,
|
||||
package.as_deref(),
|
||||
string_imports,
|
||||
type_checking_imports,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!("Failed to generate import map for {path}: {err}");
|
||||
ModuleImports::default()
|
||||
});
|
||||
let mut imports =
|
||||
ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!("Failed to generate import map for {path}: {err}");
|
||||
ModuleImports::default()
|
||||
});
|
||||
|
||||
debug!("Discovered {} imports for {}", imports.len(), path);
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ pub(crate) fn format_source(
|
||||
let line_index = LineIndex::from_source_text(unformatted);
|
||||
let byte_range = range.to_text_range(unformatted, &line_index);
|
||||
format_range(unformatted, byte_range, options).map(|formatted_range| {
|
||||
let mut formatted = unformatted.clone();
|
||||
let mut formatted = unformatted.to_string();
|
||||
formatted.replace_range(
|
||||
std::ops::Range::<usize>::from(formatted_range.source_range()),
|
||||
formatted_range.as_code(),
|
||||
|
||||
@@ -16,8 +16,6 @@ struct LinterInfo {
|
||||
prefix: &'static str,
|
||||
name: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<&'static str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
categories: Option<Vec<LinterCategoryInfo>>,
|
||||
}
|
||||
|
||||
@@ -52,7 +50,6 @@ pub(crate) fn linter(format: HelpFormat) -> Result<()> {
|
||||
.map(|linter_info| LinterInfo {
|
||||
prefix: linter_info.common_prefix(),
|
||||
name: linter_info.name(),
|
||||
url: linter_info.url(),
|
||||
categories: linter_info.upstream_categories().map(|cats| {
|
||||
cats.iter()
|
||||
.map(|c| LinterCategoryInfo {
|
||||
|
||||
@@ -25,7 +25,6 @@ struct Explanation<'a> {
|
||||
explanation: Option<&'a str>,
|
||||
preview: bool,
|
||||
status: RuleGroup,
|
||||
source_location: SourceLocation,
|
||||
}
|
||||
|
||||
impl<'a> Explanation<'a> {
|
||||
@@ -44,10 +43,6 @@ impl<'a> Explanation<'a> {
|
||||
explanation: rule.explanation(),
|
||||
preview: rule.is_preview(),
|
||||
status: rule.group(),
|
||||
source_location: SourceLocation {
|
||||
file: rule.file(),
|
||||
line: rule.line(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,14 +127,3 @@ pub(crate) fn rules(format: HelpFormat) -> Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The location of the rule's implementation in the Ruff source tree, relative to the repository
|
||||
/// root.
|
||||
///
|
||||
/// For most rules this will point to the `#[derive(ViolationMetadata)]` line above the rule's
|
||||
/// struct.
|
||||
#[derive(Serialize)]
|
||||
struct SourceLocation {
|
||||
file: &'static str,
|
||||
line: u32,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
|
||||
use anyhow::Result;
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use notify::{RecursiveMode, Watcher, recommended_watcher};
|
||||
|
||||
use args::{GlobalConfigArgs, ServerCommand};
|
||||
@@ -319,20 +319,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
||||
warn_user!("Detected debug build without --no-cache.");
|
||||
}
|
||||
|
||||
if let Some(reason) = &cli.add_noqa {
|
||||
if cli.add_noqa {
|
||||
if !fix_mode.is_generate() {
|
||||
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 =
|
||||
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 {
|
||||
let s = if modifications == 1 { "" } else { "s" };
|
||||
#[expect(clippy::print_stderr)]
|
||||
|
||||
@@ -9,7 +9,9 @@ use itertools::{Itertools, iterate};
|
||||
use ruff_linter::linter::FixTable;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, SecondaryCode};
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
|
||||
};
|
||||
use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{EmitterContext, render_diagnostics};
|
||||
@@ -34,21 +36,9 @@ struct ExpandedStatistics<'a> {
|
||||
code: Option<&'a SecondaryCode>,
|
||||
name: &'static str,
|
||||
count: usize,
|
||||
#[serde(rename = "fixable")]
|
||||
all_fixable: bool,
|
||||
fixable_count: usize,
|
||||
fixable: bool,
|
||||
}
|
||||
|
||||
impl ExpandedStatistics<'_> {
|
||||
fn any_fixable(&self) -> bool {
|
||||
self.fixable_count > 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Accumulator type for grouping diagnostics by code.
|
||||
/// Format: (`code`, `representative_diagnostic`, `total_count`, `fixable_count`)
|
||||
type DiagnosticGroup<'a> = (Option<&'a SecondaryCode>, &'a Diagnostic, usize, usize);
|
||||
|
||||
pub(crate) struct Printer {
|
||||
format: OutputFormat,
|
||||
log_level: LogLevel,
|
||||
@@ -145,7 +135,7 @@ impl Printer {
|
||||
if fixables.applicable > 0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"{fix_prefix} {} fixable with the `--fix` option.",
|
||||
"{fix_prefix} {} fixable with the --fix option.",
|
||||
fixables.applicable
|
||||
)?;
|
||||
}
|
||||
@@ -268,41 +258,35 @@ impl Printer {
|
||||
diagnostics: &Diagnostics,
|
||||
writer: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
let required_applicability = self.unsafe_fixes.required_applicability();
|
||||
let statistics: Vec<ExpandedStatistics> = diagnostics
|
||||
.inner
|
||||
.iter()
|
||||
.sorted_by_key(|diagnostic| diagnostic.secondary_code())
|
||||
.fold(vec![], |mut acc: Vec<DiagnosticGroup>, diagnostic| {
|
||||
let is_fixable = diagnostic
|
||||
.fix()
|
||||
.is_some_and(|fix| fix.applies(required_applicability));
|
||||
let code = diagnostic.secondary_code();
|
||||
|
||||
if let Some((prev_code, _prev_message, count, fixable_count)) = acc.last_mut() {
|
||||
if *prev_code == code {
|
||||
*count += 1;
|
||||
if is_fixable {
|
||||
*fixable_count += 1;
|
||||
.map(|message| (message.secondary_code(), message))
|
||||
.sorted_by_key(|(code, message)| (*code, message.fixable()))
|
||||
.fold(
|
||||
vec![],
|
||||
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
|
||||
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
|
||||
if *prev_code == code {
|
||||
*count += 1;
|
||||
return acc;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
acc.push((code, diagnostic, 1, usize::from(is_fixable)));
|
||||
acc
|
||||
})
|
||||
.iter()
|
||||
.map(
|
||||
|&(code, message, count, fixable_count)| ExpandedStatistics {
|
||||
code,
|
||||
name: message.name(),
|
||||
count,
|
||||
// Backward compatibility: `fixable` is true only when all violations are fixable.
|
||||
// See: https://github.com/astral-sh/ruff/pull/21513
|
||||
all_fixable: fixable_count == count,
|
||||
fixable_count,
|
||||
acc.push(((code, message), 1));
|
||||
acc
|
||||
},
|
||||
)
|
||||
.iter()
|
||||
.map(|&((code, message), count)| ExpandedStatistics {
|
||||
code,
|
||||
name: message.name(),
|
||||
count,
|
||||
fixable: if let Some(fix) = message.fix() {
|
||||
fix.applies(self.unsafe_fixes.required_applicability())
|
||||
} else {
|
||||
false
|
||||
},
|
||||
})
|
||||
.sorted_by_key(|statistic| Reverse(statistic.count))
|
||||
.collect();
|
||||
|
||||
@@ -326,14 +310,13 @@ impl Printer {
|
||||
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
|
||||
.max()
|
||||
.unwrap();
|
||||
let any_fixable = statistics.iter().any(ExpandedStatistics::any_fixable);
|
||||
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
|
||||
|
||||
let all_fixable = format!("[{}] ", "*".cyan());
|
||||
let partially_fixable = format!("[{}] ", "-".cyan());
|
||||
let fixable = format!("[{}] ", "*".cyan());
|
||||
let unfixable = "[ ] ";
|
||||
|
||||
// By default, we mimic Flake8's `--statistics` format.
|
||||
for statistic in &statistics {
|
||||
for statistic in statistics {
|
||||
writeln!(
|
||||
writer,
|
||||
"{:>count_width$}\t{:<code_width$}\t{}{}",
|
||||
@@ -345,10 +328,8 @@ impl Printer {
|
||||
.red()
|
||||
.bold(),
|
||||
if any_fixable {
|
||||
if statistic.all_fixable {
|
||||
&all_fixable
|
||||
} else if statistic.any_fixable() {
|
||||
&partially_fixable
|
||||
if statistic.fixable {
|
||||
&fixable
|
||||
} else {
|
||||
unfixable
|
||||
}
|
||||
@@ -409,18 +390,21 @@ impl Printer {
|
||||
|
||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
let format = if preview {
|
||||
self.format
|
||||
DiagnosticFormat::Full
|
||||
} else {
|
||||
OutputFormat::Concise
|
||||
DiagnosticFormat::Concise
|
||||
};
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.preview(preview)
|
||||
.hide_severity(true)
|
||||
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability())
|
||||
.show_fix_diff(preview);
|
||||
render_diagnostics(writer, format, config, &context, &diagnostics.inner)?;
|
||||
.format(format)
|
||||
.with_fix_applicability(self.unsafe_fixes.required_applicability());
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
DisplayDiagnostics::new(&context, &config, &diagnostics.inner)
|
||||
)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
|
||||
|
||||
@@ -653,133 +653,3 @@ fn venv() -> Result<()> {
|
||||
|
||||
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]
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1440,78 +1440,6 @@ def function():
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
import os # noqa: F401
|
||||
|
||||
# ruff: disable[F401]
|
||||
import sys
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// without --ignore-noqa
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.args(["--preview"]),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// with --ignore-noqa --preview
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.args(["--ignore-noqa", "--preview"]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
noqa.py:2:8: F401 [*] `os` imported but unused
|
||||
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
@@ -1704,100 +1632,6 @@ def unused(x): # noqa: ANN001, ARG001, D103
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_existing_file_level_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
# ruff: noqa F401
|
||||
import os
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let test_code =
|
||||
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r"
|
||||
# ruff: noqa F401
|
||||
import os
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_existing_range_suppression() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
# ruff: disable[F401]
|
||||
import os
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let test_code =
|
||||
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r"
|
||||
# ruff: disable[F401]
|
||||
import os
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_multiline_comment() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
@@ -1926,64 +1760,6 @@ from foo import ( # noqa: F401
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_with_reason() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"test.py",
|
||||
r#"import os
|
||||
|
||||
def foo():
|
||||
x = 1
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.arg("--add-noqa=TODO: fix")
|
||||
.arg("--select=F401,F841")
|
||||
.arg("test.py"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Added 2 noqa directives.
|
||||
");
|
||||
|
||||
let content = fs::read_to_string(fixture.root().join("test.py"))?;
|
||||
insta::assert_snapshot!(content, @r"
|
||||
import os # noqa: F401 TODO: fix
|
||||
|
||||
def foo():
|
||||
x = 1 # noqa: F841 TODO: fix
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_with_newline_in_reason() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file("test.py", "import os\n")?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.arg("--add-noqa=line1\nline2")
|
||||
.arg("--select=F401")
|
||||
.arg("test.py"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: --add-noqa <reason> cannot contain newline characters
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
|
||||
#[test]
|
||||
fn requires_python() -> Result<()> {
|
||||
|
||||
@@ -15,7 +15,6 @@ use std::{
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
mod analyze_graph;
|
||||
mod format;
|
||||
mod lint;
|
||||
|
||||
@@ -63,7 +62,9 @@ impl CliTest {
|
||||
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let case = Self::new()?;
|
||||
case.write_files(files)?;
|
||||
for file in files {
|
||||
case.write_file(file.0, file.1)?;
|
||||
}
|
||||
Ok(case)
|
||||
}
|
||||
|
||||
@@ -152,16 +153,6 @@ impl CliTest {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_files<'a>(
|
||||
&self,
|
||||
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||
) -> Result<()> {
|
||||
for file in files {
|
||||
self.write_file(file.0, file.1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the path to the test directory root.
|
||||
pub(crate) fn root(&self) -> &Path {
|
||||
&self.project_dir
|
||||
|
||||
@@ -14,6 +14,6 @@ info:
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,endLine=2::input.py:1:1: unformatted: File would be reformatted
|
||||
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,col=1,endLine=2,endColumn=1::input.py:1:1: unformatted: File would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -9,6 +9,7 @@ info:
|
||||
- concise
|
||||
- "--show-settings"
|
||||
- test.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -125,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -261,7 +262,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -284,6 +284,5 @@ analyze.target_version = 3.10
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -12,6 +12,7 @@ info:
|
||||
- UP007
|
||||
- test.py
|
||||
- "-"
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -127,7 +128,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -263,7 +264,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -286,6 +286,5 @@ analyze.target_version = 3.11
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -13,6 +13,7 @@ info:
|
||||
- UP007
|
||||
- test.py
|
||||
- "-"
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -129,7 +130,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -265,7 +266,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -288,6 +288,5 @@ analyze.target_version = 3.11
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -14,6 +14,7 @@ info:
|
||||
- py310
|
||||
- test.py
|
||||
- "-"
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -129,7 +130,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -265,7 +266,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -288,6 +288,5 @@ analyze.target_version = 3.10
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -11,6 +11,7 @@ info:
|
||||
- "--select"
|
||||
- UP007
|
||||
- foo/test.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -126,7 +127,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -262,7 +263,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -285,6 +285,5 @@ analyze.target_version = 3.11
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -11,6 +11,7 @@ info:
|
||||
- "--select"
|
||||
- UP007
|
||||
- foo/test.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -126,7 +127,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -262,7 +263,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -285,6 +285,5 @@ analyze.target_version = 3.10
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -284,6 +283,5 @@ analyze.target_version = 3.10
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -284,6 +283,5 @@ analyze.target_version = 3.10
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -9,6 +9,7 @@ info:
|
||||
- concise
|
||||
- test.py
|
||||
- "--show-settings"
|
||||
snapshot_kind: text
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
@@ -125,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -261,7 +262,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -284,6 +284,5 @@ analyze.target_version = 3.11
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -953,11 +953,7 @@ fn rule_f401() {
|
||||
|
||||
#[test]
|
||||
fn rule_f401_output_json() {
|
||||
insta::with_settings!({filters => vec![
|
||||
(r#"("file": ")[^"]+(",)"#, "$1<FILE>$2"),
|
||||
]}, {
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
|
||||
});
|
||||
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1043,7 +1039,7 @@ def mvce(keys, values):
|
||||
----- stdout -----
|
||||
1 C416 [*] unnecessary-comprehension
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
[*] 1 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
@@ -1073,8 +1069,7 @@ def mvce(keys, values):
|
||||
"code": "C416",
|
||||
"name": "unnecessary-comprehension",
|
||||
"count": 1,
|
||||
"fixable": false,
|
||||
"fixable_count": 0
|
||||
"fixable": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1107,8 +1102,7 @@ def mvce(keys, values):
|
||||
"code": "C416",
|
||||
"name": "unnecessary-comprehension",
|
||||
"count": 1,
|
||||
"fixable": true,
|
||||
"fixable_count": 1
|
||||
"fixable": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1116,54 +1110,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"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
2 UP035 [-] deprecated-import
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_statistics_syntax_errors() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
@@ -1860,7 +1806,7 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
|
||||
--> -:1:1
|
||||
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
[*] 1 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
@@ -1903,7 +1849,7 @@ fn check_shows_unsafe_fixes_with_opt_in() {
|
||||
--> -:1:1
|
||||
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
[*] 2 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
@@ -25,14 +25,6 @@ exit_code: 0
|
||||
"fix_availability": "Sometimes",
|
||||
"explanation": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n```python\nimport a\nimport a.b\n```\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n```python\nimport a.b\nimport a\n\na.b.foo()\n```\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\n\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\n",
|
||||
"preview": false,
|
||||
"status": {
|
||||
"Stable": {
|
||||
"since": "v0.0.18"
|
||||
}
|
||||
},
|
||||
"source_location": {
|
||||
"file": "<FILE>",
|
||||
"line": 145
|
||||
}
|
||||
"status": "Stable"
|
||||
}
|
||||
----- stderr -----
|
||||
|
||||
@@ -238,7 +238,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.function_names = [
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
@@ -374,7 +374,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pylint.max_nested_blocks = 5
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.strictly_empty_init_modules = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
@@ -397,6 +396,5 @@ analyze.target_version = 3.7
|
||||
analyze.string_imports = disabled
|
||||
analyze.extension = ExtensionMapping({})
|
||||
analyze.include_dependencies = {}
|
||||
analyze.type_checking_imports = true
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
//! styling.
|
||||
//!
|
||||
//! The above snippet has been built out of the following structure:
|
||||
use crate::{Id, snippet};
|
||||
use crate::snippet;
|
||||
use std::cmp::{Reverse, max, min};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
@@ -189,7 +189,6 @@ impl DisplaySet<'_> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_annotation(
|
||||
&self,
|
||||
line_offset: usize,
|
||||
@@ -200,13 +199,11 @@ impl DisplaySet<'_> {
|
||||
) -> fmt::Result {
|
||||
let hide_severity = annotation.annotation_type.is_none();
|
||||
let color = get_annotation_style(&annotation.annotation_type, stylesheet);
|
||||
|
||||
let formatted_len = if let Some(id) = &annotation.id {
|
||||
let id_len = id.id.len();
|
||||
if hide_severity {
|
||||
id_len
|
||||
id.len()
|
||||
} else {
|
||||
2 + id_len + annotation_type_len(&annotation.annotation_type)
|
||||
2 + id.len() + annotation_type_len(&annotation.annotation_type)
|
||||
}
|
||||
} else {
|
||||
annotation_type_len(&annotation.annotation_type)
|
||||
@@ -259,20 +256,9 @@ impl DisplaySet<'_> {
|
||||
let annotation_type = annotation_type_str(&annotation.annotation_type);
|
||||
if let Some(id) = annotation.id {
|
||||
if hide_severity {
|
||||
buffer.append(
|
||||
line_offset,
|
||||
&format!("{id} ", id = fmt_with_hyperlink(id.id, id.url, stylesheet)),
|
||||
*stylesheet.error(),
|
||||
);
|
||||
buffer.append(line_offset, &format!("{id} "), *stylesheet.error());
|
||||
} else {
|
||||
buffer.append(
|
||||
line_offset,
|
||||
&format!(
|
||||
"{annotation_type}[{id}]",
|
||||
id = fmt_with_hyperlink(id.id, id.url, stylesheet)
|
||||
),
|
||||
*color,
|
||||
);
|
||||
buffer.append(line_offset, &format!("{annotation_type}[{id}]"), *color);
|
||||
}
|
||||
} else {
|
||||
buffer.append(line_offset, annotation_type, *color);
|
||||
@@ -721,7 +707,7 @@ impl DisplaySet<'_> {
|
||||
let style =
|
||||
get_annotation_style(&annotation.annotation_type, stylesheet);
|
||||
let mut formatted_len = if let Some(id) = &annotation.annotation.id {
|
||||
2 + id.id.len()
|
||||
2 + id.len()
|
||||
+ annotation_type_len(&annotation.annotation.annotation_type)
|
||||
} else {
|
||||
annotation_type_len(&annotation.annotation.annotation_type)
|
||||
@@ -738,10 +724,7 @@ impl DisplaySet<'_> {
|
||||
} else if formatted_len != 0 {
|
||||
formatted_len += 2;
|
||||
let id = match &annotation.annotation.id {
|
||||
Some(id) => format!(
|
||||
"[{id}]",
|
||||
id = fmt_with_hyperlink(&id.id, id.url, stylesheet)
|
||||
),
|
||||
Some(id) => format!("[{id}]"),
|
||||
None => String::new(),
|
||||
};
|
||||
buffer.puts(
|
||||
@@ -844,7 +827,7 @@ impl DisplaySet<'_> {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct Annotation<'a> {
|
||||
pub(crate) annotation_type: DisplayAnnotationType,
|
||||
pub(crate) id: Option<Id<'a>>,
|
||||
pub(crate) id: Option<&'a str>,
|
||||
pub(crate) label: Vec<DisplayTextFragment<'a>>,
|
||||
pub(crate) is_fixable: bool,
|
||||
}
|
||||
@@ -1157,7 +1140,7 @@ fn format_message<'m>(
|
||||
|
||||
fn format_title<'a>(
|
||||
level: crate::Level,
|
||||
id: Option<Id<'a>>,
|
||||
id: Option<&'a str>,
|
||||
label: &'a str,
|
||||
is_fixable: bool,
|
||||
) -> DisplayLine<'a> {
|
||||
@@ -1175,7 +1158,7 @@ fn format_title<'a>(
|
||||
|
||||
fn format_footer<'a>(
|
||||
level: crate::Level,
|
||||
id: Option<Id<'a>>,
|
||||
id: Option<&'a str>,
|
||||
label: &'a str,
|
||||
) -> Vec<DisplayLine<'a>> {
|
||||
let mut result = vec![];
|
||||
@@ -1723,7 +1706,6 @@ fn format_body<'m>(
|
||||
annotation: Annotation {
|
||||
annotation_type,
|
||||
id: None,
|
||||
|
||||
label: format_label(annotation.label, None),
|
||||
is_fixable: false,
|
||||
},
|
||||
@@ -1905,40 +1887,3 @@ fn char_width(c: char) -> Option<usize> {
|
||||
unicode_width::UnicodeWidthChar::width(c)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fmt_with_hyperlink<'a, T>(
|
||||
content: T,
|
||||
url: Option<&'a str>,
|
||||
stylesheet: &Stylesheet,
|
||||
) -> impl std::fmt::Display + 'a
|
||||
where
|
||||
T: std::fmt::Display + 'a,
|
||||
{
|
||||
struct FmtHyperlink<'a, T> {
|
||||
content: T,
|
||||
url: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Display for FmtHyperlink<'_, T>
|
||||
where
|
||||
T: std::fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(url) = self.url {
|
||||
write!(f, "\x1B]8;;{url}\x1B\\")?;
|
||||
}
|
||||
|
||||
self.content.fmt(f)?;
|
||||
|
||||
if self.url.is_some() {
|
||||
f.write_str("\x1B]8;;\x1B\\")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let url = if stylesheet.hyperlink { url } else { None };
|
||||
|
||||
FmtHyperlink { content, url }
|
||||
}
|
||||
|
||||
@@ -76,7 +76,6 @@ impl Renderer {
|
||||
}
|
||||
.effects(Effects::BOLD),
|
||||
none: Style::new(),
|
||||
hyperlink: true,
|
||||
},
|
||||
..Self::plain()
|
||||
}
|
||||
@@ -155,11 +154,6 @@ impl Renderer {
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn hyperlink(mut self, hyperlink: bool) -> Self {
|
||||
self.stylesheet.hyperlink = hyperlink;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the string used for when a long line is cut.
|
||||
///
|
||||
/// The default is `...` (three `U+002E` characters).
|
||||
|
||||
@@ -10,7 +10,6 @@ pub(crate) struct Stylesheet {
|
||||
pub(crate) line_no: Style,
|
||||
pub(crate) emphasis: Style,
|
||||
pub(crate) none: Style,
|
||||
pub(crate) hyperlink: bool,
|
||||
}
|
||||
|
||||
impl Default for Stylesheet {
|
||||
@@ -30,7 +29,6 @@ impl Stylesheet {
|
||||
line_no: Style::new(),
|
||||
emphasis: Style::new(),
|
||||
none: Style::new(),
|
||||
hyperlink: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,13 @@
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub(crate) struct Id<'a> {
|
||||
pub(crate) id: &'a str,
|
||||
pub(crate) url: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// Primary structure provided for formatting
|
||||
///
|
||||
/// See [`Level::title`] to create a [`Message`]
|
||||
#[derive(Debug)]
|
||||
pub struct Message<'a> {
|
||||
pub(crate) level: Level,
|
||||
pub(crate) id: Option<Id<'a>>,
|
||||
pub(crate) id: Option<&'a str>,
|
||||
pub(crate) title: &'a str,
|
||||
pub(crate) snippets: Vec<Snippet<'a>>,
|
||||
pub(crate) footer: Vec<Message<'a>>,
|
||||
@@ -34,12 +28,7 @@ pub struct Message<'a> {
|
||||
|
||||
impl<'a> Message<'a> {
|
||||
pub fn id(mut self, id: &'a str) -> Self {
|
||||
self.id = Some(Id { id, url: None });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn id_with_url(mut self, id: &'a str, url: Option<&'a str>) -> Self {
|
||||
self.id = Some(Id { id, url });
|
||||
self.id = Some(id);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -19,32 +19,32 @@ doctest = false
|
||||
[[bench]]
|
||||
name = "linter"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "lexer"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "parser"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "formatter"
|
||||
harness = false
|
||||
required-features = ["ruff_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "ty"
|
||||
harness = false
|
||||
required-features = ["ty_instrumented"]
|
||||
required-features = ["instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "ty_walltime"
|
||||
harness = false
|
||||
required-features = ["ty_walltime"]
|
||||
required-features = ["walltime"]
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
@@ -59,6 +59,8 @@ divan = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true }
|
||||
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
|
||||
criterion = { workspace = true, default-features = false, optional = true }
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -67,33 +69,22 @@ tracing = { workspace = true }
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
|
||||
# Enables the ruff instrumented benchmarks
|
||||
ruff_instrumented = [
|
||||
default = ["instrumented", "walltime"]
|
||||
# Enables the benchmark that should only run with codspeed's instrumented runner
|
||||
instrumented = [
|
||||
"criterion",
|
||||
"ruff_linter",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"mimalloc",
|
||||
"tikv-jemallocator",
|
||||
]
|
||||
# Enables the ty instrumented benchmarks
|
||||
ty_instrumented = [
|
||||
"criterion",
|
||||
"ty_project",
|
||||
"ruff_python_trivia",
|
||||
]
|
||||
codspeed = ["codspeed-criterion-compat"]
|
||||
# Enables the ty_walltime benchmarks
|
||||
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
# Enables benchmark that should only run with codspeed's walltime runner.
|
||||
walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true, optional = true }
|
||||
[target.'cfg(target_os = "windows")'.dev-dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-hash = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
@@ -6,8 +6,7 @@ use criterion::{
|
||||
use ruff_benchmark::{
|
||||
LARGE_DATASET, NUMPY_CTYPESLIB, NUMPY_GLOBALS, PYDANTIC_TYPES, TestCase, UNICODE_PYPINYIN,
|
||||
};
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_parser::{Mode, lexer};
|
||||
use ruff_python_parser::{Mode, TokenKind, lexer};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[global_allocator]
|
||||
|
||||
@@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
120,
|
||||
110,
|
||||
);
|
||||
|
||||
bench_project(&benchmark, criterion);
|
||||
|
||||
@@ -71,13 +71,16 @@ impl Display for Benchmark<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_project(db: &ProjectDatabase, project_name: &str, max_diagnostics: usize) {
|
||||
fn check_project(db: &ProjectDatabase, max_diagnostics: usize) {
|
||||
let result = db.check();
|
||||
let diagnostics = result.len();
|
||||
|
||||
assert!(
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,7 +123,7 @@ static COLOUR_SCIENCE: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
1070,
|
||||
600,
|
||||
);
|
||||
|
||||
static FREQTRADE: Benchmark = Benchmark::new(
|
||||
@@ -143,7 +146,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
600,
|
||||
400,
|
||||
);
|
||||
|
||||
static PANDAS: Benchmark = Benchmark::new(
|
||||
@@ -163,7 +166,7 @@ static PANDAS: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
4057,
|
||||
3000,
|
||||
);
|
||||
|
||||
static PYDANTIC: Benchmark = Benchmark::new(
|
||||
@@ -181,7 +184,7 @@ static PYDANTIC: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY39,
|
||||
},
|
||||
7000,
|
||||
1000,
|
||||
);
|
||||
|
||||
static SYMPY: Benchmark = Benchmark::new(
|
||||
@@ -194,7 +197,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13200,
|
||||
13000,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
@@ -223,7 +226,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
1100,
|
||||
750,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
@@ -231,59 +234,34 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
bencher
|
||||
.with_inputs(|| benchmark.setup_iteration())
|
||||
.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)]
|
||||
fn altair(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &ALTAIR);
|
||||
#[bench(args=[&ALTAIR, &FREQTRADE, &PYDANTIC, &TANJUN], sample_size=2, sample_count=3)]
|
||||
fn small(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn freqtrade(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &FREQTRADE);
|
||||
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
fn medium(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn tanjun(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &TANJUN);
|
||||
#[bench(args=[&SYMPY], sample_size=1, sample_count=2)]
|
||||
fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn pydantic(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &PYDANTIC);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 3)]
|
||||
fn static_frame(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &STATIC_FRAME);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn colour_science(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &COLOUR_SCIENCE);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn pandas(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &PANDAS);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn sympy(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &SYMPY);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 3, sample_count = 8)]
|
||||
fn multithreaded(bencher: Bencher) {
|
||||
#[bench(args=[&PYDANTIC], sample_size=3, sample_count=8)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
bencher
|
||||
.with_inputs(|| ALTAIR.setup_iteration())
|
||||
.with_inputs(|| benchmark.setup_iteration())
|
||||
.bench_local_values(|db| {
|
||||
thread_pool.install(|| {
|
||||
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics);
|
||||
check_project(&db, benchmark.max_diagnostics);
|
||||
db
|
||||
})
|
||||
});
|
||||
@@ -307,7 +285,7 @@ fn main() {
|
||||
// branch when looking up the ingredient index.
|
||||
{
|
||||
let db = TANJUN.setup_iteration();
|
||||
check_project(&db, TANJUN.project.name, TANJUN.max_diagnostics);
|
||||
check_project(&db, TANJUN.max_diagnostics);
|
||||
}
|
||||
|
||||
divan::main();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(any(feature = "ty_instrumented", feature = "ruff_instrumented"))]
|
||||
#[cfg(feature = "instrumented")]
|
||||
pub mod criterion;
|
||||
pub mod real_world_projects;
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
similar = { workspace = true }
|
||||
supports-hyperlinks = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// Signals a [`CancellationToken`] that it should be canceled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CancellationTokenSource {
|
||||
cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Default for CancellationTokenSource {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CancellationTokenSource {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cancelled: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_cancellation_requested(&self) -> bool {
|
||||
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Creates a new token that uses this source.
|
||||
pub fn token(&self) -> CancellationToken {
|
||||
CancellationToken {
|
||||
cancelled: self.cancelled.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests cancellation for operations using this token.
|
||||
pub fn cancel(&self) {
|
||||
self.cancelled
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Token signals whether an operation should be canceled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CancellationToken {
|
||||
cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{borrow::Cow, fmt::Formatter, path::Path, sync::Arc};
|
||||
use std::{fmt::Formatter, path::Path, sync::Arc};
|
||||
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
@@ -11,7 +11,6 @@ pub use self::render::{
|
||||
ceil_char_boundary,
|
||||
github::{DisplayGithubDiagnostics, GithubRenderer},
|
||||
};
|
||||
use crate::cancellation::CancellationToken;
|
||||
use crate::{Db, files::File};
|
||||
|
||||
mod render;
|
||||
@@ -65,8 +64,6 @@ impl Diagnostic {
|
||||
id,
|
||||
severity,
|
||||
message: message.into_diagnostic_message(),
|
||||
custom_concise_message: None,
|
||||
documentation_url: None,
|
||||
annotations: vec![],
|
||||
subs: vec![],
|
||||
fix: None,
|
||||
@@ -167,8 +164,28 @@ impl Diagnostic {
|
||||
/// Returns the primary message for this diagnostic.
|
||||
///
|
||||
/// A diagnostic always has a message, but it may be empty.
|
||||
///
|
||||
/// NOTE: At present, this routine will return the first primary
|
||||
/// annotation's message as the primary message when the main diagnostic
|
||||
/// message is empty. This is meant to facilitate an incremental migration
|
||||
/// in ty over to the new diagnostic data model. (The old data model
|
||||
/// didn't distinguish between messages on the entire diagnostic and
|
||||
/// messages attached to a particular span.)
|
||||
pub fn primary_message(&self) -> &str {
|
||||
self.inner.message.as_str()
|
||||
if !self.inner.message.as_str().is_empty() {
|
||||
return self.inner.message.as_str();
|
||||
}
|
||||
// FIXME: As a special case, while we're migrating ty
|
||||
// to the new diagnostic data model, we'll look for a primary
|
||||
// message from the primary annotation. This is because most
|
||||
// ty diagnostics are created with an empty diagnostic
|
||||
// message and instead attach the message to the annotation.
|
||||
// Fixing this will require touching basically every diagnostic
|
||||
// in ty, so we do it this way for now to match the old
|
||||
// semantics. ---AG
|
||||
self.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Introspects this diagnostic and returns what kind of "primary" message
|
||||
@@ -180,35 +197,35 @@ impl Diagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
pub fn concise_message(&self) -> ConciseMessage<'_> {
|
||||
if let Some(custom_message) = &self.inner.custom_concise_message {
|
||||
return ConciseMessage::Custom(custom_message.as_str());
|
||||
}
|
||||
|
||||
let main = self.inner.message.as_str();
|
||||
let annotation = self
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a custom message for the concise formatting of this diagnostic.
|
||||
///
|
||||
/// This overrides the default behavior of generating a concise message
|
||||
/// from the main diagnostic message and the primary annotation.
|
||||
pub fn set_concise_message(&mut self, message: impl IntoDiagnosticMessage) {
|
||||
Arc::make_mut(&mut self.inner).custom_concise_message =
|
||||
Some(message.into_diagnostic_message());
|
||||
}
|
||||
|
||||
/// Returns the severity of this diagnostic.
|
||||
///
|
||||
/// Note that this may be different than the severity of sub-diagnostics.
|
||||
@@ -322,13 +339,6 @@ impl Diagnostic {
|
||||
Arc::make_mut(&mut self.inner).fix = Some(fix);
|
||||
}
|
||||
|
||||
/// If `fix` is `Some`, set the fix for this diagnostic.
|
||||
pub fn set_optional_fix(&mut self, fix: Option<Fix>) {
|
||||
if let Some(fix) = fix {
|
||||
self.set_fix(fix);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the fix for this diagnostic.
|
||||
pub fn remove_fix(&mut self) {
|
||||
Arc::make_mut(&mut self.inner).fix = None;
|
||||
@@ -346,14 +356,6 @@ impl Diagnostic {
|
||||
.is_some_and(|fix| fix.applies(config.fix_applicability))
|
||||
}
|
||||
|
||||
pub fn documentation_url(&self) -> Option<&str> {
|
||||
self.inner.documentation_url.as_deref()
|
||||
}
|
||||
|
||||
pub fn set_documentation_url(&mut self, url: Option<String>) {
|
||||
Arc::make_mut(&mut self.inner).documentation_url = url;
|
||||
}
|
||||
|
||||
/// Returns the offset of the parent statement for this diagnostic if it exists.
|
||||
///
|
||||
/// This is primarily used for checking noqa/secondary code suppressions.
|
||||
@@ -411,6 +413,11 @@ impl Diagnostic {
|
||||
self.id().is_invalid_syntax()
|
||||
}
|
||||
|
||||
/// Returns the message body to display to the user.
|
||||
pub fn body(&self) -> &str {
|
||||
self.primary_message()
|
||||
}
|
||||
|
||||
/// Returns the message of the first sub-diagnostic with a `Help` severity.
|
||||
///
|
||||
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
|
||||
@@ -422,6 +429,28 @@ impl Diagnostic {
|
||||
.map(|sub| sub.inner.message.as_str())
|
||||
}
|
||||
|
||||
/// Returns the URL for the rule documentation, if it exists.
|
||||
pub fn to_ruff_url(&self) -> Option<String> {
|
||||
match self.id() {
|
||||
DiagnosticId::Panic
|
||||
| DiagnosticId::Io
|
||||
| DiagnosticId::InvalidSyntax
|
||||
| DiagnosticId::RevealedType
|
||||
| DiagnosticId::UnknownRule
|
||||
| DiagnosticId::InvalidGlob
|
||||
| DiagnosticId::EmptyInclude
|
||||
| DiagnosticId::UnnecessaryOverridesSection
|
||||
| DiagnosticId::UselessOverridesSection
|
||||
| DiagnosticId::DeprecatedSetting
|
||||
| DiagnosticId::Unformatted
|
||||
| DiagnosticId::InvalidCliOption
|
||||
| DiagnosticId::InternalError => None,
|
||||
DiagnosticId::Lint(lint_name) => {
|
||||
Some(format!("{}/rules/{lint_name}", env!("CARGO_PKG_HOMEPAGE")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the filename for the message.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`.
|
||||
@@ -501,10 +530,8 @@ impl Diagnostic {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
documentation_url: Option<String>,
|
||||
severity: Severity,
|
||||
message: DiagnosticMessage,
|
||||
custom_concise_message: Option<DiagnosticMessage>,
|
||||
annotations: Vec<Annotation>,
|
||||
subs: Vec<SubDiagnostic>,
|
||||
fix: Option<Fix>,
|
||||
@@ -656,6 +683,18 @@ impl SubDiagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -665,10 +704,11 @@ impl SubDiagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -838,10 +878,6 @@ impl Annotation {
|
||||
pub fn hide_snippet(&mut self, yes: bool) {
|
||||
self.hide_snippet = yes;
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.is_primary
|
||||
}
|
||||
}
|
||||
|
||||
/// Tags that can be associated with an annotation.
|
||||
@@ -1308,8 +1344,6 @@ pub struct DisplayDiagnosticConfig {
|
||||
show_fix_diff: bool,
|
||||
/// The lowest applicability that should be shown when reporting diagnostics.
|
||||
fix_applicability: Applicability,
|
||||
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl DisplayDiagnosticConfig {
|
||||
@@ -1383,20 +1417,6 @@ impl DisplayDiagnosticConfig {
|
||||
pub fn fix_applicability(&self) -> Applicability {
|
||||
self.fix_applicability
|
||||
}
|
||||
|
||||
pub fn with_cancellation_token(
|
||||
mut self,
|
||||
token: Option<CancellationToken>,
|
||||
) -> DisplayDiagnosticConfig {
|
||||
self.cancellation_token = token;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_canceled(&self) -> bool {
|
||||
self.cancellation_token
|
||||
.as_ref()
|
||||
.is_some_and(|token| token.is_cancelled())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DisplayDiagnosticConfig {
|
||||
@@ -1410,7 +1430,6 @@ impl Default for DisplayDiagnosticConfig {
|
||||
show_fix_status: false,
|
||||
show_fix_diff: false,
|
||||
fix_applicability: Applicability::Safe,
|
||||
cancellation_token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1479,21 +1498,28 @@ pub enum DiagnosticFormat {
|
||||
pub enum ConciseMessage<'a> {
|
||||
/// A diagnostic contains a non-empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
MainDiagnostic(&'a str),
|
||||
/// A diagnostic contains an empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "old" data model.
|
||||
PrimaryAnnotation(&'a str),
|
||||
/// A diagnostic contains a non-empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
Both { main: &'a str, annotation: &'a str },
|
||||
/// A custom concise message has been provided.
|
||||
Custom(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> ConciseMessage<'a> {
|
||||
pub fn to_str(&self) -> Cow<'a, str> {
|
||||
match self {
|
||||
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
|
||||
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
|
||||
}
|
||||
}
|
||||
/// A diagnostic contains an empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This indicates that the diagnostic is probably using the old
|
||||
/// model.
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConciseMessage<'_> {
|
||||
@@ -1502,26 +1528,17 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
ConciseMessage::MainDiagnostic(main) => {
|
||||
write!(f, "{main}")
|
||||
}
|
||||
ConciseMessage::PrimaryAnnotation(annotation) => {
|
||||
write!(f, "{annotation}")
|
||||
}
|
||||
ConciseMessage::Both { main, annotation } => {
|
||||
write!(f, "{main}: {annotation}")
|
||||
}
|
||||
ConciseMessage::Custom(message) => {
|
||||
write!(f, "{message}")
|
||||
}
|
||||
ConciseMessage::Empty => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConciseMessage<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A diagnostic message string.
|
||||
///
|
||||
/// This is, for all intents and purposes, equivalent to a `Box<str>`.
|
||||
|
||||
@@ -205,7 +205,6 @@ impl<'a> Resolved<'a> {
|
||||
struct ResolvedDiagnostic<'a> {
|
||||
level: AnnotateLevel,
|
||||
id: Option<String>,
|
||||
documentation_url: Option<String>,
|
||||
message: String,
|
||||
annotations: Vec<ResolvedAnnotation<'a>>,
|
||||
is_fixable: bool,
|
||||
@@ -241,12 +240,12 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
// `DisplaySet::format_annotation` for both cases, but this is a small hack to improve
|
||||
// the formatting of syntax errors for now. This should also be kept consistent with the
|
||||
// concise formatting.
|
||||
diag.secondary_code().map_or_else(
|
||||
Some(diag.secondary_code().map_or_else(
|
||||
|| format!("{id}:", id = diag.inner.id),
|
||||
|code| code.to_string(),
|
||||
)
|
||||
))
|
||||
} else {
|
||||
diag.inner.id.to_string()
|
||||
Some(diag.inner.id.to_string())
|
||||
};
|
||||
|
||||
let level = if config.hide_severity {
|
||||
@@ -257,8 +256,7 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
|
||||
ResolvedDiagnostic {
|
||||
level,
|
||||
id: Some(id),
|
||||
documentation_url: diag.documentation_url().map(ToString::to_string),
|
||||
id,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
is_fixable: config.show_fix_status && diag.has_applicable_fix(config),
|
||||
@@ -289,7 +287,6 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
ResolvedDiagnostic {
|
||||
level: diag.inner.severity.to_annotate(),
|
||||
id: None,
|
||||
documentation_url: None,
|
||||
message: diag.inner.message.as_str().to_string(),
|
||||
annotations,
|
||||
is_fixable: false,
|
||||
@@ -388,7 +385,6 @@ impl<'a> ResolvedDiagnostic<'a> {
|
||||
RenderableDiagnostic {
|
||||
level: self.level,
|
||||
id: self.id.as_deref(),
|
||||
documentation_url: self.documentation_url.as_deref(),
|
||||
message: &self.message,
|
||||
snippets_by_input,
|
||||
is_fixable: self.is_fixable,
|
||||
@@ -489,7 +485,6 @@ struct RenderableDiagnostic<'r> {
|
||||
/// An ID is always present for top-level diagnostics and always absent for
|
||||
/// sub-diagnostics.
|
||||
id: Option<&'r str>,
|
||||
documentation_url: Option<&'r str>,
|
||||
/// The message emitted with the diagnostic, before any snippets are
|
||||
/// rendered.
|
||||
message: &'r str,
|
||||
@@ -524,7 +519,7 @@ impl RenderableDiagnostic<'_> {
|
||||
.is_fixable(self.is_fixable)
|
||||
.lineno_offset(self.header_offset);
|
||||
if let Some(id) = self.id {
|
||||
message = message.id_with_url(id, self.documentation_url);
|
||||
message = message.id(id);
|
||||
}
|
||||
message.snippets(snippets)
|
||||
}
|
||||
@@ -2881,12 +2876,6 @@ watermelon
|
||||
self.diag.help(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the documentation URL for the diagnostic.
|
||||
pub(super) fn documentation_url(mut self, url: impl Into<String>) -> DiagnosticBuilder<'e> {
|
||||
self.diag.set_documentation_url(Some(url.into()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper builder for tersely populating a `SubDiagnostic`.
|
||||
@@ -3001,7 +2990,6 @@ def fibonacci(n):
|
||||
TextSize::from(10),
|
||||
))))
|
||||
.noqa_offset(TextSize::from(7))
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/unused-import")
|
||||
.build(),
|
||||
env.builder(
|
||||
"unused-variable",
|
||||
@@ -3016,13 +3004,11 @@ def fibonacci(n):
|
||||
TextSize::from(99),
|
||||
)))
|
||||
.noqa_offset(TextSize::from(94))
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/unused-variable")
|
||||
.build(),
|
||||
env.builder("undefined-name", Severity::Error, "Undefined name `a`")
|
||||
.primary("undef.py", "1:3", "1:4", "")
|
||||
.secondary_code("F821")
|
||||
.noqa_offset(TextSize::from(3))
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/undefined-name")
|
||||
.build(),
|
||||
];
|
||||
|
||||
@@ -3137,7 +3123,6 @@ if call(foo
|
||||
TextSize::from(19),
|
||||
))))
|
||||
.noqa_offset(TextSize::from(16))
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/unused-import")
|
||||
.build(),
|
||||
env.builder(
|
||||
"unused-import",
|
||||
@@ -3152,7 +3137,6 @@ if call(foo
|
||||
TextSize::from(40),
|
||||
))))
|
||||
.noqa_offset(TextSize::from(35))
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/unused-import")
|
||||
.build(),
|
||||
env.builder(
|
||||
"unused-variable",
|
||||
@@ -3167,7 +3151,6 @@ if call(foo
|
||||
TextSize::from(104),
|
||||
))))
|
||||
.noqa_offset(TextSize::from(98))
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/unused-variable")
|
||||
.build(),
|
||||
];
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AzureRenderer<'_> {
|
||||
f,
|
||||
"code={code};]{body}",
|
||||
code = diag.secondary_code_or_id(),
|
||||
body = diag.concise_message(),
|
||||
body = diag.body(),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::diagnostic::{
|
||||
Diagnostic, DisplayDiagnosticConfig, Severity,
|
||||
stylesheet::{DiagnosticStylesheet, fmt_styled, fmt_with_hyperlink},
|
||||
stylesheet::{DiagnosticStylesheet, fmt_styled},
|
||||
};
|
||||
|
||||
use super::FileResolver;
|
||||
@@ -28,10 +28,6 @@ impl<'a> ConciseRenderer<'a> {
|
||||
|
||||
let sep = fmt_styled(":", stylesheet.separator);
|
||||
for diag in diagnostics {
|
||||
if self.config.is_canceled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(span) = diag.primary_span() {
|
||||
write!(
|
||||
f,
|
||||
@@ -66,29 +62,18 @@ impl<'a> ConciseRenderer<'a> {
|
||||
}
|
||||
write!(f, "{sep} ")?;
|
||||
}
|
||||
|
||||
if self.config.hide_severity {
|
||||
if let Some(code) = diag.secondary_code() {
|
||||
write!(
|
||||
f,
|
||||
"{code} ",
|
||||
code = fmt_styled(
|
||||
fmt_with_hyperlink(&code, diag.documentation_url(), &stylesheet),
|
||||
stylesheet.secondary_code
|
||||
)
|
||||
code = fmt_styled(code, stylesheet.secondary_code)
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{id}: ",
|
||||
id = fmt_styled(
|
||||
fmt_with_hyperlink(
|
||||
&diag.inner.id,
|
||||
diag.documentation_url(),
|
||||
&stylesheet
|
||||
),
|
||||
stylesheet.secondary_code
|
||||
)
|
||||
id = fmt_styled(diag.inner.id.as_str(), stylesheet.secondary_code)
|
||||
)?;
|
||||
}
|
||||
if self.config.show_fix_status {
|
||||
@@ -108,10 +93,7 @@ impl<'a> ConciseRenderer<'a> {
|
||||
f,
|
||||
"{severity}[{id}] ",
|
||||
severity = fmt_styled(severity, severity_style),
|
||||
id = fmt_styled(
|
||||
fmt_with_hyperlink(&diag.id(), diag.documentation_url(), &stylesheet),
|
||||
stylesheet.emphasis
|
||||
)
|
||||
id = fmt_styled(diag.id(), stylesheet.emphasis)
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,14 +49,9 @@ impl<'a> FullRenderer<'a> {
|
||||
.help(stylesheet.help)
|
||||
.line_no(stylesheet.line_no)
|
||||
.emphasis(stylesheet.emphasis)
|
||||
.none(stylesheet.none)
|
||||
.hyperlink(stylesheet.hyperlink);
|
||||
.none(stylesheet.none);
|
||||
|
||||
for diag in diagnostics {
|
||||
if self.config.is_canceled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let resolved = Resolved::new(self.resolver, diag, self.config);
|
||||
let renderable = resolved.to_renderable(self.config.context);
|
||||
for diag in renderable.diagnostics.iter() {
|
||||
@@ -117,16 +112,16 @@ impl std::fmt::Display for Diff<'_> {
|
||||
// `None`, indicating a regular script file, all the lines will be in one "cell" under the
|
||||
// `None` key.
|
||||
let cells = if let Some(notebook_index) = &self.notebook_index {
|
||||
let mut last_cell_index = OneIndexed::MIN;
|
||||
let mut last_cell = OneIndexed::MIN;
|
||||
let mut cells: Vec<(Option<OneIndexed>, TextSize)> = Vec::new();
|
||||
for cell in notebook_index.iter() {
|
||||
if cell.cell_index() != last_cell_index {
|
||||
let offset = source_code.line_start(cell.start_row());
|
||||
cells.push((Some(last_cell_index), offset));
|
||||
last_cell_index = cell.cell_index();
|
||||
for (row, cell) in notebook_index.iter() {
|
||||
if cell != last_cell {
|
||||
let offset = source_code.line_start(row);
|
||||
cells.push((Some(last_cell), offset));
|
||||
last_cell = cell;
|
||||
}
|
||||
}
|
||||
cells.push((Some(last_cell_index), source_text.text_len()));
|
||||
cells.push((Some(last_cell), source_text.text_len()));
|
||||
cells
|
||||
} else {
|
||||
vec![(None, source_text.text_len())]
|
||||
@@ -708,7 +703,52 @@ print()
|
||||
env.show_fix_status(true);
|
||||
env.fix_applicability(Applicability::DisplayOnly);
|
||||
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics));
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
::: cell 1
|
||||
1 | # cell 1
|
||||
- import os
|
||||
|
||||
error[unused-import][*]: `math` imported but unused
|
||||
--> notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
help: Remove unused import: `math`
|
||||
::: cell 2
|
||||
1 | # cell 2
|
||||
- import math
|
||||
2 |
|
||||
3 | print('hello world')
|
||||
|
||||
error[unused-variable][*]: Local variable `x` is assigned to but never used
|
||||
--> notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
::: cell 3
|
||||
1 | # cell 3
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
- x = 1
|
||||
4 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -728,7 +768,31 @@ print()
|
||||
}
|
||||
*fix = Fix::unsafe_edits(edits.remove(0), edits);
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic));
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
::: cell 1
|
||||
1 | # cell 1
|
||||
- import os
|
||||
::: cell 2
|
||||
1 | # cell 2
|
||||
- import math
|
||||
2 |
|
||||
3 | print('hello world')
|
||||
::: cell 3
|
||||
1 | # cell 3
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
- x = 1
|
||||
4 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
");
|
||||
}
|
||||
|
||||
/// Carriage return (`\r`) is a valid line-ending in Python, so we should normalize this to a
|
||||
|
||||
@@ -49,26 +49,14 @@ impl<'a> GithubRenderer<'a> {
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
// GitHub Actions workflow commands have constraints on error annotations:
|
||||
// - `col` and `endColumn` cannot be set if `line` and `endLine` are different
|
||||
// See: https://github.com/astral-sh/ruff/issues/22074
|
||||
if start_location.line == end_location.line {
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
",line={row},endLine={end_row}::",
|
||||
row = start_location.line,
|
||||
end_row = end_location.line,
|
||||
)?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
@@ -87,7 +75,7 @@ impl<'a> GithubRenderer<'a> {
|
||||
write!(f, "{id}:", id = diagnostic.id())?;
|
||||
}
|
||||
|
||||
writeln!(f, " {}", diagnostic.concise_message())?;
|
||||
writeln!(f, " {}", diagnostic.body())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let description = diagnostic.concise_message();
|
||||
let description = diagnostic.body();
|
||||
let check_name = diagnostic.secondary_code_or_id();
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => "info",
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::{ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -100,8 +100,8 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
if config.preview {
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.documentation_url(),
|
||||
message: diagnostic.concise_message(),
|
||||
url: diagnostic.to_ruff_url(),
|
||||
message: diagnostic.body(),
|
||||
fix,
|
||||
cell: notebook_cell_index,
|
||||
location: start_location.map(JsonLocation::from),
|
||||
@@ -112,8 +112,8 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
} else {
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.documentation_url(),
|
||||
message: diagnostic.concise_message(),
|
||||
url: diagnostic.to_ruff_url(),
|
||||
message: diagnostic.body(),
|
||||
fix,
|
||||
cell: notebook_cell_index,
|
||||
location: Some(start_location.unwrap_or_default().into()),
|
||||
@@ -226,9 +226,9 @@ pub(crate) struct JsonDiagnostic<'a> {
|
||||
filename: Option<&'a str>,
|
||||
fix: Option<JsonFix<'a>>,
|
||||
location: Option<JsonLocation>,
|
||||
message: ConciseMessage<'a>,
|
||||
message: &'a str,
|
||||
noqa_row: Option<OneIndexed>,
|
||||
url: Option<&'a str>,
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -294,10 +294,7 @@ mod tests {
|
||||
env.format(DiagnosticFormat::Json);
|
||||
env.preview(false);
|
||||
|
||||
let diag = env
|
||||
.err()
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/test-diagnostic")
|
||||
.build();
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
@@ -331,10 +328,7 @@ mod tests {
|
||||
env.format(DiagnosticFormat::Json);
|
||||
env.preview(true);
|
||||
|
||||
let diag = env
|
||||
.err()
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/test-diagnostic")
|
||||
.build();
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(
|
||||
env.render(&diag),
|
||||
|
||||
@@ -56,17 +56,17 @@ impl<'a> JunitRenderer<'a> {
|
||||
start_location: location,
|
||||
} = diagnostic;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(diagnostic.concise_message().to_str());
|
||||
status.set_message(diagnostic.body());
|
||||
|
||||
if let Some(location) = location {
|
||||
status.set_description(format!(
|
||||
"line {row}, col {col}, {body}",
|
||||
row = location.line,
|
||||
col = location.column,
|
||||
body = diagnostic.concise_message()
|
||||
body = diagnostic.body()
|
||||
));
|
||||
} else {
|
||||
status.set_description(diagnostic.concise_message().to_str());
|
||||
status.set_description(diagnostic.body());
|
||||
}
|
||||
|
||||
let code = diagnostic
|
||||
|
||||
@@ -55,7 +55,7 @@ impl PylintRenderer<'_> {
|
||||
f,
|
||||
"{path}:{row}: [{code}] {body}",
|
||||
path = filename,
|
||||
body = diagnostic.concise_message()
|
||||
body = diagnostic.body()
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::{ConciseMessage, Diagnostic};
|
||||
use crate::diagnostic::Diagnostic;
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -76,13 +76,13 @@ fn diagnostic_to_rdjson<'a>(
|
||||
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
|
||||
|
||||
RdjsonDiagnostic {
|
||||
message: diagnostic.concise_message(),
|
||||
message: diagnostic.body(),
|
||||
location,
|
||||
code: RdjsonCode {
|
||||
value: diagnostic
|
||||
.secondary_code()
|
||||
.map_or_else(|| diagnostic.name(), |code| code.as_str()),
|
||||
url: diagnostic.documentation_url(),
|
||||
url: diagnostic.to_ruff_url(),
|
||||
},
|
||||
suggestions: rdjson_suggestions(
|
||||
edits,
|
||||
@@ -155,7 +155,7 @@ struct RdjsonDiagnostic<'a> {
|
||||
code: RdjsonCode<'a>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
location: Option<RdjsonLocation<'a>>,
|
||||
message: ConciseMessage<'a>,
|
||||
message: &'a str,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
suggestions: Vec<RdjsonSuggestion<'a>>,
|
||||
}
|
||||
@@ -182,7 +182,7 @@ impl RdjsonRange {
|
||||
#[derive(Serialize)]
|
||||
struct RdjsonCode<'a> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<&'a str>,
|
||||
url: Option<String>,
|
||||
value: &'a str,
|
||||
}
|
||||
|
||||
@@ -217,10 +217,7 @@ mod tests {
|
||||
env.format(DiagnosticFormat::Rdjson);
|
||||
env.preview(false);
|
||||
|
||||
let diag = env
|
||||
.err()
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/test-diagnostic")
|
||||
.build();
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diag));
|
||||
}
|
||||
@@ -231,10 +228,7 @@ mod tests {
|
||||
env.format(DiagnosticFormat::Rdjson);
|
||||
env.preview(true);
|
||||
|
||||
let diag = env
|
||||
.err()
|
||||
.documentation_url("https://docs.astral.sh/ruff/rules/test-diagnostic")
|
||||
.build();
|
||||
let diag = env.err().build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diag));
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/full.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
::: cell 1
|
||||
1 | # cell 1
|
||||
- import os
|
||||
|
||||
error[unused-import][*]: `math` imported but unused
|
||||
--> notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
help: Remove unused import: `math`
|
||||
::: cell 2
|
||||
1 | # cell 2
|
||||
- import math
|
||||
2 |
|
||||
3 | print('hello world')
|
||||
|
||||
error[unused-variable][*]: Local variable `x` is assigned to but never used
|
||||
--> notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
::: cell 3
|
||||
1 | # cell 3
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
- x = 1
|
||||
4 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_db/src/diagnostic/render/full.rs
|
||||
expression: env.render(&diagnostic)
|
||||
---
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
::: cell 1
|
||||
1 | # cell 1
|
||||
- import os
|
||||
::: cell 2
|
||||
1 | # cell 2
|
||||
- import math
|
||||
2 |
|
||||
3 | print('hello world')
|
||||
::: cell 3
|
||||
1 | # cell 3
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
- x = 1
|
||||
4 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,endLine=2::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,endLine=4::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
|
||||
@@ -31,43 +31,6 @@ where
|
||||
FmtStyled { content, style }
|
||||
}
|
||||
|
||||
pub(super) fn fmt_with_hyperlink<'a, T>(
|
||||
content: T,
|
||||
url: Option<&'a str>,
|
||||
stylesheet: &DiagnosticStylesheet,
|
||||
) -> 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 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 }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DiagnosticStylesheet {
|
||||
pub(crate) error: Style,
|
||||
@@ -84,7 +47,6 @@ pub struct DiagnosticStylesheet {
|
||||
pub(crate) deletion: Style,
|
||||
pub(crate) insertion_line_no: Style,
|
||||
pub(crate) deletion_line_no: Style,
|
||||
pub(crate) hyperlink: bool,
|
||||
}
|
||||
|
||||
impl Default for DiagnosticStylesheet {
|
||||
@@ -97,8 +59,6 @@ impl DiagnosticStylesheet {
|
||||
/// Default terminal styling
|
||||
pub fn styled() -> Self {
|
||||
let bright_blue = AnsiColor::BrightBlue.on_default();
|
||||
|
||||
let hyperlink = supports_hyperlinks::supports_hyperlinks();
|
||||
Self {
|
||||
error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
|
||||
warning: AnsiColor::Yellow.on_default().effects(Effects::BOLD),
|
||||
@@ -114,7 +74,6 @@ impl DiagnosticStylesheet {
|
||||
deletion: AnsiColor::Red.on_default(),
|
||||
insertion_line_no: AnsiColor::Green.on_default().effects(Effects::BOLD),
|
||||
deletion_line_no: AnsiColor::Red.on_default().effects(Effects::BOLD),
|
||||
hyperlink,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +93,6 @@ impl DiagnosticStylesheet {
|
||||
deletion: Style::new(),
|
||||
insertion_line_no: Style::new(),
|
||||
deletion_line_no: Style::new(),
|
||||
hyperlink: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,17 +470,6 @@ impl File {
|
||||
self.source_type(db).is_stub()
|
||||
}
|
||||
|
||||
/// Returns `true` if the file is an `__init__.pyi`
|
||||
pub fn is_package_stub(self, db: &dyn Db) -> bool {
|
||||
self.path(db).as_str().ends_with("__init__.pyi")
|
||||
}
|
||||
|
||||
/// Returns `true` if the file is an `__init__.pyi`
|
||||
pub fn is_package(self, db: &dyn Db) -> bool {
|
||||
let path = self.path(db).as_str();
|
||||
path.ends_with("__init__.pyi") || path.ends_with("__init__.py")
|
||||
}
|
||||
|
||||
pub fn source_type(self, db: &dyn Db) -> PySourceType {
|
||||
match self.path(db) {
|
||||
FilePath::System(path) => path
|
||||
|
||||
@@ -12,7 +12,6 @@ use std::hash::BuildHasherDefault;
|
||||
use std::num::NonZeroUsize;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
pub mod cancellation;
|
||||
pub mod diagnostic;
|
||||
pub mod display;
|
||||
pub mod file_revision;
|
||||
|
||||
@@ -21,11 +21,7 @@ use crate::source::source_text;
|
||||
/// reflected in the changed AST offsets.
|
||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
||||
/// for determining if a query result is unchanged.
|
||||
///
|
||||
/// The LRU capacity of 200 was picked without any empirical evidence that it's optimal,
|
||||
/// instead it's a wild guess that it should be unlikely that incremental changes involve
|
||||
/// more than 200 modules. Parsed ASTs within the same revision are never evicted by Salsa.
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size, lru=200)]
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
@@ -96,9 +92,14 @@ impl ParsedModule {
|
||||
self.inner.store(None);
|
||||
}
|
||||
|
||||
/// Returns the file to which this module belongs.
|
||||
pub fn file(&self) -> File {
|
||||
self.file
|
||||
/// Returns the pointer address of this [`ParsedModule`].
|
||||
///
|
||||
/// The pointer uniquely identifies the module within the current Salsa revision,
|
||||
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
|
||||
pub fn addr(&self) -> usize {
|
||||
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
|
||||
// `Arc` within the `ArcSwap` may change.
|
||||
Arc::as_ptr(&self.inner).addr()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ use ruff_source_file::LineIndex;
|
||||
|
||||
use crate::Db;
|
||||
use crate::files::{File, FilePath};
|
||||
use crate::system::System;
|
||||
|
||||
/// Reads the source text of a python text file (must be valid UTF8) or notebook.
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
@@ -16,7 +15,7 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
let _span = tracing::trace_span!("source_text", file = %path).entered();
|
||||
let mut read_error = None;
|
||||
|
||||
let kind = if is_notebook(db.system(), path) {
|
||||
let kind = if is_notebook(file.path(db)) {
|
||||
file.read_to_notebook(db)
|
||||
.unwrap_or_else(|error| {
|
||||
tracing::debug!("Failed to read notebook '{path}': {error}");
|
||||
@@ -41,17 +40,18 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_notebook(system: &dyn System, path: &FilePath) -> bool {
|
||||
let source_type = match path {
|
||||
FilePath::System(path) => system.source_type(path),
|
||||
FilePath::SystemVirtual(system_virtual) => system.virtual_path_source_type(system_virtual),
|
||||
FilePath::Vendored(_) => return false,
|
||||
};
|
||||
|
||||
let with_extension_fallback =
|
||||
source_type.or_else(|| PySourceType::try_from_extension(path.extension()?));
|
||||
|
||||
with_extension_fallback == Some(PySourceType::Ipynb)
|
||||
fn is_notebook(path: &FilePath) -> bool {
|
||||
match path {
|
||||
FilePath::System(system) => system.extension().is_some_and(|extension| {
|
||||
PySourceType::try_from_extension(extension) == Some(PySourceType::Ipynb)
|
||||
}),
|
||||
FilePath::SystemVirtual(system_virtual) => {
|
||||
system_virtual.extension().is_some_and(|extension| {
|
||||
PySourceType::try_from_extension(extension) == Some(PySourceType::Ipynb)
|
||||
})
|
||||
}
|
||||
FilePath::Vendored(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// The source text of a file containing python code.
|
||||
|
||||
@@ -9,7 +9,6 @@ pub use os::OsSystem;
|
||||
|
||||
use filetime::FileTime;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -17,11 +16,12 @@ use std::{fmt, io};
|
||||
pub use test::{DbWithTestSystem, DbWithWritableSystem, InMemorySystem, TestSystem};
|
||||
use walk_directory::WalkDirectoryBuilder;
|
||||
|
||||
use crate::file_revision::FileRevision;
|
||||
|
||||
pub use self::path::{
|
||||
DeduplicatedNestedPathsIter, SystemPath, SystemPathBuf, SystemVirtualPath,
|
||||
SystemVirtualPathBuf, deduplicate_nested_paths,
|
||||
};
|
||||
use crate::file_revision::FileRevision;
|
||||
|
||||
mod memory_fs;
|
||||
#[cfg(feature = "os")]
|
||||
@@ -66,35 +66,6 @@ pub trait System: Debug + Sync + Send {
|
||||
/// See [dunce::canonicalize] for more information.
|
||||
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
|
||||
|
||||
/// Returns the source type for `path` if known or `None`.
|
||||
///
|
||||
/// The default is to always return `None`, assuming the system
|
||||
/// has no additional information and that the caller should
|
||||
/// rely on the file extension instead.
|
||||
///
|
||||
/// This is primarily used for the LSP integration to respect
|
||||
/// the chosen language (or the fact that it is a notebook) in
|
||||
/// the editor.
|
||||
fn source_type(&self, path: &SystemPath) -> Option<PySourceType> {
|
||||
let _ = path;
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the source type for `path` if known or `None`.
|
||||
///
|
||||
/// The default is to always return `None`, assuming the system
|
||||
/// has no additional information and that the caller should
|
||||
/// rely on the file extension instead.
|
||||
///
|
||||
/// This is primarily used for the LSP integration to respect
|
||||
/// the chosen language (or the fact that it is a notebook) in
|
||||
/// the editor.
|
||||
fn virtual_path_source_type(&self, path: &SystemVirtualPath) -> Option<PySourceType> {
|
||||
let _ = path;
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Reads the content of the file at `path` into a [`String`].
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
|
||||
|
||||
|
||||
@@ -200,12 +200,7 @@ impl System for OsSystem {
|
||||
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
|
||||
/// when setting [`WalkDirectoryBuilder::standard_filters`] to true.
|
||||
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
|
||||
WalkDirectoryBuilder::new(
|
||||
path,
|
||||
OsDirectoryWalker {
|
||||
cwd: self.current_directory().to_path_buf(),
|
||||
},
|
||||
)
|
||||
WalkDirectoryBuilder::new(path, OsDirectoryWalker {})
|
||||
}
|
||||
|
||||
fn glob(
|
||||
@@ -275,16 +270,16 @@ impl OsSystem {
|
||||
/// instead of at least one system call for each component between `path` and `prefix`.
|
||||
///
|
||||
/// However, using `canonicalize` to resolve the path's casing doesn't work in two cases:
|
||||
/// * if `path` is a symlink, `canonicalize` returns the symlink's target and not the symlink's source path.
|
||||
/// * on Windows: If `path` is a mapped network drive, `canonicalize` returns the UNC path
|
||||
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` returns `\\?\UNC\server\share`).
|
||||
/// * if `path` is a symlink because `canonicalize` then returns the symlink's target and not the symlink's source path.
|
||||
/// * on Windows: If `path` is a mapped network drive because `canonicalize` then returns the UNC path
|
||||
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` then returns `\\?\UNC\server\share`).
|
||||
///
|
||||
/// Symlinks and mapped network drives should be rare enough that this fast path is worth trying first,
|
||||
/// even if it comes at a cost for those rare use cases.
|
||||
fn path_exists_case_sensitive_fast(&self, path: &SystemPath) -> Option<bool> {
|
||||
// This is a more forgiving version of `dunce::simplified` that removes all `\\?\` prefixes on Windows.
|
||||
// We use this more forgiving version because we don't intend on using either path for anything other than comparison
|
||||
// and the prefix is only relevant when passing the path to other programs and it's longer than 200 something
|
||||
// and the prefix is only relevant when passing the path to other programs and its longer than 200 something
|
||||
// characters.
|
||||
fn simplify_ignore_verbatim(path: &SystemPath) -> &SystemPath {
|
||||
if cfg!(windows) {
|
||||
@@ -298,7 +293,9 @@ impl OsSystem {
|
||||
}
|
||||
}
|
||||
|
||||
let Ok(canonicalized) = path.as_std_path().canonicalize() else {
|
||||
let simplified = simplify_ignore_verbatim(path);
|
||||
|
||||
let Ok(canonicalized) = simplified.as_std_path().canonicalize() else {
|
||||
// The path doesn't exist or can't be accessed. The path doesn't exist.
|
||||
return Some(false);
|
||||
};
|
||||
@@ -307,13 +304,12 @@ impl OsSystem {
|
||||
// The original path is valid UTF8 but the canonicalized path isn't. This definitely suggests
|
||||
// that a symlink is involved. Fall back to the slow path.
|
||||
tracing::debug!(
|
||||
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{path}` is not valid UTF-8"
|
||||
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{simplified}` is not valid UTF-8"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let simplified_canonicalized = simplify_ignore_verbatim(&canonicalized);
|
||||
let simplified = simplify_ignore_verbatim(path);
|
||||
|
||||
// Test if the paths differ by anything other than casing. If so, that suggests that
|
||||
// `path` pointed to a symlink (or some other none reversible path normalization happened).
|
||||
@@ -458,9 +454,7 @@ struct ListedDirectory {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OsDirectoryWalker {
|
||||
cwd: SystemPathBuf,
|
||||
}
|
||||
struct OsDirectoryWalker;
|
||||
|
||||
impl DirectoryWalker for OsDirectoryWalker {
|
||||
fn walk(
|
||||
@@ -479,7 +473,6 @@ impl DirectoryWalker for OsDirectoryWalker {
|
||||
};
|
||||
|
||||
let mut builder = ignore::WalkBuilder::new(first.as_std_path());
|
||||
builder.current_dir(self.cwd.as_std_path());
|
||||
|
||||
builder.standard_filters(standard_filters);
|
||||
builder.hidden(hidden);
|
||||
|
||||
@@ -667,13 +667,6 @@ impl Deref for SystemPathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for SystemPathBuf {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<SystemPath>> FromIterator<P> for SystemPathBuf {
|
||||
fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
|
||||
let mut buf = SystemPathBuf::new();
|
||||
@@ -730,11 +723,10 @@ impl ruff_cache::CacheKey for SystemPathBuf {
|
||||
|
||||
/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
|
||||
#[repr(transparent)]
|
||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub struct SystemVirtualPath(str);
|
||||
|
||||
impl SystemVirtualPath {
|
||||
pub const fn new(path: &str) -> &SystemVirtualPath {
|
||||
pub fn new(path: &str) -> &SystemVirtualPath {
|
||||
// SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
|
||||
// *const str to a *const SystemVirtualPath is valid.
|
||||
unsafe { &*(path as *const str as *const SystemVirtualPath) }
|
||||
@@ -775,8 +767,8 @@ pub struct SystemVirtualPathBuf(String);
|
||||
|
||||
impl SystemVirtualPathBuf {
|
||||
#[inline]
|
||||
pub const fn as_path(&self) -> &SystemVirtualPath {
|
||||
SystemVirtualPath::new(self.0.as_str())
|
||||
pub fn as_path(&self) -> &SystemVirtualPath {
|
||||
SystemVirtualPath::new(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,12 +852,6 @@ impl ruff_cache::CacheKey for SystemVirtualPathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<SystemVirtualPath> for SystemVirtualPathBuf {
|
||||
fn borrow(&self) -> &SystemVirtualPath {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Deduplicates identical paths and removes nested paths.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use glob::PatternError;
|
||||
use ruff_notebook::{Notebook, NotebookError};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -21,44 +20,18 @@ use super::walk_directory::WalkDirectoryBuilder;
|
||||
///
|
||||
/// ## Warning
|
||||
/// Don't use this system for production code. It's intended for testing only.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestSystem {
|
||||
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||
/// Environment variable overrides. If a key is present here, it takes precedence
|
||||
/// over the inner system's environment variables.
|
||||
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
|
||||
}
|
||||
|
||||
impl Clone for TestSystem {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
env_overrides: self.env_overrides.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSystem {
|
||||
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(inner),
|
||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an environment variable override. This takes precedence over the inner system.
|
||||
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
|
||||
self.env_overrides
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(name.into(), Some(value.into()));
|
||||
}
|
||||
|
||||
/// Removes an environment variable override, making it appear as not set.
|
||||
pub fn remove_env_var(&self, name: impl Into<String>) {
|
||||
self.env_overrides.lock().unwrap().insert(name.into(), None);
|
||||
}
|
||||
|
||||
/// Returns the [`InMemorySystem`].
|
||||
///
|
||||
/// ## Panics
|
||||
@@ -174,18 +147,6 @@ impl System for TestSystem {
|
||||
self.system().case_sensitivity()
|
||||
}
|
||||
|
||||
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
||||
// Check overrides first
|
||||
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
|
||||
return match override_value {
|
||||
Some(value) => Ok(value.clone()),
|
||||
None => Err(std::env::VarError::NotPresent),
|
||||
};
|
||||
}
|
||||
// Fall back to inner system
|
||||
self.system().env_var(name)
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn System> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
@@ -195,7 +156,6 @@ impl Default for TestSystem {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InMemorySystem::default()),
|
||||
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use regex::{Captures, Regex};
|
||||
use ruff_linter::codes::RuleGroup;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_linter::FixAvailability;
|
||||
@@ -32,47 +31,6 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
|
||||
let _ = writeln!(&mut output, "# {} ({})", rule.name(), rule.noqa_code());
|
||||
|
||||
let status_text = match rule.group() {
|
||||
RuleGroup::Stable { since } => {
|
||||
format!(
|
||||
r#"Added in <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>"#
|
||||
)
|
||||
}
|
||||
RuleGroup::Preview { since } => {
|
||||
format!(
|
||||
r#"Preview (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
RuleGroup::Deprecated { since } => {
|
||||
format!(
|
||||
r#"Deprecated (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
RuleGroup::Removed { since } => {
|
||||
format!(
|
||||
r#"Removed (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20(%27{encoded_name}%27%20OR%20{rule_code})" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
"#,
|
||||
encoded_name =
|
||||
url::form_urlencoded::byte_serialize(rule.name().as_str().as_bytes())
|
||||
.collect::<String>(),
|
||||
rule_code = rule.noqa_code(),
|
||||
file =
|
||||
url::form_urlencoded::byte_serialize(rule.file().replace('\\', "/").as_bytes())
|
||||
.collect::<String>(),
|
||||
line = rule.line(),
|
||||
);
|
||||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
||||
if linter.url().is_some() {
|
||||
let common_prefix: String = match linter.common_prefix() {
|
||||
|
||||
@@ -62,7 +62,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||
generate_set(
|
||||
output,
|
||||
Set::Named {
|
||||
name: set_name.clone(),
|
||||
name: set_name.to_string(),
|
||||
set: *sub_set,
|
||||
},
|
||||
parents,
|
||||
|
||||
@@ -32,24 +32,20 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
table_out.push('\n');
|
||||
for rule in rules {
|
||||
let status_token = match rule.group() {
|
||||
RuleGroup::Removed { since } => {
|
||||
RuleGroup::Removed => {
|
||||
format!(
|
||||
"<span {SYMBOL_STYLE} title='Rule was removed in {since}'>{REMOVED_SYMBOL}</span>"
|
||||
"<span {SYMBOL_STYLE} title='Rule has been removed'>{REMOVED_SYMBOL}</span>"
|
||||
)
|
||||
}
|
||||
RuleGroup::Deprecated { since } => {
|
||||
RuleGroup::Deprecated => {
|
||||
format!(
|
||||
"<span {SYMBOL_STYLE} title='Rule has been deprecated since {since}'>{WARNING_SYMBOL}</span>"
|
||||
"<span {SYMBOL_STYLE} title='Rule has been deprecated'>{WARNING_SYMBOL}</span>"
|
||||
)
|
||||
}
|
||||
RuleGroup::Preview { since } => {
|
||||
format!(
|
||||
"<span {SYMBOL_STYLE} title='Rule has been in preview since {since}'>{PREVIEW_SYMBOL}</span>"
|
||||
)
|
||||
}
|
||||
RuleGroup::Stable { since } => {
|
||||
format!("<span {SYMBOL_STYLE} title='Rule has been stable since {since}'></span>")
|
||||
RuleGroup::Preview => {
|
||||
format!("<span {SYMBOL_STYLE} title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
}
|
||||
RuleGroup::Stable => format!("<span {SYMBOL_STYLE}></span>"),
|
||||
};
|
||||
|
||||
let fix_token = match rule.fixable() {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use anyhow::bail;
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::StrComparison;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ruff_python_trivia::textwrap;
|
||||
use ty_project::metadata::Options;
|
||||
|
||||
use crate::{
|
||||
@@ -106,7 +104,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
|
||||
generate_set(
|
||||
output,
|
||||
Set::Named {
|
||||
name: set_name.clone(),
|
||||
name: set_name.to_string(),
|
||||
set: *sub_set,
|
||||
},
|
||||
parents,
|
||||
@@ -146,8 +144,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
output.push('\n');
|
||||
|
||||
if let Some(deprecated) = &field.deprecated {
|
||||
output.push_str("!!! warning \"Deprecated\"\n");
|
||||
output.push_str(" This option has been deprecated");
|
||||
output.push_str("> [!WARN] \"Deprecated\"\n");
|
||||
output.push_str("> This option has been deprecated");
|
||||
|
||||
if let Some(since) = deprecated.since {
|
||||
write!(output, " in {since}").unwrap();
|
||||
@@ -167,69 +165,61 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
let _ = writeln!(output, "**Default value**: `{}`", field.default);
|
||||
output.push('\n');
|
||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||
|
||||
output.push('\n');
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
|
||||
output.push_str(&format_example(
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::PyprojectToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
for configuration_file in [ConfigurationFile::PyprojectToml, ConfigurationFile::TyToml] {
|
||||
let (header, example) =
|
||||
format_snippet(field.scope, field.example, parents, configuration_file);
|
||||
output.push_str(&format_tab(configuration_file.name(), &header, &example));
|
||||
|
||||
output.push('\n');
|
||||
fn format_example(header: &str, content: &str) -> String {
|
||||
if header.is_empty() {
|
||||
format!("```toml\n{content}\n```\n",)
|
||||
} else {
|
||||
format!("```toml\n{header}\n{content}\n```\n",)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||
let header = if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\n {header}")
|
||||
};
|
||||
format!(
|
||||
"=== \"{}\"\n\n ```toml{}\n{}\n ```\n",
|
||||
tab_name,
|
||||
header,
|
||||
textwrap::indent(content, " ")
|
||||
)
|
||||
}
|
||||
/// Format the TOML header for the example usage for a given option.
|
||||
///
|
||||
/// For example: `[tool.ty.rules]`.
|
||||
fn format_snippet<'a>(
|
||||
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
|
||||
fn format_header(
|
||||
scope: Option<&str>,
|
||||
example: &'a str,
|
||||
example: &str,
|
||||
parents: &[Set],
|
||||
configuration: ConfigurationFile,
|
||||
) -> (String, Cow<'a, str>) {
|
||||
let mut example = Cow::Borrowed(example);
|
||||
) -> String {
|
||||
let tool_parent = match configuration {
|
||||
ConfigurationFile::PyprojectToml => Some("tool.ty"),
|
||||
ConfigurationFile::TyToml => None,
|
||||
};
|
||||
|
||||
let header = configuration
|
||||
.parent_table()
|
||||
let header = tool_parent
|
||||
.into_iter()
|
||||
.chain(parents.iter().filter_map(|parent| parent.name()))
|
||||
.chain(scope)
|
||||
.join(".");
|
||||
|
||||
// Rewrite examples starting with `[tool.ty]` or `[[tool.ty]]` to their `ty.toml` equivalent.
|
||||
if matches!(configuration, ConfigurationFile::TyToml) {
|
||||
example = example.replace("[tool.ty.", "[").into();
|
||||
}
|
||||
|
||||
// Ex) `[[tool.ty.xx]]`
|
||||
if example.starts_with(&format!("[[{header}")) {
|
||||
return (String::new(), example);
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Ex) `[tool.ty.rules]`
|
||||
if example.starts_with(&format!("[{header}")) {
|
||||
return (String::new(), example);
|
||||
return String::new();
|
||||
}
|
||||
|
||||
if header.is_empty() {
|
||||
(String::new(), example)
|
||||
String::new()
|
||||
} else {
|
||||
(format!("[{header}]"), example)
|
||||
format!("[{header}]")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,25 +242,10 @@ impl Visit for CollectOptionsVisitor {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ConfigurationFile {
|
||||
PyprojectToml,
|
||||
#[expect(dead_code)]
|
||||
TyToml,
|
||||
}
|
||||
|
||||
impl ConfigurationFile {
|
||||
const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::PyprojectToml => "pyproject.toml",
|
||||
Self::TyToml => "ty.toml",
|
||||
}
|
||||
}
|
||||
|
||||
const fn parent_table(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::PyprojectToml => Some("tool.ty"),
|
||||
Self::TyToml => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -63,7 +63,12 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(&mut output, "# Rules\n");
|
||||
|
||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||
lints.sort_by_key(|a| a.name());
|
||||
lints.sort_by(|a, b| {
|
||||
a.default_level()
|
||||
.cmp(&b.default_level())
|
||||
.reverse()
|
||||
.then_with(|| a.name().cmp(&b.name()))
|
||||
});
|
||||
|
||||
for lint in lints {
|
||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||
@@ -114,7 +119,7 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Edit {
|
||||
|
||||
/// Creates an edit that replaces the content in `range` with `content`.
|
||||
pub fn range_replacement(content: String, range: TextRange) -> Self {
|
||||
debug_assert!(!content.is_empty(), "Prefer `Edit::deletion`");
|
||||
debug_assert!(!content.is_empty(), "Prefer `Fix::deletion`");
|
||||
|
||||
Self {
|
||||
content: Some(Box::from(content)),
|
||||
|
||||
@@ -149,10 +149,6 @@ impl Fix {
|
||||
&self.edits
|
||||
}
|
||||
|
||||
pub fn into_edits(self) -> Vec<Edit> {
|
||||
self.edits
|
||||
}
|
||||
|
||||
/// Return the [`Applicability`] of the [`Fix`].
|
||||
pub fn applicability(&self) -> Applicability {
|
||||
self.applicability
|
||||
|
||||
@@ -1006,7 +1006,7 @@ impl<Context> std::fmt::Debug for Align<'_, Context> {
|
||||
/// Block indents indent a block of code, such as in a function body, and therefore insert a line
|
||||
/// break before and after the content.
|
||||
///
|
||||
/// Doesn't create an indentation if the passed in content is empty.
|
||||
/// Doesn't create an indentation if the passed in content is [`FormatElement.is_empty`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user