Compare commits
23 Commits
pythonplus
...
zb/pygrep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b75864c5f9 | ||
|
|
4a5d711a6e | ||
|
|
204c000e20 | ||
|
|
27f2b3af5d | ||
|
|
4d5d889055 | ||
|
|
427a05b1b5 | ||
|
|
7e763cf84d | ||
|
|
e5bf2301bb | ||
|
|
c7e40e6dc1 | ||
|
|
761e148308 | ||
|
|
5c75da980f | ||
|
|
747728beca | ||
|
|
897b7f75b9 | ||
|
|
07c6a3e672 | ||
|
|
57576fa139 | ||
|
|
b1c793e2ad | ||
|
|
9fca4d830e | ||
|
|
d318a27b0f | ||
|
|
002d040c41 | ||
|
|
432b4da27b | ||
|
|
7992caed37 | ||
|
|
4c8d1fe0e4 | ||
|
|
8e8c632409 |
@@ -1,8 +0,0 @@
|
|||||||
[profile.ci]
|
|
||||||
# Print out output for failing tests as soon as they fail, and also at the end
|
|
||||||
# of the run (for easy scrollability).
|
|
||||||
failure-output = "immediate-final"
|
|
||||||
# Do not cancel the test run on the first failure.
|
|
||||||
fail-fast = false
|
|
||||||
|
|
||||||
status-level = "skip"
|
|
||||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
|
crates/ruff_linter/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
|
||||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
||||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_2.py text eol=crlf
|
|
||||||
crates/ruff_linter/resources/test/fixtures/pycodestyle/W391_3.py text eol=crlf
|
|
||||||
|
|
||||||
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples_crlf.py text eol=crlf
|
||||||
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples_crlf.py.snap text eol=crlf
|
||||||
|
|||||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -7,9 +7,3 @@
|
|||||||
|
|
||||||
# Jupyter
|
# Jupyter
|
||||||
/crates/ruff_linter/src/jupyter/ @dhruvmanila
|
/crates/ruff_linter/src/jupyter/ @dhruvmanila
|
||||||
/crates/ruff_formatter/ @MichaReiser
|
|
||||||
/crates/ruff_python_formatter/ @MichaReiser
|
|
||||||
/crates/ruff_python_parser/ @MichaReiser
|
|
||||||
|
|
||||||
# flake8-pyi
|
|
||||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -3,8 +3,6 @@ Thank you for taking the time to report an issue! We're glad to have you involve
|
|||||||
|
|
||||||
If you're filing a bug report, please consider including the following information:
|
If you're filing a bug report, please consider including the following information:
|
||||||
|
|
||||||
* List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
|
|
||||||
e.g. "RUF001", "unused variable", "Jupyter notebook"
|
|
||||||
* A minimal code snippet that reproduces the bug.
|
* A minimal code snippet that reproduces the bug.
|
||||||
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
* The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
||||||
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
* The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
||||||
|
|||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -9,10 +9,6 @@ updates:
|
|||||||
actions:
|
actions:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
ignore:
|
|
||||||
# The latest versions of these are not compatible with our release workflow
|
|
||||||
- dependency-name: "actions/upload-artifact"
|
|
||||||
- dependency-name: "actions/download-artifact"
|
|
||||||
|
|
||||||
- package-ecosystem: "cargo"
|
- package-ecosystem: "cargo"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
|||||||
40
.github/workflows/ci.yaml
vendored
40
.github/workflows/ci.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: tj-actions/changed-files@v42
|
- uses: tj-actions/changed-files@v41
|
||||||
id: changed
|
id: changed
|
||||||
with:
|
with:
|
||||||
files_yaml: |
|
files_yaml: |
|
||||||
@@ -111,29 +111,19 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install mold"
|
|
||||||
uses: rui314/setup-mold@v1
|
|
||||||
- name: "Install cargo nextest"
|
|
||||||
uses: taiki-e/install-action@v2
|
|
||||||
with:
|
|
||||||
tool: cargo-nextest
|
|
||||||
- name: "Install cargo insta"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@v2
|
uses: taiki-e/install-action@v2
|
||||||
with:
|
with:
|
||||||
tool: cargo-insta
|
tool: cargo-insta
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
shell: bash
|
run: cargo insta test --all --all-features --unreferenced reject
|
||||||
env:
|
|
||||||
NEXTEST_PROFILE: "ci"
|
|
||||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
|
||||||
|
|
||||||
# Check for broken links in the documentation.
|
# Check for broken links in the documentation.
|
||||||
- run: cargo doc --all --no-deps
|
- run: cargo doc --all --no-deps
|
||||||
env:
|
env:
|
||||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||||
RUSTDOCFLAGS: "-D warnings"
|
RUSTDOCFLAGS: "-D warnings"
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ruff
|
name: ruff
|
||||||
path: target/debug/ruff
|
path: target/debug/ruff
|
||||||
@@ -148,16 +138,15 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Install cargo nextest"
|
- name: "Install cargo insta"
|
||||||
uses: taiki-e/install-action@v2
|
uses: taiki-e/install-action@v2
|
||||||
with:
|
with:
|
||||||
tool: cargo-nextest
|
tool: cargo-insta
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: "Run tests"
|
- name: "Run tests"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
||||||
cargo nextest run --all-features --profile ci
|
run: cargo insta test --all --all-features
|
||||||
cargo test --all-features --doc
|
|
||||||
|
|
||||||
cargo-test-wasm:
|
cargo-test-wasm:
|
||||||
name: "cargo test (wasm)"
|
name: "cargo test (wasm)"
|
||||||
@@ -238,7 +227,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v3
|
||||||
name: Download comparison Ruff binary
|
name: Download comparison Ruff binary
|
||||||
id: ruff-target
|
id: ruff-target
|
||||||
with:
|
with:
|
||||||
@@ -250,7 +239,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: ruff
|
name: ruff
|
||||||
branch: ${{ github.event.pull_request.base.ref }}
|
branch: ${{ github.event.pull_request.base.ref }}
|
||||||
workflow: "ci.yaml"
|
|
||||||
check_artifacts: true
|
check_artifacts: true
|
||||||
|
|
||||||
- name: Install ruff-ecosystem
|
- name: Install ruff-ecosystem
|
||||||
@@ -325,13 +313,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo ${{ github.event.number }} > pr-number
|
echo ${{ github.event.number }} > pr-number
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v3
|
||||||
name: Upload PR Number
|
name: Upload PR Number
|
||||||
with:
|
with:
|
||||||
name: pr-number
|
name: pr-number
|
||||||
path: pr-number
|
path: pr-number
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v3
|
||||||
name: Upload Results
|
name: Upload Results
|
||||||
with:
|
with:
|
||||||
name: ecosystem-result
|
name: ecosystem-result
|
||||||
@@ -394,7 +382,7 @@ jobs:
|
|||||||
- name: "Install pre-commit"
|
- name: "Install pre-commit"
|
||||||
run: pip install pre-commit
|
run: pip install pre-commit
|
||||||
- name: "Cache pre-commit"
|
- name: "Cache pre-commit"
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pre-commit
|
path: ~/.cache/pre-commit
|
||||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
@@ -419,7 +407,7 @@ jobs:
|
|||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
- name: "Add SSH key"
|
- name: "Add SSH key"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||||
uses: webfactory/ssh-agent@v0.9.0
|
uses: webfactory/ssh-agent@v0.8.0
|
||||||
with:
|
with:
|
||||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
@@ -472,7 +460,7 @@ jobs:
|
|||||||
- determine_changes
|
- determine_changes
|
||||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: extractions/setup-just@v2
|
- uses: extractions/setup-just@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -485,7 +473,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v3
|
||||||
name: Download development ruff binary
|
name: Download development ruff binary
|
||||||
id: ruff-target
|
id: ruff-target
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
- name: "Add SSH key"
|
- name: "Add SSH key"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||||
uses: webfactory/ssh-agent@v0.9.0
|
uses: webfactory/ssh-agent@v0.8.0
|
||||||
with:
|
with:
|
||||||
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
|
|||||||
4
.github/workflows/pr-comment.yaml
vendored
4
.github/workflows/pr-comment.yaml
vendored
@@ -61,7 +61,7 @@ jobs:
|
|||||||
echo 'EOF' >> $GITHUB_OUTPUT
|
echo 'EOF' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Find existing comment
|
- name: Find existing comment
|
||||||
uses: peter-evans/find-comment@v3
|
uses: peter-evans/find-comment@v2
|
||||||
if: steps.generate-comment.outcome == 'success'
|
if: steps.generate-comment.outcome == 'success'
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create or update comment
|
- name: Create or update comment
|
||||||
if: steps.find-comment.outcome == 'success'
|
if: steps.find-comment.outcome == 'success'
|
||||||
uses: peter-evans/create-or-update-comment@v4
|
uses: peter-evans/create-or-update-comment@v3
|
||||||
with:
|
with:
|
||||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||||
|
|||||||
76
.github/workflows/release.yaml
vendored
76
.github/workflows/release.yaml
vendored
@@ -52,9 +52,9 @@ jobs:
|
|||||||
ruff --help
|
ruff --help
|
||||||
python -m ruff --help
|
python -m ruff --help
|
||||||
- name: "Upload sdist"
|
- name: "Upload sdist"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-sdist
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
macos-x86_64:
|
macos-x86_64:
|
||||||
@@ -80,9 +80,9 @@ jobs:
|
|||||||
ruff --help
|
ruff --help
|
||||||
python -m ruff --help
|
python -m ruff --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-macos-x86_64
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
run: |
|
run: |
|
||||||
@@ -90,9 +90,9 @@ jobs:
|
|||||||
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
|
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
|
||||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-macos-x86_64
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -119,9 +119,9 @@ jobs:
|
|||||||
ruff --help
|
ruff --help
|
||||||
python -m ruff --help
|
python -m ruff --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-aarch64-apple-darwin
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
run: |
|
run: |
|
||||||
@@ -129,9 +129,9 @@ jobs:
|
|||||||
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
|
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
|
||||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-aarch64-apple-darwin
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -170,9 +170,9 @@ jobs:
|
|||||||
ruff --help
|
ruff --help
|
||||||
python -m ruff --help
|
python -m ruff --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-${{ matrix.platform.target }}
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -181,9 +181,9 @@ jobs:
|
|||||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
||||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-${{ matrix.platform.target }}
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.zip
|
*.zip
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -218,9 +218,9 @@ jobs:
|
|||||||
ruff --help
|
ruff --help
|
||||||
python -m ruff --help
|
python -m ruff --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-${{ matrix.target }}
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
run: |
|
run: |
|
||||||
@@ -228,9 +228,9 @@ jobs:
|
|||||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-${{ matrix.target }}
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -251,12 +251,8 @@ jobs:
|
|||||||
arch: s390x
|
arch: s390x
|
||||||
- target: powerpc64le-unknown-linux-gnu
|
- target: powerpc64le-unknown-linux-gnu
|
||||||
arch: ppc64le
|
arch: ppc64le
|
||||||
# see https://github.com/astral-sh/ruff/issues/10073
|
|
||||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
|
||||||
- target: powerpc64-unknown-linux-gnu
|
- target: powerpc64-unknown-linux-gnu
|
||||||
arch: ppc64
|
arch: ppc64
|
||||||
# see https://github.com/astral-sh/ruff/issues/10073
|
|
||||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -289,9 +285,9 @@ jobs:
|
|||||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||||
ruff --help
|
ruff --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-${{ matrix.platform.target }}
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
run: |
|
run: |
|
||||||
@@ -299,9 +295,9 @@ jobs:
|
|||||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-${{ matrix.platform.target }}
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -341,9 +337,9 @@ jobs:
|
|||||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||||
.venv/bin/ruff check --help
|
.venv/bin/ruff check --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-${{ matrix.target }}
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
run: |
|
run: |
|
||||||
@@ -351,9 +347,9 @@ jobs:
|
|||||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-${{ matrix.target }}
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -398,9 +394,9 @@ jobs:
|
|||||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||||
.venv/bin/ruff check --help
|
.venv/bin/ruff check --help
|
||||||
- name: "Upload wheels"
|
- name: "Upload wheels"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: wheels-${{ matrix.platform.target }}
|
name: wheels
|
||||||
path: dist
|
path: dist
|
||||||
- name: "Archive binary"
|
- name: "Archive binary"
|
||||||
run: |
|
run: |
|
||||||
@@ -408,9 +404,9 @@ jobs:
|
|||||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||||
- name: "Upload binary"
|
- name: "Upload binary"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: binaries-${{ matrix.platform.target }}
|
name: binaries
|
||||||
path: |
|
path: |
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.sha256
|
*.sha256
|
||||||
@@ -467,11 +463,10 @@ jobs:
|
|||||||
# For pypi trusted publishing
|
# For pypi trusted publishing
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
pattern: wheels-*
|
name: wheels
|
||||||
path: wheels
|
path: wheels
|
||||||
merge-multiple: true
|
|
||||||
- name: Publish to PyPi
|
- name: Publish to PyPi
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
@@ -511,13 +506,12 @@ jobs:
|
|||||||
# For GitHub release publishing
|
# For GitHub release publishing
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
pattern: binaries-*
|
name: binaries
|
||||||
path: binaries
|
path: binaries
|
||||||
merge-multiple: true
|
|
||||||
- name: "Publish to GitHub"
|
- name: "Publish to GitHub"
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: binaries/*
|
files: binaries/*
|
||||||
|
|||||||
@@ -1,56 +1,5 @@
|
|||||||
# Breaking Changes
|
# Breaking Changes
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
### Ruff 2024.2 style
|
|
||||||
|
|
||||||
The formatter now formats code according to the Ruff 2024.2 style guide. Read the [changelog](./CHANGELOG.md#030) for a detailed list of stabilized style changes.
|
|
||||||
|
|
||||||
### `isort`: Use one blank line after imports in typing stub files ([#9971](https://github.com/astral-sh/ruff/pull/9971))
|
|
||||||
|
|
||||||
Previously, Ruff used one or two blank lines (or the number configured by `isort.lines-after-imports`) after imports in typing stub files (`.pyi` files).
|
|
||||||
The [typing style guide for stubs](https://typing.readthedocs.io/en/latest/source/stubs.html#style-guide) recommends using at most 1 blank line for grouping.
|
|
||||||
As of this release, `isort` now always uses one blank line after imports in stub files, the same as the formatter.
|
|
||||||
|
|
||||||
### `build` is no longer excluded by default ([#10093](https://github.com/astral-sh/ruff/pull/10093))
|
|
||||||
|
|
||||||
Ruff maintains a list of directories and files that are excluded by default. This list now consists of the following patterns:
|
|
||||||
|
|
||||||
- `.bzr`
|
|
||||||
- `.direnv`
|
|
||||||
- `.eggs`
|
|
||||||
- `.git`
|
|
||||||
- `.git-rewrite`
|
|
||||||
- `.hg`
|
|
||||||
- `.ipynb_checkpoints`
|
|
||||||
- `.mypy_cache`
|
|
||||||
- `.nox`
|
|
||||||
- `.pants.d`
|
|
||||||
- `.pyenv`
|
|
||||||
- `.pytest_cache`
|
|
||||||
- `.pytype`
|
|
||||||
- `.ruff_cache`
|
|
||||||
- `.svn`
|
|
||||||
- `.tox`
|
|
||||||
- `.venv`
|
|
||||||
- `.vscode`
|
|
||||||
- `__pypackages__`
|
|
||||||
- `_build`
|
|
||||||
- `buck-out`
|
|
||||||
- `dist`
|
|
||||||
- `node_modules`
|
|
||||||
- `site-packages`
|
|
||||||
- `venv`
|
|
||||||
|
|
||||||
Previously, the `build` directory was included in this list. However, the `build` directory tends to be a not-unpopular directory
|
|
||||||
name, and excluding it by default caused confusion. Ruff now no longer excludes `build` except if it is excluded by a `.gitignore` file
|
|
||||||
or because it is listed in `extend-exclude`.
|
|
||||||
|
|
||||||
### `--format` is no longer a valid `rule` or `linter` command option
|
|
||||||
|
|
||||||
Previously, `ruff rule` and `ruff linter` accepted the `--format <FORMAT>` option as an alias for `--output-format`. Ruff no longer
|
|
||||||
supports this alias. Please use `ruff rule --output-format <FORMAT>` and `ruff linter --output-format <FORMAT>` instead.
|
|
||||||
|
|
||||||
## 0.1.9
|
## 0.1.9
|
||||||
|
|
||||||
### `site-packages` is now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
### `site-packages` is now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
||||||
|
|||||||
391
CHANGELOG.md
391
CHANGELOG.md
@@ -1,396 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.3.2
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- Improve single-`with` item formatting for Python 3.8 or older ([#10276](https://github.com/astral-sh/ruff/pull/10276))
|
|
||||||
|
|
||||||
### Rule changes
|
|
||||||
|
|
||||||
- \[`pyupgrade`\] Allow fixes for f-string rule regardless of line length (`UP032`) ([#10263](https://github.com/astral-sh/ruff/pull/10263))
|
|
||||||
- \[`pycodestyle`\] Include actual conditions in E712 diagnostics ([#10254](https://github.com/astral-sh/ruff/pull/10254))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- Fix trailing kwargs end of line comment after slash ([#10297](https://github.com/astral-sh/ruff/pull/10297))
|
|
||||||
- Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274))
|
|
||||||
- Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265))
|
|
||||||
- Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280))
|
|
||||||
- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283))
|
|
||||||
- Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285))
|
|
||||||
|
|
||||||
## 0.3.1
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- \[`pycodestyle`\] Fix E301 not triggering on decorated methods. ([#10117](https://github.com/astral-sh/ruff/pull/10117))
|
|
||||||
- \[`pycodestyle`\] Respect `isort` settings in blank line rules (`E3*`) ([#10096](https://github.com/astral-sh/ruff/pull/10096))
|
|
||||||
- \[`pycodestyle`\] Make blank lines in typing stub files optional (`E3*`) ([#10098](https://github.com/astral-sh/ruff/pull/10098))
|
|
||||||
- \[`pylint`\] Implement `singledispatch-method` (`E1519`) ([#10140](https://github.com/astral-sh/ruff/pull/10140))
|
|
||||||
- \[`pylint`\] Implement `useless-exception-statement` (`W0133`) ([#10176](https://github.com/astral-sh/ruff/pull/10176))
|
|
||||||
|
|
||||||
### Rule changes
|
|
||||||
|
|
||||||
- \[`flake8-debugger`\] Check for use of `debugpy` and `ptvsd` debug modules (#10177) ([#10194](https://github.com/astral-sh/ruff/pull/10194))
|
|
||||||
- \[`pyupgrade`\] Generate diagnostic for all valid f-string conversions regardless of line length (`UP032`) ([#10238](https://github.com/astral-sh/ruff/pull/10238))
|
|
||||||
- \[`pep8_naming`\] Add fixes for `N804` and `N805` ([#10215](https://github.com/astral-sh/ruff/pull/10215))
|
|
||||||
|
|
||||||
### CLI
|
|
||||||
|
|
||||||
- Colorize the output of `ruff format --diff` ([#10110](https://github.com/astral-sh/ruff/pull/10110))
|
|
||||||
- Make `--config` and `--isolated` global flags ([#10150](https://github.com/astral-sh/ruff/pull/10150))
|
|
||||||
- Correctly expand tildes and environment variables in paths passed to `--config` ([#10219](https://github.com/astral-sh/ruff/pull/10219))
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
- Accept a PEP 440 version specifier for `required-version` ([#10216](https://github.com/astral-sh/ruff/pull/10216))
|
|
||||||
- Implement isort's `default-section` setting ([#10149](https://github.com/astral-sh/ruff/pull/10149))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- Remove trailing space from `CapWords` message ([#10220](https://github.com/astral-sh/ruff/pull/10220))
|
|
||||||
- Respect external codes in file-level exemptions ([#10203](https://github.com/astral-sh/ruff/pull/10203))
|
|
||||||
- \[`flake8-raise`\] Avoid false-positives for parens-on-raise with `future.exception()` (`RSE102`) ([#10206](https://github.com/astral-sh/ruff/pull/10206))
|
|
||||||
- \[`pylint`\] Add fix for unary expressions in `PLC2801` ([#9587](https://github.com/astral-sh/ruff/pull/9587))
|
|
||||||
- \[`ruff`\] Fix RUF028 not allowing `# fmt: skip` on match cases ([#10178](https://github.com/astral-sh/ruff/pull/10178))
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
This release introduces the new Ruff formatter 2024.2 style and adds a new lint rule to
|
|
||||||
detect invalid formatter suppression comments.
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- \[`flake8-bandit`\] Remove suspicious-lxml-import (`S410`) ([#10154](https://github.com/astral-sh/ruff/pull/10154))
|
|
||||||
- \[`pycodestyle`\] Allow `os.environ` modifications between imports (`E402`) ([#10066](https://github.com/astral-sh/ruff/pull/10066))
|
|
||||||
- \[`pycodestyle`\] Don't warn about a single whitespace character before a comma in a tuple (`E203`) ([#10094](https://github.com/astral-sh/ruff/pull/10094))
|
|
||||||
|
|
||||||
### Rule changes
|
|
||||||
|
|
||||||
- \[`eradicate`\] Detect commented out `case` statements (`ERA001`) ([#10055](https://github.com/astral-sh/ruff/pull/10055))
|
|
||||||
- \[`eradicate`\] Detect single-line code for `try:`, `except:`, etc. (`ERA001`) ([#10057](https://github.com/astral-sh/ruff/pull/10057))
|
|
||||||
- \[`flake8-boolean-trap`\] Allow boolean positionals in `__post_init__` ([#10027](https://github.com/astral-sh/ruff/pull/10027))
|
|
||||||
- \[`flake8-copyright`\] Allow © in copyright notices ([#10065](https://github.com/astral-sh/ruff/pull/10065))
|
|
||||||
- \[`isort`\]: Use one blank line after imports in typing stub files ([#9971](https://github.com/astral-sh/ruff/pull/9971))
|
|
||||||
- \[`pylint`\] New Rule `dict-iter-missing-items` (`PLE1141`) ([#9845](https://github.com/astral-sh/ruff/pull/9845))
|
|
||||||
- \[`pylint`\] Ignore `sys.version` and `sys.platform` (`PLR1714`) ([#10054](https://github.com/astral-sh/ruff/pull/10054))
|
|
||||||
- \[`pyupgrade`\] Detect literals with unary operators (`UP018`) ([#10060](https://github.com/astral-sh/ruff/pull/10060))
|
|
||||||
- \[`ruff`\] Expand rule for `list(iterable).pop(0)` idiom (`RUF015`) ([#10148](https://github.com/astral-sh/ruff/pull/10148))
|
|
||||||
|
|
||||||
### Formatter
|
|
||||||
|
|
||||||
This release introduces the Ruff 2024.2 style, stabilizing the following changes:
|
|
||||||
|
|
||||||
- Prefer splitting the assignment's value over the target or type annotation ([#8943](https://github.com/astral-sh/ruff/pull/8943))
|
|
||||||
- Remove blank lines before class docstrings ([#9154](https://github.com/astral-sh/ruff/pull/9154))
|
|
||||||
- Wrap multiple context managers in `with` parentheses when targeting Python 3.9 or newer ([#9222](https://github.com/astral-sh/ruff/pull/9222))
|
|
||||||
- Add a blank line after nested classes with a dummy body (`...`) in typing stub files ([#9155](https://github.com/astral-sh/ruff/pull/9155))
|
|
||||||
- Reduce vertical spacing for classes and functions with a dummy (`...`) body ([#7440](https://github.com/astral-sh/ruff/issues/7440), [#9240](https://github.com/astral-sh/ruff/pull/9240))
|
|
||||||
- Add a blank line after the module docstring ([#8283](https://github.com/astral-sh/ruff/pull/8283))
|
|
||||||
- Parenthesize long type hints in assignments ([#9210](https://github.com/astral-sh/ruff/pull/9210))
|
|
||||||
- Preserve indent for single multiline-string call-expressions ([#9673](https://github.com/astral-sh/ruff/pull/9637))
|
|
||||||
- Normalize hex escape and unicode escape sequences ([#9280](https://github.com/astral-sh/ruff/pull/9280))
|
|
||||||
- Format module docstrings ([#9725](https://github.com/astral-sh/ruff/pull/9725))
|
|
||||||
|
|
||||||
### CLI
|
|
||||||
|
|
||||||
- Explicitly disallow `extend` as part of a `--config` flag ([#10135](https://github.com/astral-sh/ruff/pull/10135))
|
|
||||||
- Remove `build` from the default exclusion list ([#10093](https://github.com/astral-sh/ruff/pull/10093))
|
|
||||||
- Deprecate `ruff <path>`, `ruff --explain`, `ruff --clean`, and `ruff --generate-shell-completion` in favor of `ruff check <path>`, `ruff rule`, `ruff clean`, and `ruff generate-shell-completion` ([#10169](https://github.com/astral-sh/ruff/pull/10169))
|
|
||||||
- Remove the deprecated CLI option `--format` from `ruff rule` and `ruff linter` ([#10170](https://github.com/astral-sh/ruff/pull/10170))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- \[`flake8-bugbear`\] Avoid adding default initializers to stubs (`B006`) ([#10152](https://github.com/astral-sh/ruff/pull/10152))
|
|
||||||
- \[`flake8-type-checking`\] Respect runtime-required decorators for function signatures ([#10091](https://github.com/astral-sh/ruff/pull/10091))
|
|
||||||
- \[`pycodestyle`\] Mark fixes overlapping with a multiline string as unsafe (`W293`) ([#10049](https://github.com/astral-sh/ruff/pull/10049))
|
|
||||||
- \[`pydocstyle`\] Trim whitespace when removing blank lines after section (`D413`) ([#10162](https://github.com/astral-sh/ruff/pull/10162))
|
|
||||||
- \[`pylint`\] Delete entire statement, including semicolons (`PLR0203`) ([#10074](https://github.com/astral-sh/ruff/pull/10074))
|
|
||||||
- \[`ruff`\] Avoid f-string false positives in `gettext` calls (`RUF027`) ([#10118](https://github.com/astral-sh/ruff/pull/10118))
|
|
||||||
- Fix `ruff` crashing on PowerPC systems because of too small page size ([#10080](https://github.com/astral-sh/ruff/pull/10080))
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Add cold attribute to less likely printer queue branches in the formatter ([#10121](https://github.com/astral-sh/ruff/pull/10121))
|
|
||||||
- Skip unnecessary string normalization in the formatter ([#10116](https://github.com/astral-sh/ruff/pull/10116))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- Remove "Beta" Label from formatter documentation ([#10144](https://github.com/astral-sh/ruff/pull/10144))
|
|
||||||
- `line-length` option: fix link to `pycodestyle.max-line-length` ([#10136](https://github.com/astral-sh/ruff/pull/10136))
|
|
||||||
|
|
||||||
## 0.2.2
|
|
||||||
|
|
||||||
Highlights include:
|
|
||||||
|
|
||||||
- Initial support formatting f-strings (in `--preview`).
|
|
||||||
- Support for overriding arbitrary configuration options via the CLI through an expanded `--config`
|
|
||||||
argument (e.g., `--config "lint.isort.combine-as-imports=false"`).
|
|
||||||
- Significant performance improvements in Ruff's lexer, parser, and lint rules.
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- Implement minimal f-string formatting ([#9642](https://github.com/astral-sh/ruff/pull/9642))
|
|
||||||
- \[`pycodestyle`\] Add blank line(s) rules (`E301`, `E302`, `E303`, `E304`, `E305`, `E306`) ([#9266](https://github.com/astral-sh/ruff/pull/9266))
|
|
||||||
- \[`refurb`\] Implement `readlines_in_for` (`FURB129`) ([#9880](https://github.com/astral-sh/ruff/pull/9880))
|
|
||||||
|
|
||||||
### Rule changes
|
|
||||||
|
|
||||||
- \[`ruff`\] Ensure closing parentheses for multiline sequences are always on their own line (`RUF022`, `RUF023`) ([#9793](https://github.com/astral-sh/ruff/pull/9793))
|
|
||||||
- \[`numpy`\] Add missing deprecation violations (`NPY002`) ([#9862](https://github.com/astral-sh/ruff/pull/9862))
|
|
||||||
- \[`flake8-bandit`\] Detect `mark_safe` usages in decorators ([#9887](https://github.com/astral-sh/ruff/pull/9887))
|
|
||||||
- \[`ruff`\] Expand `asyncio-dangling-task` (`RUF006`) to include `new_event_loop` ([#9976](https://github.com/astral-sh/ruff/pull/9976))
|
|
||||||
- \[`flake8-pyi`\] Ignore 'unused' private type dicts in class scopes ([#9952](https://github.com/astral-sh/ruff/pull/9952))
|
|
||||||
|
|
||||||
### Formatter
|
|
||||||
|
|
||||||
- Docstring formatting: Preserve tab indentation when using `indent-style=tabs` ([#9915](https://github.com/astral-sh/ruff/pull/9915))
|
|
||||||
- Disable top-level docstring formatting for notebooks ([#9957](https://github.com/astral-sh/ruff/pull/9957))
|
|
||||||
- Stabilize quote-style's `preserve` mode ([#9922](https://github.com/astral-sh/ruff/pull/9922))
|
|
||||||
|
|
||||||
### CLI
|
|
||||||
|
|
||||||
- Allow arbitrary configuration options to be overridden via the CLI ([#9599](https://github.com/astral-sh/ruff/pull/9599))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- Make `show-settings` filters directory-agnostic ([#9866](https://github.com/astral-sh/ruff/pull/9866))
|
|
||||||
- Respect duplicates when rewriting type aliases ([#9905](https://github.com/astral-sh/ruff/pull/9905))
|
|
||||||
- Respect tuple assignments in typing analyzer ([#9969](https://github.com/astral-sh/ruff/pull/9969))
|
|
||||||
- Use atomic write when persisting cache ([#9981](https://github.com/astral-sh/ruff/pull/9981))
|
|
||||||
- Use non-parenthesized range for `DebugText` ([#9953](https://github.com/astral-sh/ruff/pull/9953))
|
|
||||||
- \[`flake8-simplify`\] Avoid false positive with `async` for loops (`SIM113`) ([#9996](https://github.com/astral-sh/ruff/pull/9996))
|
|
||||||
- \[`flake8-trio`\] Respect `async with` in `timeout-without-await` ([#9859](https://github.com/astral-sh/ruff/pull/9859))
|
|
||||||
- \[`perflint`\] Catch a wider range of mutations in `PERF101` ([#9955](https://github.com/astral-sh/ruff/pull/9955))
|
|
||||||
- \[`pycodestyle`\] Fix `E30X` panics on blank lines with trailing white spaces ([#9907](https://github.com/astral-sh/ruff/pull/9907))
|
|
||||||
- \[`pydocstyle`\] Allow using `parameters` as a subsection header (`D405`) ([#9894](https://github.com/astral-sh/ruff/pull/9894))
|
|
||||||
- \[`pydocstyle`\] Fix blank-line docstring rules for module-level docstrings ([#9878](https://github.com/astral-sh/ruff/pull/9878))
|
|
||||||
- \[`pylint`\] Accept 0.0 and 1.0 as common magic values (`PLR2004`) ([#9964](https://github.com/astral-sh/ruff/pull/9964))
|
|
||||||
- \[`pylint`\] Avoid suggesting set rewrites for non-hashable types ([#9956](https://github.com/astral-sh/ruff/pull/9956))
|
|
||||||
- \[`ruff`\] Avoid false negatives with string literals inside of method calls (`RUF027`) ([#9865](https://github.com/astral-sh/ruff/pull/9865))
|
|
||||||
- \[`ruff`\] Fix panic on with f-string detection (`RUF027`) ([#9990](https://github.com/astral-sh/ruff/pull/9990))
|
|
||||||
- \[`ruff`\] Ignore builtins when detecting missing f-strings ([#9849](https://github.com/astral-sh/ruff/pull/9849))
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Use `memchr` for string lexing ([#9888](https://github.com/astral-sh/ruff/pull/9888))
|
|
||||||
- Use `memchr` for tab-indentation detection ([#9853](https://github.com/astral-sh/ruff/pull/9853))
|
|
||||||
- Reduce `Result<Tok, LexicalError>` size by using `Box<str>` instead of `String` ([#9885](https://github.com/astral-sh/ruff/pull/9885))
|
|
||||||
- Reduce size of `Expr` from 80 to 64 bytes ([#9900](https://github.com/astral-sh/ruff/pull/9900))
|
|
||||||
- Improve trailing comma rule performance ([#9867](https://github.com/astral-sh/ruff/pull/9867))
|
|
||||||
- Remove unnecessary string cloning from the parser ([#9884](https://github.com/astral-sh/ruff/pull/9884))
|
|
||||||
|
|
||||||
## 0.2.1
|
|
||||||
|
|
||||||
This release includes support for range formatting (i.e., the ability to format specific lines
|
|
||||||
within a source file).
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- \[`refurb`\] Implement `missing-f-string-syntax` (`RUF027`) ([#9728](https://github.com/astral-sh/ruff/pull/9728))
|
|
||||||
- Format module-level docstrings ([#9725](https://github.com/astral-sh/ruff/pull/9725))
|
|
||||||
|
|
||||||
### Formatter
|
|
||||||
|
|
||||||
- Add `--range` option to `ruff format` ([#9733](https://github.com/astral-sh/ruff/pull/9733))
|
|
||||||
- Don't trim last empty line in docstrings ([#9813](https://github.com/astral-sh/ruff/pull/9813))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- Skip empty lines when determining base indentation ([#9795](https://github.com/astral-sh/ruff/pull/9795))
|
|
||||||
- Drop `__get__` and `__set__` from `unnecessary-dunder-call` ([#9791](https://github.com/astral-sh/ruff/pull/9791))
|
|
||||||
- Respect generic `Protocol` in ellipsis removal ([#9841](https://github.com/astral-sh/ruff/pull/9841))
|
|
||||||
- Revert "Use publicly available Apple Silicon runners (#9726)" ([#9834](https://github.com/astral-sh/ruff/pull/9834))
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Skip LibCST parsing for standard dedent adjustments ([#9769](https://github.com/astral-sh/ruff/pull/9769))
|
|
||||||
- Remove CST-based fixer for `C408` ([#9822](https://github.com/astral-sh/ruff/pull/9822))
|
|
||||||
- Add our own ignored-names abstractions ([#9802](https://github.com/astral-sh/ruff/pull/9802))
|
|
||||||
- Remove CST-based fixers for `C400`, `C401`, `C410`, and `C418` ([#9819](https://github.com/astral-sh/ruff/pull/9819))
|
|
||||||
- Use `AhoCorasick` to speed up quote match ([#9773](https://github.com/astral-sh/ruff/pull/9773))
|
|
||||||
- Remove CST-based fixers for `C405` and `C409` ([#9821](https://github.com/astral-sh/ruff/pull/9821))
|
|
||||||
- Add fast-path for comment detection ([#9808](https://github.com/astral-sh/ruff/pull/9808))
|
|
||||||
- Invert order of checks in `zero-sleep-call` ([#9766](https://github.com/astral-sh/ruff/pull/9766))
|
|
||||||
- Short-circuit typing matches based on imports ([#9800](https://github.com/astral-sh/ruff/pull/9800))
|
|
||||||
- Run dunder method rule on methods directly ([#9815](https://github.com/astral-sh/ruff/pull/9815))
|
|
||||||
- Track top-level module imports in the semantic model ([#9775](https://github.com/astral-sh/ruff/pull/9775))
|
|
||||||
- Slight speed-up for lowercase and uppercase identifier checks ([#9798](https://github.com/astral-sh/ruff/pull/9798))
|
|
||||||
- Remove LibCST-based fixer for `C403` ([#9818](https://github.com/astral-sh/ruff/pull/9818))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- Update `max-pos-args` example to `max-positional-args` ([#9797](https://github.com/astral-sh/ruff/pull/9797))
|
|
||||||
- Fixed example code in `weak_cryptographic_key.rs` ([#9774](https://github.com/astral-sh/ruff/pull/9774))
|
|
||||||
- Fix references to deprecated `ANN` rules in changelog ([#9771](https://github.com/astral-sh/ruff/pull/9771))
|
|
||||||
- Fix default for `max-positional-args` ([#9838](https://github.com/astral-sh/ruff/pull/9838))
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
### Breaking changes
|
|
||||||
|
|
||||||
- The `NURSERY` selector cannot be used anymore
|
|
||||||
- Legacy selection of nursery rules by exact codes is no longer allowed without preview enabled
|
|
||||||
|
|
||||||
See also, the "Remapped rules" section which may result in disabled rules.
|
|
||||||
|
|
||||||
### Deprecations
|
|
||||||
|
|
||||||
The following rules are now deprecated:
|
|
||||||
|
|
||||||
- [`missing-type-self`](https://docs.astral.sh/ruff/rules/missing-type-self/) (`ANN101`)
|
|
||||||
- [`missing-type-cls`](https://docs.astral.sh/ruff/rules/missing-type-cls/) (`ANN102`)
|
|
||||||
|
|
||||||
The following command line options are now deprecated:
|
|
||||||
|
|
||||||
- `--show-source`; use `--output-format full` instead
|
|
||||||
- `--no-show-source`; use `--output-format concise` instead
|
|
||||||
- `--output-format text`; use `full` or `concise` instead
|
|
||||||
|
|
||||||
The following settings have moved and the previous name is deprecated:
|
|
||||||
|
|
||||||
- `ruff.allowed-confusables` → [`ruff.lint.allowed-confusables`](https://docs.astral.sh//ruff/settings/#lint_allowed-confusables)
|
|
||||||
- `ruff.dummy-variable-rgx` → [`ruff.lint.dummy-variable-rgx`](https://docs.astral.sh//ruff/settings/#lint_dummy-variable-rgx)
|
|
||||||
- `ruff.explicit-preview-rules` → [`ruff.lint.explicit-preview-rules`](https://docs.astral.sh//ruff/settings/#lint_explicit-preview-rules)
|
|
||||||
- `ruff.extend-fixable` → [`ruff.lint.extend-fixable`](https://docs.astral.sh//ruff/settings/#lint_extend-fixable)
|
|
||||||
- `ruff.extend-ignore` → [`ruff.lint.extend-ignore`](https://docs.astral.sh//ruff/settings/#lint_extend-ignore)
|
|
||||||
- `ruff.extend-per-file-ignores` → [`ruff.lint.extend-per-file-ignores`](https://docs.astral.sh//ruff/settings/#lint_extend-per-file-ignores)
|
|
||||||
- `ruff.extend-safe-fixes` → [`ruff.lint.extend-safe-fixes`](https://docs.astral.sh//ruff/settings/#lint_extend-safe-fixes)
|
|
||||||
- `ruff.extend-select` → [`ruff.lint.extend-select`](https://docs.astral.sh//ruff/settings/#lint_extend-select)
|
|
||||||
- `ruff.extend-unfixable` → [`ruff.lint.extend-unfixable`](https://docs.astral.sh//ruff/settings/#lint_extend-unfixable)
|
|
||||||
- `ruff.extend-unsafe-fixes` → [`ruff.lint.extend-unsafe-fixes`](https://docs.astral.sh//ruff/settings/#lint_extend-unsafe-fixes)
|
|
||||||
- `ruff.external` → [`ruff.lint.external`](https://docs.astral.sh//ruff/settings/#lint_external)
|
|
||||||
- `ruff.fixable` → [`ruff.lint.fixable`](https://docs.astral.sh//ruff/settings/#lint_fixable)
|
|
||||||
- `ruff.flake8-annotations` → [`ruff.lint.flake8-annotations`](https://docs.astral.sh//ruff/settings/#lint_flake8-annotations)
|
|
||||||
- `ruff.flake8-bandit` → [`ruff.lint.flake8-bandit`](https://docs.astral.sh//ruff/settings/#lint_flake8-bandit)
|
|
||||||
- `ruff.flake8-bugbear` → [`ruff.lint.flake8-bugbear`](https://docs.astral.sh//ruff/settings/#lint_flake8-bugbear)
|
|
||||||
- `ruff.flake8-builtins` → [`ruff.lint.flake8-builtins`](https://docs.astral.sh//ruff/settings/#lint_flake8-builtins)
|
|
||||||
- `ruff.flake8-comprehensions` → [`ruff.lint.flake8-comprehensions`](https://docs.astral.sh//ruff/settings/#lint_flake8-comprehensions)
|
|
||||||
- `ruff.flake8-copyright` → [`ruff.lint.flake8-copyright`](https://docs.astral.sh//ruff/settings/#lint_flake8-copyright)
|
|
||||||
- `ruff.flake8-errmsg` → [`ruff.lint.flake8-errmsg`](https://docs.astral.sh//ruff/settings/#lint_flake8-errmsg)
|
|
||||||
- `ruff.flake8-gettext` → [`ruff.lint.flake8-gettext`](https://docs.astral.sh//ruff/settings/#lint_flake8-gettext)
|
|
||||||
- `ruff.flake8-implicit-str-concat` → [`ruff.lint.flake8-implicit-str-concat`](https://docs.astral.sh//ruff/settings/#lint_flake8-implicit-str-concat)
|
|
||||||
- `ruff.flake8-import-conventions` → [`ruff.lint.flake8-import-conventions`](https://docs.astral.sh//ruff/settings/#lint_flake8-import-conventions)
|
|
||||||
- `ruff.flake8-pytest-style` → [`ruff.lint.flake8-pytest-style`](https://docs.astral.sh//ruff/settings/#lint_flake8-pytest-style)
|
|
||||||
- `ruff.flake8-quotes` → [`ruff.lint.flake8-quotes`](https://docs.astral.sh//ruff/settings/#lint_flake8-quotes)
|
|
||||||
- `ruff.flake8-self` → [`ruff.lint.flake8-self`](https://docs.astral.sh//ruff/settings/#lint_flake8-self)
|
|
||||||
- `ruff.flake8-tidy-imports` → [`ruff.lint.flake8-tidy-imports`](https://docs.astral.sh//ruff/settings/#lint_flake8-tidy-imports)
|
|
||||||
- `ruff.flake8-type-checking` → [`ruff.lint.flake8-type-checking`](https://docs.astral.sh//ruff/settings/#lint_flake8-type-checking)
|
|
||||||
- `ruff.flake8-unused-arguments` → [`ruff.lint.flake8-unused-arguments`](https://docs.astral.sh//ruff/settings/#lint_flake8-unused-arguments)
|
|
||||||
- `ruff.ignore` → [`ruff.lint.ignore`](https://docs.astral.sh//ruff/settings/#lint_ignore)
|
|
||||||
- `ruff.ignore-init-module-imports` → [`ruff.lint.ignore-init-module-imports`](https://docs.astral.sh//ruff/settings/#lint_ignore-init-module-imports)
|
|
||||||
- `ruff.isort` → [`ruff.lint.isort`](https://docs.astral.sh//ruff/settings/#lint_isort)
|
|
||||||
- `ruff.logger-objects` → [`ruff.lint.logger-objects`](https://docs.astral.sh//ruff/settings/#lint_logger-objects)
|
|
||||||
- `ruff.mccabe` → [`ruff.lint.mccabe`](https://docs.astral.sh//ruff/settings/#lint_mccabe)
|
|
||||||
- `ruff.pep8-naming` → [`ruff.lint.pep8-naming`](https://docs.astral.sh//ruff/settings/#lint_pep8-naming)
|
|
||||||
- `ruff.per-file-ignores` → [`ruff.lint.per-file-ignores`](https://docs.astral.sh//ruff/settings/#lint_per-file-ignores)
|
|
||||||
- `ruff.pycodestyle` → [`ruff.lint.pycodestyle`](https://docs.astral.sh//ruff/settings/#lint_pycodestyle)
|
|
||||||
- `ruff.pydocstyle` → [`ruff.lint.pydocstyle`](https://docs.astral.sh//ruff/settings/#lint_pydocstyle)
|
|
||||||
- `ruff.pyflakes` → [`ruff.lint.pyflakes`](https://docs.astral.sh//ruff/settings/#lint_pyflakes)
|
|
||||||
- `ruff.pylint` → [`ruff.lint.pylint`](https://docs.astral.sh//ruff/settings/#lint_pylint)
|
|
||||||
- `ruff.pyupgrade` → [`ruff.lint.pyupgrade`](https://docs.astral.sh//ruff/settings/#lint_pyupgrade)
|
|
||||||
- `ruff.select` → [`ruff.lint.select`](https://docs.astral.sh//ruff/settings/#lint_select)
|
|
||||||
- `ruff.task-tags` → [`ruff.lint.task-tags`](https://docs.astral.sh//ruff/settings/#lint_task-tags)
|
|
||||||
- `ruff.typing-modules` → [`ruff.lint.typing-modules`](https://docs.astral.sh//ruff/settings/#lint_typing-modules)
|
|
||||||
- `ruff.unfixable` → [`ruff.lint.unfixable`](https://docs.astral.sh//ruff/settings/#lint_unfixable)
|
|
||||||
|
|
||||||
### Remapped rules
|
|
||||||
|
|
||||||
The following rules have been remapped to new codes:
|
|
||||||
|
|
||||||
- [`raise-without-from-inside-except`](https://docs.astral.sh/ruff/rules/raise-without-from-inside-except/): `TRY200` to `B904`
|
|
||||||
- [`suspicious-eval-usage`](https://docs.astral.sh/ruff/rules/suspicious-eval-usage/): `PGH001` to `S307`
|
|
||||||
- [`logging-warn`](https://docs.astral.sh/ruff/rules/logging-warn/): `PGH002` to `G010`
|
|
||||||
- [`static-key-dict-comprehension`](https://docs.astral.sh/ruff/rules/static-key-dict-comprehension): `RUF011` to `B035`
|
|
||||||
- [`runtime-string-union`](https://docs.astral.sh/ruff/rules/runtime-string-union): `TCH006` to `TCH010`
|
|
||||||
|
|
||||||
### Stabilizations
|
|
||||||
|
|
||||||
The following rules have been stabilized and are no longer in preview:
|
|
||||||
|
|
||||||
- [`trio-timeout-without-await`](https://docs.astral.sh/ruff/rules/trio-timeout-without-await) (`TRIO100`)
|
|
||||||
- [`trio-sync-call`](https://docs.astral.sh/ruff/rules/trio-sync-call) (`TRIO105`)
|
|
||||||
- [`trio-async-function-with-timeout`](https://docs.astral.sh/ruff/rules/trio-async-function-with-timeout) (`TRIO109`)
|
|
||||||
- [`trio-unneeded-sleep`](https://docs.astral.sh/ruff/rules/trio-unneeded-sleep) (`TRIO110`)
|
|
||||||
- [`trio-zero-sleep-call`](https://docs.astral.sh/ruff/rules/trio-zero-sleep-call) (`TRIO115`)
|
|
||||||
- [`unnecessary-escaped-quote`](https://docs.astral.sh/ruff/rules/unnecessary-escaped-quote) (`Q004`)
|
|
||||||
- [`enumerate-for-loop`](https://docs.astral.sh/ruff/rules/enumerate-for-loop) (`SIM113`)
|
|
||||||
- [`zip-dict-keys-and-values`](https://docs.astral.sh/ruff/rules/zip-dict-keys-and-values) (`SIM911`)
|
|
||||||
- [`timeout-error-alias`](https://docs.astral.sh/ruff/rules/timeout-error-alias) (`UP041`)
|
|
||||||
- [`flask-debug-true`](https://docs.astral.sh/ruff/rules/flask-debug-true) (`S201`)
|
|
||||||
- [`tarfile-unsafe-members`](https://docs.astral.sh/ruff/rules/tarfile-unsafe-members) (`S202`)
|
|
||||||
- [`ssl-insecure-version`](https://docs.astral.sh/ruff/rules/ssl-insecure-version) (`S502`)
|
|
||||||
- [`ssl-with-bad-defaults`](https://docs.astral.sh/ruff/rules/ssl-with-bad-defaults) (`S503`)
|
|
||||||
- [`ssl-with-no-version`](https://docs.astral.sh/ruff/rules/ssl-with-no-version) (`S504`)
|
|
||||||
- [`weak-cryptographic-key`](https://docs.astral.sh/ruff/rules/weak-cryptographic-key) (`S505`)
|
|
||||||
- [`ssh-no-host-key-verification`](https://docs.astral.sh/ruff/rules/ssh-no-host-key-verification) (`S507`)
|
|
||||||
- [`django-raw-sql`](https://docs.astral.sh/ruff/rules/django-raw-sql) (`S611`)
|
|
||||||
- [`mako-templates`](https://docs.astral.sh/ruff/rules/mako-templates) (`S702`)
|
|
||||||
- [`generator-return-from-iter-method`](https://docs.astral.sh/ruff/rules/generator-return-from-iter-method) (`PYI058`)
|
|
||||||
- [`runtime-string-union`](https://docs.astral.sh/ruff/rules/runtime-string-union) (`TCH006`)
|
|
||||||
- [`numpy2-deprecation`](https://docs.astral.sh/ruff/rules/numpy2-deprecation) (`NPY201`)
|
|
||||||
- [`quadratic-list-summation`](https://docs.astral.sh/ruff/rules/quadratic-list-summation) (`RUF017`)
|
|
||||||
- [`assignment-in-assert`](https://docs.astral.sh/ruff/rules/assignment-in-assert) (`RUF018`)
|
|
||||||
- [`unnecessary-key-check`](https://docs.astral.sh/ruff/rules/unnecessary-key-check) (`RUF019`)
|
|
||||||
- [`never-union`](https://docs.astral.sh/ruff/rules/never-union) (`RUF020`)
|
|
||||||
- [`direct-logger-instantiation`](https://docs.astral.sh/ruff/rules/direct-logger-instantiation) (`LOG001`)
|
|
||||||
- [`invalid-get-logger-argument`](https://docs.astral.sh/ruff/rules/invalid-get-logger-argument) (`LOG002`)
|
|
||||||
- [`exception-without-exc-info`](https://docs.astral.sh/ruff/rules/exception-without-exc-info) (`LOG007`)
|
|
||||||
- [`undocumented-warn`](https://docs.astral.sh/ruff/rules/undocumented-warn) (`LOG009`)
|
|
||||||
|
|
||||||
Fixes for the following rules have been stabilized and are now available without preview:
|
|
||||||
|
|
||||||
- [`triple-single-quotes`](https://docs.astral.sh/ruff/rules/triple-single-quotes) (`D300`)
|
|
||||||
- [`non-pep604-annotation`](https://docs.astral.sh/ruff/rules/non-pep604-annotation) (`UP007`)
|
|
||||||
- [`dict-get-with-none-default`](https://docs.astral.sh/ruff/rules/dict-get-with-none-default) (`SIM910`)
|
|
||||||
- [`in-dict-keys`](https://docs.astral.sh/ruff/rules/in-dict-keys) (`SIM118`)
|
|
||||||
- [`collapsible-else-if`](https://docs.astral.sh/ruff/rules/collapsible-else-if) (`PLR5501`)
|
|
||||||
- [`if-with-same-arms`](https://docs.astral.sh/ruff/rules/if-with-same-arms) (`SIM114`)
|
|
||||||
- [`useless-else-on-loop`](https://docs.astral.sh/ruff/rules/useless-else-on-loop) (`PLW0120`)
|
|
||||||
- [`unnecessary-literal-union`](https://docs.astral.sh/ruff/rules/unnecessary-literal-union) (`PYI030`)
|
|
||||||
- [`unnecessary-spread`](https://docs.astral.sh/ruff/rules/unnecessary-spread) (`PIE800`)
|
|
||||||
- [`error-instead-of-exception`](https://docs.astral.sh/ruff/rules/error-instead-of-exception) (`TRY400`)
|
|
||||||
- [`redefined-while-unused`](https://docs.astral.sh/ruff/rules/redefined-while-unused) (`F811`)
|
|
||||||
- [`duplicate-value`](https://docs.astral.sh/ruff/rules/duplicate-value) (`B033`)
|
|
||||||
- [`multiple-imports-on-one-line`](https://docs.astral.sh/ruff/rules/multiple-imports-on-one-line) (`E401`)
|
|
||||||
- [`non-pep585-annotation`](https://docs.astral.sh/ruff/rules/non-pep585-annotation) (`UP006`)
|
|
||||||
|
|
||||||
Fixes for the following rules have been promoted from unsafe to safe:
|
|
||||||
|
|
||||||
- [`unaliased-collections-abc-set-import`](https://docs.astral.sh/ruff/rules/unaliased-collections-abc-set-import) (`PYI025`)
|
|
||||||
|
|
||||||
The following behaviors have been stabilized:
|
|
||||||
|
|
||||||
- [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file/) (`E402`) allows `sys.path` modifications between imports
|
|
||||||
- [`reimplemented-container-builtin`](https://docs.astral.sh/ruff/rules/reimplemented-container-builtin/) (`PIE807`) includes lambdas that can be replaced with `dict`
|
|
||||||
- [`unnecessary-placeholder`](https://docs.astral.sh/ruff/rules/unnecessary-placeholder/) (`PIE790`) applies to unnecessary ellipses (`...`)
|
|
||||||
- [`if-else-block-instead-of-dict-get`](https://docs.astral.sh/ruff/rules/if-else-block-instead-of-dict-get/) (`SIM401`) applies to `if-else` expressions
|
|
||||||
|
|
||||||
### Preview features
|
|
||||||
|
|
||||||
- \[`refurb`\] Implement `metaclass_abcmeta` (`FURB180`) ([#9658](https://github.com/astral-sh/ruff/pull/9658))
|
|
||||||
- Implement `blank_line_after_nested_stub_class` preview style ([#9155](https://github.com/astral-sh/ruff/pull/9155))
|
|
||||||
- The preview rule [`and-or-ternary`](https://docs.astral.sh/ruff/rules/and-or-ternary) (`PLR1706`) was removed
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- \[`flake8-async`\] Take `pathlib.Path` into account when analyzing async functions ([#9703](https://github.com/astral-sh/ruff/pull/9703))
|
|
||||||
- \[`flake8-return`\] - fix indentation syntax error (`RET505`) ([#9705](https://github.com/astral-sh/ruff/pull/9705))
|
|
||||||
- Detect multi-statement lines in else removal ([#9748](https://github.com/astral-sh/ruff/pull/9748))
|
|
||||||
- `RUF022`, `RUF023`: never add two trailing commas to the end of a sequence ([#9698](https://github.com/astral-sh/ruff/pull/9698))
|
|
||||||
- `RUF023`: Don't sort `__match_args__`, only `__slots__` ([#9724](https://github.com/astral-sh/ruff/pull/9724))
|
|
||||||
- \[`flake8-simplify`\] - Fix syntax error in autofix (`SIM114`) ([#9704](https://github.com/astral-sh/ruff/pull/9704))
|
|
||||||
- \[`pylint`\] Show verbatim constant in `magic-value-comparison` (`PLR2004`) ([#9694](https://github.com/astral-sh/ruff/pull/9694))
|
|
||||||
- Removing trailing whitespace inside multiline strings is unsafe ([#9744](https://github.com/astral-sh/ruff/pull/9744))
|
|
||||||
- Support `IfExp` with dual string arms in `invalid-envvar-default` ([#9734](https://github.com/astral-sh/ruff/pull/9734))
|
|
||||||
- \[`pylint`\] Add `__mro_entries__` to known dunder methods (`PLW3201`) ([#9706](https://github.com/astral-sh/ruff/pull/9706))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- Removed rules are now retained in the documentation ([#9691](https://github.com/astral-sh/ruff/pull/9691))
|
|
||||||
- Deprecated rules are now indicated in the documentation ([#9689](https://github.com/astral-sh/ruff/pull/9689))
|
|
||||||
|
|
||||||
## 0.1.15
|
## 0.1.15
|
||||||
|
|
||||||
### Preview features
|
### Preview features
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
|
|||||||
- [`cargo dev`](#cargo-dev)
|
- [`cargo dev`](#cargo-dev)
|
||||||
- [Subsystems](#subsystems)
|
- [Subsystems](#subsystems)
|
||||||
- [Compilation Pipeline](#compilation-pipeline)
|
- [Compilation Pipeline](#compilation-pipeline)
|
||||||
- [Import Categorization](#import-categorization)
|
|
||||||
- [Project root](#project-root)
|
|
||||||
- [Package root](#package-root)
|
|
||||||
- [Import categorization](#import-categorization-1)
|
|
||||||
|
|
||||||
## The Basics
|
## The Basics
|
||||||
|
|
||||||
@@ -39,7 +35,7 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
|
|||||||
|
|
||||||
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
|
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
|
||||||
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
|
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
|
||||||
You can also join us on [**Discord**](https://discord.com/invite/astral-sh) to discuss your idea with the
|
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
|
||||||
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||||
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||||
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
|
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
|
||||||
@@ -67,7 +63,7 @@ You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
|
|||||||
cargo install cargo-insta
|
cargo install cargo-insta
|
||||||
```
|
```
|
||||||
|
|
||||||
And you'll need pre-commit to run some validation checks:
|
and pre-commit to run some validation checks:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
|
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
|
||||||
@@ -80,16 +76,6 @@ when making a commit:
|
|||||||
pre-commit install
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
|
|
||||||
though it's not strictly necessary:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo install cargo-nextest --locked
|
|
||||||
```
|
|
||||||
|
|
||||||
Throughout this guide, any usages of `cargo test` can be replaced with `cargo nextest run`,
|
|
||||||
if you choose to install `nextest`.
|
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
After cloning the repository, run Ruff locally from the repository root with:
|
After cloning the repository, run Ruff locally from the repository root with:
|
||||||
@@ -245,7 +231,7 @@ Once you've completed the code for the rule itself, you can define tests with th
|
|||||||
For example, if you're adding a new rule named `E402`, you would run:
|
For example, if you're adding a new rule named `E402`, you would run:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --preview --select E402
|
cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
|
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
|
||||||
@@ -316,7 +302,7 @@ To preview any changes to the documentation locally:
|
|||||||
```
|
```
|
||||||
|
|
||||||
The documentation should then be available locally at
|
The documentation should then be available locally at
|
||||||
[http://127.0.0.1:8000/ruff/](http://127.0.0.1:8000/ruff/).
|
[http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
|
||||||
|
|
||||||
## Release Process
|
## Release Process
|
||||||
|
|
||||||
@@ -329,13 +315,13 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
|||||||
|
|
||||||
### Creating a new release
|
### Creating a new release
|
||||||
|
|
||||||
1. Install `uv`: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
We use an experimental in-house tool for managing releases.
|
||||||
1. Run `./scripts/release/bump.sh`; this command will:
|
|
||||||
- Generate a temporary virtual environment with `rooster`
|
1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
|
||||||
|
1. Run `rooster release`; this command will:
|
||||||
- Generate a changelog entry in `CHANGELOG.md`
|
- Generate a changelog entry in `CHANGELOG.md`
|
||||||
- Update versions in `pyproject.toml` and `Cargo.toml`
|
- Update versions in `pyproject.toml` and `Cargo.toml`
|
||||||
- Update references to versions in the `README.md` and documentation
|
- Update references to versions in the `README.md` and documentation
|
||||||
- Display contributors for the release
|
|
||||||
1. The changelog should then be editorialized for consistency
|
1. The changelog should then be editorialized for consistency
|
||||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||||
@@ -359,7 +345,7 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
|||||||
1. Open the draft release in the GitHub release section
|
1. Open the draft release in the GitHub release section
|
||||||
1. Copy the changelog for the release into the GitHub release
|
1. Copy the changelog for the release into the GitHub release
|
||||||
- See previous releases for formatting of section headers
|
- See previous releases for formatting of section headers
|
||||||
1. Append the contributors from the `bump.sh` script
|
1. Generate the contributor list with `rooster contributors` and add to the release notes
|
||||||
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
1. If needed, [update the schemastore](https://github.com/astral-sh/ruff/blob/main/scripts/update_schemastore.py).
|
||||||
1. One can determine if an update is needed when
|
1. One can determine if an update is needed when
|
||||||
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
`git diff old-version-tag new-version-tag -- ruff.schema.json` returns a non-empty diff.
|
||||||
@@ -387,11 +373,6 @@ We have several ways of benchmarking and profiling Ruff:
|
|||||||
- Microbenchmarks which run the linter or the formatter on individual files. These run on pull requests.
|
- Microbenchmarks which run the linter or the formatter on individual files. These run on pull requests.
|
||||||
- Profiling the linter on either the microbenchmarks or entire projects
|
- Profiling the linter on either the microbenchmarks or entire projects
|
||||||
|
|
||||||
> \[!NOTE\]
|
|
||||||
> When running benchmarks, ensure that your CPU is otherwise idle (e.g., close any background
|
|
||||||
> applications, like web browsers). You may also want to switch your CPU to a "performance"
|
|
||||||
> mode, if it exists, especially when benchmarking short-lived processes.
|
|
||||||
|
|
||||||
### CPython Benchmark
|
### CPython Benchmark
|
||||||
|
|
||||||
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
|
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
|
||||||
|
|||||||
1090
Cargo.lock
generated
1090
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
74
Cargo.toml
74
Cargo.toml
@@ -14,55 +14,49 @@ license = "MIT"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
aho-corasick = { version = "1.1.2" }
|
aho-corasick = { version = "1.1.2" }
|
||||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||||
anyhow = { version = "1.0.80" }
|
anyhow = { version = "1.0.79" }
|
||||||
argfile = { version = "0.1.6" }
|
argfile = { version = "0.1.6" }
|
||||||
assert_cmd = { version = "2.0.13" }
|
assert_cmd = { version = "2.0.13" }
|
||||||
bincode = { version = "1.3.3" }
|
bincode = { version = "1.3.3" }
|
||||||
bitflags = { version = "2.4.1" }
|
bitflags = { version = "2.4.1" }
|
||||||
bstr = { version = "1.9.1" }
|
|
||||||
cachedir = { version = "0.3.1" }
|
cachedir = { version = "0.3.1" }
|
||||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.33", default-features = false, features = ["clock"] }
|
||||||
clap = { version = "4.5.2", features = ["derive"] }
|
clap = { version = "4.4.13", features = ["derive"] }
|
||||||
clap_complete_command = { version = "0.5.1" }
|
clap_complete_command = { version = "0.5.1" }
|
||||||
clearscreen = { version = "2.0.0" }
|
clearscreen = { version = "2.0.0" }
|
||||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
|
||||||
colored = { version = "2.1.0" }
|
colored = { version = "2.1.0" }
|
||||||
configparser = { version = "3.0.3" }
|
configparser = { version = "3.0.3" }
|
||||||
console_error_panic_hook = { version = "0.1.7" }
|
console_error_panic_hook = { version = "0.1.7" }
|
||||||
console_log = { version = "1.0.0" }
|
console_log = { version = "1.0.0" }
|
||||||
countme = { version = "3.0.1" }
|
countme = { version ="3.0.1"}
|
||||||
criterion = { version = "0.5.1", default-features = false }
|
criterion = { version = "0.5.1", default-features = false }
|
||||||
crossbeam = { version = "0.8.4" }
|
|
||||||
dirs = { version = "5.0.0" }
|
dirs = { version = "5.0.0" }
|
||||||
drop_bomb = { version = "0.1.5" }
|
drop_bomb = { version = "0.1.5" }
|
||||||
env_logger = { version = "0.10.1" }
|
env_logger = { version ="0.10.1"}
|
||||||
fern = { version = "0.6.1" }
|
fern = { version = "0.6.1" }
|
||||||
filetime = { version = "0.2.23" }
|
filetime = { version = "0.2.23" }
|
||||||
fs-err = { version = "2.11.0" }
|
fs-err = { version ="2.11.0"}
|
||||||
glob = { version = "0.3.1" }
|
glob = { version = "0.3.1" }
|
||||||
globset = { version = "0.4.14" }
|
globset = { version = "0.4.14" }
|
||||||
hexf-parse = { version = "0.2.1" }
|
hexf-parse = { version ="0.2.1"}
|
||||||
ignore = { version = "0.4.22" }
|
ignore = { version = "0.4.22" }
|
||||||
imara-diff = { version = "0.1.5" }
|
imara-diff ={ version = "0.1.5"}
|
||||||
imperative = { version = "1.0.4" }
|
imperative = { version = "1.0.4" }
|
||||||
indicatif = { version = "0.17.8" }
|
indicatif ={ version = "0.17.7"}
|
||||||
indoc = { version = "2.0.4" }
|
indoc ={ version = "2.0.4"}
|
||||||
insta = { version = "1.35.1", feature = ["filters", "glob"] }
|
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||||
insta-cmd = { version = "0.4.0" }
|
insta-cmd = { version = "0.4.0" }
|
||||||
is-macro = { version = "0.3.5" }
|
is-macro = { version = "0.3.4" }
|
||||||
is-wsl = { version = "0.4.0" }
|
is-wsl = { version = "0.4.0" }
|
||||||
itertools = { version = "0.12.1" }
|
itertools = { version = "0.12.0" }
|
||||||
js-sys = { version = "0.3.69" }
|
js-sys = { version = "0.3.67" }
|
||||||
jod-thread = { version = "0.1.2" }
|
|
||||||
lalrpop-util = { version = "0.20.0", default-features = false }
|
lalrpop-util = { version = "0.20.0", default-features = false }
|
||||||
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
||||||
libc = { version = "0.2.153" }
|
|
||||||
libcst = { version = "1.1.0", default-features = false }
|
libcst = { version = "1.1.0", default-features = false }
|
||||||
log = { version = "0.4.17" }
|
log = { version = "0.4.17" }
|
||||||
lsp-server = { version = "0.7.6" }
|
memchr = { version = "2.6.4" }
|
||||||
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
mimalloc = { version ="0.1.39"}
|
||||||
memchr = { version = "2.7.1" }
|
|
||||||
mimalloc = { version = "0.1.39" }
|
|
||||||
natord = { version = "1.0.9" }
|
natord = { version = "1.0.9" }
|
||||||
notify = { version = "6.1.1" }
|
notify = { version = "6.1.1" }
|
||||||
once_cell = { version = "1.19.0" }
|
once_cell = { version = "1.19.0" }
|
||||||
@@ -70,8 +64,8 @@ path-absolutize = { version = "3.1.1" }
|
|||||||
pathdiff = { version = "0.2.1" }
|
pathdiff = { version = "0.2.1" }
|
||||||
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
pep440_rs = { version = "0.4.0", features = ["serde"] }
|
||||||
pretty_assertions = "1.3.0"
|
pretty_assertions = "1.3.0"
|
||||||
proc-macro2 = { version = "1.0.78" }
|
proc-macro2 = { version = "1.0.73" }
|
||||||
pyproject-toml = { version = "0.9.0" }
|
pyproject-toml = { version = "0.8.1" }
|
||||||
quick-junit = { version = "0.3.5" }
|
quick-junit = { version = "0.3.5" }
|
||||||
quote = { version = "1.0.23" }
|
quote = { version = "1.0.23" }
|
||||||
rand = { version = "0.8.5" }
|
rand = { version = "0.8.5" }
|
||||||
@@ -80,39 +74,39 @@ regex = { version = "1.10.2" }
|
|||||||
result-like = { version = "0.5.0" }
|
result-like = { version = "0.5.0" }
|
||||||
rustc-hash = { version = "1.1.0" }
|
rustc-hash = { version = "1.1.0" }
|
||||||
schemars = { version = "0.8.16" }
|
schemars = { version = "0.8.16" }
|
||||||
seahash = { version = "4.1.0" }
|
seahash = { version ="4.1.0"}
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
semver = { version = "1.0.21" }
|
||||||
serde-wasm-bindgen = { version = "0.6.4" }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
|
serde-wasm-bindgen = { version = "0.6.3" }
|
||||||
serde_json = { version = "1.0.113" }
|
serde_json = { version = "1.0.113" }
|
||||||
serde_test = { version = "1.0.152" }
|
serde_test = { version = "1.0.152" }
|
||||||
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
|
serde_with = { version = "3.5.1", default-features = false, features = ["macros"] }
|
||||||
shellexpand = { version = "3.0.0" }
|
shellexpand = { version = "3.0.0" }
|
||||||
shlex = { version = "1.3.0" }
|
shlex = { version ="1.3.0"}
|
||||||
similar = { version = "2.4.0", features = ["inline"] }
|
similar = { version = "2.4.0", features = ["inline"] }
|
||||||
smallvec = { version = "1.13.1" }
|
smallvec = { version = "1.13.1" }
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||||
strum_macros = { version = "0.25.3" }
|
strum_macros = { version = "0.25.3" }
|
||||||
syn = { version = "2.0.51" }
|
syn = { version = "2.0.40" }
|
||||||
tempfile = { version = "3.9.0" }
|
tempfile = { version ="3.9.0"}
|
||||||
test-case = { version = "3.3.1" }
|
test-case = { version = "3.3.1" }
|
||||||
thiserror = { version = "1.0.57" }
|
thiserror = { version = "1.0.51" }
|
||||||
tikv-jemallocator = { version = "0.5.0" }
|
tikv-jemallocator = { version ="0.5.0"}
|
||||||
toml = { version = "0.8.9" }
|
toml = { version = "0.8.8" }
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
tracing-indicatif = { version = "0.3.6" }
|
tracing-indicatif = { version = "0.3.6" }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
tracing-tree = { version = "0.2.4" }
|
|
||||||
typed-arena = { version = "2.0.2" }
|
typed-arena = { version = "2.0.2" }
|
||||||
unic-ucd-category = { version = "0.9" }
|
unic-ucd-category = { version ="0.9"}
|
||||||
unicode-ident = { version = "1.0.12" }
|
unicode-ident = { version = "1.0.12" }
|
||||||
unicode-width = { version = "0.1.11" }
|
unicode-width = { version = "0.1.11" }
|
||||||
unicode_names2 = { version = "1.2.2" }
|
unicode_names2 = { version = "1.2.1" }
|
||||||
ureq = { version = "2.9.6" }
|
ureq = { version = "2.9.1" }
|
||||||
url = { version = "2.5.0" }
|
url = { version = "2.5.0" }
|
||||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||||
walkdir = { version = "2.3.2" }
|
walkdir = { version = "2.3.2" }
|
||||||
wasm-bindgen = { version = "0.2.92" }
|
wasm-bindgen = { version = "0.2.84" }
|
||||||
wasm-bindgen-test = { version = "0.3.40" }
|
wasm-bindgen-test = { version = "0.3.40" }
|
||||||
wild = { version = "2" }
|
wild = { version = "2" }
|
||||||
|
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -7,9 +7,8 @@
|
|||||||
[](https://pypi.python.org/pypi/ruff)
|
[](https://pypi.python.org/pypi/ruff)
|
||||||
[](https://pypi.python.org/pypi/ruff)
|
[](https://pypi.python.org/pypi/ruff)
|
||||||
[](https://github.com/astral-sh/ruff/actions)
|
[](https://github.com/astral-sh/ruff/actions)
|
||||||
[](https://discord.com/invite/astral-sh)
|
|
||||||
|
|
||||||
[**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
|
||||||
|
|
||||||
An extremely fast Python linter and code formatter, written in Rust.
|
An extremely fast Python linter and code formatter, written in Rust.
|
||||||
|
|
||||||
@@ -129,7 +128,7 @@ and with [a variety of other package managers](https://docs.astral.sh/ruff/insta
|
|||||||
To run Ruff as a linter, try any of the following:
|
To run Ruff as a linter, try any of the following:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff check # Lint all files in the current directory (and any subdirectories).
|
ruff check . # Lint all files in the current directory (and any subdirectories).
|
||||||
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
|
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories).
|
||||||
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
|
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`.
|
||||||
ruff check path/to/code/to/file.py # Lint `file.py`.
|
ruff check path/to/code/to/file.py # Lint `file.py`.
|
||||||
@@ -139,7 +138,7 @@ ruff check @arguments.txt # Lint using an input file, treating its con
|
|||||||
Or, to run Ruff as a formatter:
|
Or, to run Ruff as a formatter:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff format # Format all files in the current directory (and any subdirectories).
|
ruff format . # Format all files in the current directory (and any subdirectories).
|
||||||
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
|
ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories).
|
||||||
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
|
ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`.
|
||||||
ruff format path/to/code/to/file.py # Format `file.py`.
|
ruff format path/to/code/to/file.py # Format `file.py`.
|
||||||
@@ -151,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
|||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.3.2
|
rev: v0.1.15
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
@@ -173,7 +172,7 @@ jobs:
|
|||||||
ruff:
|
ruff:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: chartboost/ruff-action@v1
|
- uses: chartboost/ruff-action@v1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -183,9 +182,10 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml`
|
|||||||
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
[_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/)
|
||||||
for a complete list of all configuration options).
|
for a complete list of all configuration options).
|
||||||
|
|
||||||
If left unspecified, Ruff's default configuration is equivalent to the following `ruff.toml` file:
|
If left unspecified, Ruff's default configuration is equivalent to:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
[tool.ruff]
|
||||||
# Exclude a variety of commonly ignored directories.
|
# Exclude a variety of commonly ignored directories.
|
||||||
exclude = [
|
exclude = [
|
||||||
".bzr",
|
".bzr",
|
||||||
@@ -223,7 +223,7 @@ indent-width = 4
|
|||||||
# Assume Python 3.8
|
# Assume Python 3.8
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
|
|
||||||
[lint]
|
[tool.ruff.lint]
|
||||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
select = ["E4", "E7", "E9", "F"]
|
select = ["E4", "E7", "E9", "F"]
|
||||||
ignore = []
|
ignore = []
|
||||||
@@ -235,7 +235,7 @@ unfixable = []
|
|||||||
# Allow unused variables when underscore-prefixed.
|
# Allow unused variables when underscore-prefixed.
|
||||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
[format]
|
[tool.ruff.format]
|
||||||
# Like Black, use double quotes for strings.
|
# Like Black, use double quotes for strings.
|
||||||
quote-style = "double"
|
quote-style = "double"
|
||||||
|
|
||||||
@@ -249,20 +249,11 @@ skip-magic-trailing-comma = false
|
|||||||
line-ending = "auto"
|
line-ending = "auto"
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that, in a `pyproject.toml`, each section header should be prefixed with `tool.ruff`. For
|
Some configuration options can be provided via the command-line, such as those related to
|
||||||
example, `[lint]` should be replaced with `[tool.ruff.lint]`.
|
rule enablement and disablement, file discovery, and logging level:
|
||||||
|
|
||||||
Some configuration options can be provided via dedicated command-line arguments, such as those
|
|
||||||
related to rule enablement and disablement, file discovery, and logging level:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
ruff check --select F401 --select F403 --quiet
|
ruff check path/to/code/ --select F401 --select F403 --quiet
|
||||||
```
|
|
||||||
|
|
||||||
The remaining configuration options can be provided through a catch-all `--config` argument:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
ruff check --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
||||||
@@ -350,14 +341,14 @@ For a complete enumeration of the supported rules, see [_Rules_](https://docs.as
|
|||||||
Contributions are welcome and highly appreciated. To get started, check out the
|
Contributions are welcome and highly appreciated. To get started, check out the
|
||||||
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
|
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
|
||||||
|
|
||||||
You can also join us on [**Discord**](https://discord.com/invite/astral-sh).
|
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues),
|
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues),
|
||||||
or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new).
|
or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new).
|
||||||
|
|
||||||
You can also ask for help on [**Discord**](https://discord.com/invite/astral-sh).
|
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
@@ -387,7 +378,6 @@ Ruff is released under the MIT license.
|
|||||||
|
|
||||||
Ruff is used by a number of major open-source projects and companies, including:
|
Ruff is used by a number of major open-source projects and companies, including:
|
||||||
|
|
||||||
- [Albumentations](https://github.com/albumentations-team/albumentations)
|
|
||||||
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
|
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
|
||||||
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
|
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
|
||||||
- [Apache Airflow](https://github.com/apache/airflow)
|
- [Apache Airflow](https://github.com/apache/airflow)
|
||||||
@@ -412,9 +402,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
|||||||
[Diffusers](https://github.com/huggingface/diffusers))
|
[Diffusers](https://github.com/huggingface/diffusers))
|
||||||
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
|
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
|
||||||
- [Ibis](https://github.com/ibis-project/ibis)
|
- [Ibis](https://github.com/ibis-project/ibis)
|
||||||
- [ivy](https://github.com/unifyai/ivy)
|
|
||||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||||
- [Kraken Tech](https://kraken.tech/)
|
|
||||||
- [LangChain](https://github.com/hwchase17/langchain)
|
- [LangChain](https://github.com/hwchase17/langchain)
|
||||||
- [Litestar](https://litestar.dev/)
|
- [Litestar](https://litestar.dev/)
|
||||||
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
||||||
@@ -444,7 +432,6 @@ Ruff is used by a number of major open-source projects and companies, including:
|
|||||||
- [PyInstaller](https://github.com/pyinstaller/pyinstaller)
|
- [PyInstaller](https://github.com/pyinstaller/pyinstaller)
|
||||||
- [PyMC](https://github.com/pymc-devs/pymc/)
|
- [PyMC](https://github.com/pymc-devs/pymc/)
|
||||||
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
||||||
- [pytest](https://github.com/pytest-dev/pytest)
|
|
||||||
- [PyTorch](https://github.com/pytorch/pytorch)
|
- [PyTorch](https://github.com/pytorch/pytorch)
|
||||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||||
- [Pylint](https://github.com/PyCQA/pylint)
|
- [Pylint](https://github.com/PyCQA/pylint)
|
||||||
@@ -475,7 +462,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
|||||||
|
|
||||||
### Show Your Support
|
### Show Your Support
|
||||||
|
|
||||||
If you're using Ruff, consider adding the Ruff badge to your project's `README.md`:
|
If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/astral-sh/ruff)
|
[](https://github.com/astral-sh/ruff)
|
||||||
@@ -501,6 +488,6 @@ MIT
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a target="_blank" href="https://astral.sh" style="background:none">
|
<a target="_blank" href="https://astral.sh" style="background:none">
|
||||||
<img src="https://raw.githubusercontent.com/astral-sh/ruff/main/assets/svg/Astral.svg" alt="Made by Astral">
|
<img src="https://raw.githubusercontent.com/astral-sh/ruff/main/assets/svg/Astral.svg">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.3.2"
|
version = "0.1.15"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -20,7 +20,6 @@ ruff_macros = { path = "../ruff_macros" }
|
|||||||
ruff_notebook = { path = "../ruff_notebook" }
|
ruff_notebook = { path = "../ruff_notebook" }
|
||||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||||
ruff_server = { path = "../ruff_server" }
|
|
||||||
ruff_source_file = { path = "../ruff_source_file" }
|
ruff_source_file = { path = "../ruff_source_file" }
|
||||||
ruff_text_size = { path = "../ruff_text_size" }
|
ruff_text_size = { path = "../ruff_text_size" }
|
||||||
ruff_workspace = { path = "../ruff_workspace" }
|
ruff_workspace = { path = "../ruff_workspace" }
|
||||||
@@ -49,18 +48,12 @@ serde = { workspace = true }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
shellexpand = { workspace = true }
|
shellexpand = { workspace = true }
|
||||||
strum = { workspace = true, features = [] }
|
strum = { workspace = true, features = [] }
|
||||||
tempfile = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
toml = { workspace = true }
|
|
||||||
tracing = { workspace = true, features = ["log"] }
|
tracing = { workspace = true, features = ["log"] }
|
||||||
tracing-subscriber = { workspace = true, features = ["registry"]}
|
|
||||||
tracing-tree = { workspace = true }
|
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
wild = { workspace = true }
|
wild = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Enable test rules during development
|
|
||||||
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
|
|
||||||
assert_cmd = { workspace = true }
|
assert_cmd = { workspace = true }
|
||||||
# Avoid writing colored snapshots when running tests from the terminal
|
# Avoid writing colored snapshots when running tests from the terminal
|
||||||
colored = { workspace = true, features = ["no-color"]}
|
colored = { workspace = true, features = ["no-color"]}
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
use std::cmp::Ordering;
|
use std::path::PathBuf;
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
|
||||||
use clap::{command, Parser};
|
use clap::{command, Parser};
|
||||||
use colored::Colorize;
|
|
||||||
use path_absolutize::path_dedot;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use toml;
|
|
||||||
|
|
||||||
use ruff_linter::line_width::LineLength;
|
use ruff_linter::line_width::LineLength;
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
@@ -22,58 +12,10 @@ use ruff_linter::settings::types::{
|
|||||||
SerializationFormat, UnsafeFixes,
|
SerializationFormat, UnsafeFixes,
|
||||||
};
|
};
|
||||||
use ruff_linter::{warn_user, RuleParser, RuleSelector, RuleSelectorParser};
|
use ruff_linter::{warn_user, RuleParser, RuleSelector, RuleSelectorParser};
|
||||||
use ruff_source_file::{LineIndex, OneIndexed};
|
|
||||||
use ruff_text_size::TextRange;
|
|
||||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||||
use ruff_workspace::options::{Options, PycodestyleOptions};
|
use ruff_workspace::options::PycodestyleOptions;
|
||||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||||
|
|
||||||
/// All configuration options that can be passed "globally",
|
|
||||||
/// i.e., can be passed to all subcommands
|
|
||||||
#[derive(Debug, Default, Clone, clap::Args)]
|
|
||||||
pub struct GlobalConfigArgs {
|
|
||||||
#[clap(flatten)]
|
|
||||||
log_level_args: LogLevelArgs,
|
|
||||||
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
|
|
||||||
/// or a TOML `<KEY> = <VALUE>` pair
|
|
||||||
/// (such as you might find in a `ruff.toml` configuration file)
|
|
||||||
/// overriding a specific configuration option.
|
|
||||||
/// Overrides of individual settings using this option always take precedence
|
|
||||||
/// over all configuration files, including configuration files that were also
|
|
||||||
/// specified using `--config`.
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
action = clap::ArgAction::Append,
|
|
||||||
value_name = "CONFIG_OPTION",
|
|
||||||
value_parser = ConfigArgumentParser,
|
|
||||||
global = true,
|
|
||||||
help_heading = "Global options",
|
|
||||||
)]
|
|
||||||
pub config: Vec<SingleConfigArgument>,
|
|
||||||
/// Ignore all configuration files.
|
|
||||||
//
|
|
||||||
// Note: We can't mark this as conflicting with `--config` here
|
|
||||||
// as `--config` can be used for specifying configuration overrides
|
|
||||||
// as well as configuration files.
|
|
||||||
// Specifying a configuration file conflicts with `--isolated`;
|
|
||||||
// specifying a configuration override does not.
|
|
||||||
// If a user specifies `ruff check --isolated --config=ruff.toml`,
|
|
||||||
// we emit an error later on, after the initial parsing by clap.
|
|
||||||
#[arg(long, help_heading = "Global options", global = true)]
|
|
||||||
pub isolated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlobalConfigArgs {
|
|
||||||
pub fn log_level(&self) -> LogLevel {
|
|
||||||
LogLevel::from(&self.log_level_args)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn partition(self) -> (LogLevel, Vec<SingleConfigArgument>, bool) {
|
|
||||||
(self.log_level(), self.config, self.isolated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
author,
|
author,
|
||||||
@@ -84,9 +26,9 @@ impl GlobalConfigArgs {
|
|||||||
#[command(version)]
|
#[command(version)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub(crate) command: Command,
|
pub command: Command,
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) global_options: GlobalConfigArgs,
|
pub log_level_args: LogLevelArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -109,6 +51,10 @@ pub enum Command {
|
|||||||
/// Output format
|
/// Output format
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
output_format: HelpFormat,
|
output_format: HelpFormat,
|
||||||
|
|
||||||
|
/// Output format (Deprecated: Use `--output-format` instead).
|
||||||
|
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||||
|
format: Option<HelpFormat>,
|
||||||
},
|
},
|
||||||
/// List or describe the available configuration options.
|
/// List or describe the available configuration options.
|
||||||
Config { option: Option<String> },
|
Config { option: Option<String> },
|
||||||
@@ -117,6 +63,10 @@ pub enum Command {
|
|||||||
/// Output format
|
/// Output format
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
output_format: HelpFormat,
|
output_format: HelpFormat,
|
||||||
|
|
||||||
|
/// Output format (Deprecated: Use `--output-format` instead).
|
||||||
|
#[arg(long, value_enum, conflicts_with = "output_format", hide = true)]
|
||||||
|
format: Option<HelpFormat>,
|
||||||
},
|
},
|
||||||
/// Clear any caches in the current directory and any subdirectories.
|
/// Clear any caches in the current directory and any subdirectories.
|
||||||
#[clap(alias = "--clean")]
|
#[clap(alias = "--clean")]
|
||||||
@@ -126,8 +76,6 @@ pub enum Command {
|
|||||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||||
/// Run the Ruff formatter on the given files or directories.
|
/// Run the Ruff formatter on the given files or directories.
|
||||||
Format(FormatCommand),
|
Format(FormatCommand),
|
||||||
/// Run the language server.
|
|
||||||
Server(ServerCommand),
|
|
||||||
/// Display Ruff's version
|
/// Display Ruff's version
|
||||||
Version {
|
Version {
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
@@ -201,6 +149,10 @@ pub struct CheckCommand {
|
|||||||
preview: bool,
|
preview: bool,
|
||||||
#[clap(long, overrides_with("preview"), hide = true)]
|
#[clap(long, overrides_with("preview"), hide = true)]
|
||||||
no_preview: bool,
|
no_preview: bool,
|
||||||
|
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||||
|
/// configuration.
|
||||||
|
#[arg(long, conflicts_with = "isolated")]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
@@ -332,6 +284,9 @@ pub struct CheckCommand {
|
|||||||
/// Disable cache reads.
|
/// Disable cache reads.
|
||||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
|
/// Ignore all configuration files.
|
||||||
|
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||||
|
pub isolated: bool,
|
||||||
/// Path to the cache directory.
|
/// Path to the cache directory.
|
||||||
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||||
pub cache_dir: Option<PathBuf>,
|
pub cache_dir: Option<PathBuf>,
|
||||||
@@ -423,6 +378,9 @@ pub struct FormatCommand {
|
|||||||
/// difference between the current file and how the formatted file would look like.
|
/// difference between the current file and how the formatted file would look like.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub diff: bool,
|
pub diff: bool,
|
||||||
|
/// Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
|
||||||
|
#[arg(long, conflicts_with = "isolated")]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
|
|
||||||
/// Disable cache reads.
|
/// Disable cache reads.
|
||||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||||
@@ -463,6 +421,9 @@ pub struct FormatCommand {
|
|||||||
/// Set the line-length.
|
/// Set the line-length.
|
||||||
#[arg(long, help_heading = "Format configuration")]
|
#[arg(long, help_heading = "Format configuration")]
|
||||||
pub line_length: Option<LineLength>,
|
pub line_length: Option<LineLength>,
|
||||||
|
/// Ignore all configuration files.
|
||||||
|
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||||
|
pub isolated: bool,
|
||||||
/// The name of the file when passing it through stdin.
|
/// The name of the file when passing it through stdin.
|
||||||
#[arg(long, help_heading = "Miscellaneous")]
|
#[arg(long, help_heading = "Miscellaneous")]
|
||||||
pub stdin_filename: Option<PathBuf>,
|
pub stdin_filename: Option<PathBuf>,
|
||||||
@@ -479,28 +440,6 @@ pub struct FormatCommand {
|
|||||||
preview: bool,
|
preview: bool,
|
||||||
#[clap(long, overrides_with("preview"), hide = true)]
|
#[clap(long, overrides_with("preview"), hide = true)]
|
||||||
no_preview: bool,
|
no_preview: bool,
|
||||||
|
|
||||||
/// When specified, Ruff will try to only format the code in the given range.
|
|
||||||
/// It might be necessary to extend the start backwards or the end forwards, to fully enclose a logical line.
|
|
||||||
/// The `<RANGE>` uses the format `<start_line>:<start_column>-<end_line>:<end_column>`.
|
|
||||||
///
|
|
||||||
/// - The line and column numbers are 1 based.
|
|
||||||
/// - The column specifies the nth-unicode codepoint on that line.
|
|
||||||
/// - The end offset is exclusive.
|
|
||||||
/// - The column numbers are optional. You can write `--range=1-2` instead of `--range=1:1-2:1`.
|
|
||||||
/// - The end position is optional. You can write `--range=2` to format the entire document starting from the second line.
|
|
||||||
/// - The start position is optional. You can write `--range=-3` to format the first three lines of the document.
|
|
||||||
///
|
|
||||||
/// The option can only be used when formatting a single file. Range formatting of notebooks is unsupported.
|
|
||||||
#[clap(long, help_heading = "Editor options", verbatim_doc_comment)]
|
|
||||||
pub range: Option<FormatRange>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, clap::Parser)]
|
|
||||||
pub struct ServerCommand {
|
|
||||||
/// Enable preview mode; required for regular operation
|
|
||||||
#[arg(long)]
|
|
||||||
pub(crate) preview: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||||
@@ -510,7 +449,7 @@ pub enum HelpFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
#[derive(Debug, Default, Clone, clap::Args)]
|
#[derive(Debug, clap::Args)]
|
||||||
pub struct LogLevelArgs {
|
pub struct LogLevelArgs {
|
||||||
/// Enable verbose logging.
|
/// Enable verbose logging.
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -555,111 +494,21 @@ impl From<&LogLevelArgs> for LogLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration-related arguments passed via the CLI.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ConfigArguments {
|
|
||||||
/// Whether the user specified --isolated on the command line
|
|
||||||
pub(crate) isolated: bool,
|
|
||||||
/// The logging level to be used, derived from command-line arguments passed
|
|
||||||
pub(crate) log_level: LogLevel,
|
|
||||||
/// Path to a pyproject.toml or ruff.toml configuration file (etc.).
|
|
||||||
/// Either 0 or 1 configuration file paths may be provided on the command line.
|
|
||||||
config_file: Option<PathBuf>,
|
|
||||||
/// Overrides provided via the `--config "KEY=VALUE"` option.
|
|
||||||
/// An arbitrary number of these overrides may be provided on the command line.
|
|
||||||
/// These overrides take precedence over all configuration files,
|
|
||||||
/// even configuration files that were also specified using `--config`.
|
|
||||||
overrides: Configuration,
|
|
||||||
/// Overrides provided via dedicated flags such as `--line-length` etc.
|
|
||||||
/// These overrides take precedence over all configuration files,
|
|
||||||
/// and also over all overrides specified using any `--config "KEY=VALUE"` flags.
|
|
||||||
per_flag_overrides: ExplicitConfigOverrides,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigArguments {
|
|
||||||
pub fn config_file(&self) -> Option<&Path> {
|
|
||||||
self.config_file.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_cli_arguments(
|
|
||||||
global_options: GlobalConfigArgs,
|
|
||||||
per_flag_overrides: ExplicitConfigOverrides,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
let (log_level, config_options, isolated) = global_options.partition();
|
|
||||||
let mut config_file: Option<PathBuf> = None;
|
|
||||||
let mut overrides = Configuration::default();
|
|
||||||
|
|
||||||
for option in config_options {
|
|
||||||
match option {
|
|
||||||
SingleConfigArgument::SettingsOverride(overridden_option) => {
|
|
||||||
let overridden_option = Arc::try_unwrap(overridden_option)
|
|
||||||
.unwrap_or_else(|option| option.deref().clone());
|
|
||||||
overrides = overrides.combine(Configuration::from_options(
|
|
||||||
overridden_option,
|
|
||||||
None,
|
|
||||||
&path_dedot::CWD,
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
SingleConfigArgument::FilePath(path) => {
|
|
||||||
if isolated {
|
|
||||||
bail!(
|
|
||||||
"\
|
|
||||||
The argument `--config={}` cannot be used with `--isolated`
|
|
||||||
|
|
||||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
|
||||||
as `--isolated` causes ruff to ignore all configuration files.
|
|
||||||
For more information, try `--help`.
|
|
||||||
",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(ref config_file) = config_file {
|
|
||||||
let (first, second) = (config_file.display(), path.display());
|
|
||||||
bail!(
|
|
||||||
"\
|
|
||||||
You cannot specify more than one configuration file on the command line.
|
|
||||||
|
|
||||||
tip: remove either `--config={first}` or `--config={second}`.
|
|
||||||
For more information, try `--help`.
|
|
||||||
"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
config_file = Some(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
isolated,
|
|
||||||
log_level,
|
|
||||||
config_file,
|
|
||||||
overrides,
|
|
||||||
per_flag_overrides,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigurationTransformer for ConfigArguments {
|
|
||||||
fn transform(&self, config: Configuration) -> Configuration {
|
|
||||||
let with_config_overrides = self.overrides.clone().combine(config);
|
|
||||||
self.per_flag_overrides.transform(with_config_overrides)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CheckCommand {
|
impl CheckCommand {
|
||||||
/// Partition the CLI into command-line arguments and configuration
|
/// Partition the CLI into command-line arguments and configuration
|
||||||
/// overrides.
|
/// overrides.
|
||||||
pub fn partition(
|
pub fn partition(self) -> (CheckArguments, CliOverrides) {
|
||||||
self,
|
(
|
||||||
global_options: GlobalConfigArgs,
|
CheckArguments {
|
||||||
) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
|
|
||||||
let check_arguments = CheckArguments {
|
|
||||||
add_noqa: self.add_noqa,
|
add_noqa: self.add_noqa,
|
||||||
|
config: self.config,
|
||||||
diff: self.diff,
|
diff: self.diff,
|
||||||
ecosystem_ci: self.ecosystem_ci,
|
ecosystem_ci: self.ecosystem_ci,
|
||||||
exit_non_zero_on_fix: self.exit_non_zero_on_fix,
|
exit_non_zero_on_fix: self.exit_non_zero_on_fix,
|
||||||
exit_zero: self.exit_zero,
|
exit_zero: self.exit_zero,
|
||||||
files: self.files,
|
files: self.files,
|
||||||
ignore_noqa: self.ignore_noqa,
|
ignore_noqa: self.ignore_noqa,
|
||||||
|
isolated: self.isolated,
|
||||||
no_cache: self.no_cache,
|
no_cache: self.no_cache,
|
||||||
output_file: self.output_file,
|
output_file: self.output_file,
|
||||||
show_files: self.show_files,
|
show_files: self.show_files,
|
||||||
@@ -667,9 +516,8 @@ impl CheckCommand {
|
|||||||
statistics: self.statistics,
|
statistics: self.statistics,
|
||||||
stdin_filename: self.stdin_filename,
|
stdin_filename: self.stdin_filename,
|
||||||
watch: self.watch,
|
watch: self.watch,
|
||||||
};
|
},
|
||||||
|
CliOverrides {
|
||||||
let cli_overrides = ExplicitConfigOverrides {
|
|
||||||
dummy_variable_rgx: self.dummy_variable_rgx,
|
dummy_variable_rgx: self.dummy_variable_rgx,
|
||||||
exclude: self.exclude,
|
exclude: self.exclude,
|
||||||
extend_exclude: self.extend_exclude,
|
extend_exclude: self.extend_exclude,
|
||||||
@@ -683,7 +531,10 @@ impl CheckCommand {
|
|||||||
line_length: self.line_length,
|
line_length: self.line_length,
|
||||||
per_file_ignores: self.per_file_ignores,
|
per_file_ignores: self.per_file_ignores,
|
||||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||||
respect_gitignore: resolve_bool_arg(self.respect_gitignore, self.no_respect_gitignore),
|
respect_gitignore: resolve_bool_arg(
|
||||||
|
self.respect_gitignore,
|
||||||
|
self.no_respect_gitignore,
|
||||||
|
),
|
||||||
select: self.select,
|
select: self.select,
|
||||||
target_version: self.target_version,
|
target_version: self.target_version,
|
||||||
unfixable: self.unfixable,
|
unfixable: self.unfixable,
|
||||||
@@ -701,32 +552,31 @@ impl CheckCommand {
|
|||||||
),
|
),
|
||||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||||
extension: self.extension,
|
extension: self.extension,
|
||||||
};
|
},
|
||||||
|
)
|
||||||
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
|
|
||||||
Ok((check_arguments, config_args))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatCommand {
|
impl FormatCommand {
|
||||||
/// Partition the CLI into command-line arguments and configuration
|
/// Partition the CLI into command-line arguments and configuration
|
||||||
/// overrides.
|
/// overrides.
|
||||||
pub fn partition(
|
pub fn partition(self) -> (FormatArguments, CliOverrides) {
|
||||||
self,
|
(
|
||||||
global_options: GlobalConfigArgs,
|
FormatArguments {
|
||||||
) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
|
||||||
let format_arguments = FormatArguments {
|
|
||||||
check: self.check,
|
check: self.check,
|
||||||
diff: self.diff,
|
diff: self.diff,
|
||||||
|
config: self.config,
|
||||||
files: self.files,
|
files: self.files,
|
||||||
|
isolated: self.isolated,
|
||||||
no_cache: self.no_cache,
|
no_cache: self.no_cache,
|
||||||
stdin_filename: self.stdin_filename,
|
stdin_filename: self.stdin_filename,
|
||||||
range: self.range,
|
},
|
||||||
};
|
CliOverrides {
|
||||||
|
|
||||||
let cli_overrides = ExplicitConfigOverrides {
|
|
||||||
line_length: self.line_length,
|
line_length: self.line_length,
|
||||||
respect_gitignore: resolve_bool_arg(self.respect_gitignore, self.no_respect_gitignore),
|
respect_gitignore: resolve_bool_arg(
|
||||||
|
self.respect_gitignore,
|
||||||
|
self.no_respect_gitignore,
|
||||||
|
),
|
||||||
exclude: self.exclude,
|
exclude: self.exclude,
|
||||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||||
@@ -735,11 +585,9 @@ impl FormatCommand {
|
|||||||
extension: self.extension,
|
extension: self.extension,
|
||||||
|
|
||||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||||
..ExplicitConfigOverrides::default()
|
..CliOverrides::default()
|
||||||
};
|
},
|
||||||
|
)
|
||||||
let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
|
|
||||||
Ok((format_arguments, config_args))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,180 +600,6 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumeration of various ways in which a --config CLI flag
|
|
||||||
/// could be invalid
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum InvalidConfigFlagReason {
|
|
||||||
InvalidToml(toml::de::Error),
|
|
||||||
/// It was valid TOML, but not a valid ruff config file.
|
|
||||||
/// E.g. the user tried to select a rule that doesn't exist,
|
|
||||||
/// or tried to enable a setting that doesn't exist
|
|
||||||
ValidTomlButInvalidRuffSchema(toml::de::Error),
|
|
||||||
/// It was a valid ruff config file, but the user tried to pass a
|
|
||||||
/// value for `extend` as part of the config override.
|
|
||||||
// `extend` is special, because it affects which config files we look at
|
|
||||||
/// in the first place. We currently only parse --config overrides *after*
|
|
||||||
/// we've combined them with all the arguments from the various config files
|
|
||||||
/// that we found, so trying to override `extend` as part of a --config
|
|
||||||
/// override is forbidden.
|
|
||||||
ExtendPassedViaConfigFlag,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InvalidConfigFlagReason {
|
|
||||||
const fn description(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::InvalidToml(_) => "The supplied argument is not valid TOML",
|
|
||||||
Self::ValidTomlButInvalidRuffSchema(_) => {
|
|
||||||
"Could not parse the supplied argument as a `ruff.toml` configuration option"
|
|
||||||
}
|
|
||||||
Self::ExtendPassedViaConfigFlag => "Cannot include `extend` in a --config flag value",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enumeration to represent a single `--config` argument
|
|
||||||
/// passed via the CLI.
|
|
||||||
///
|
|
||||||
/// Using the `--config` flag, users may pass 0 or 1 paths
|
|
||||||
/// to configuration files and an arbitrary number of
|
|
||||||
/// "inline TOML" overrides for specific settings.
|
|
||||||
///
|
|
||||||
/// For example:
|
|
||||||
///
|
|
||||||
/// ```sh
|
|
||||||
/// ruff check --config "path/to/ruff.toml" --config "extend-select=['E501', 'F841']" --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum SingleConfigArgument {
|
|
||||||
FilePath(PathBuf),
|
|
||||||
SettingsOverride(Arc<Options>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ConfigArgumentParser;
|
|
||||||
|
|
||||||
impl ValueParserFactory for SingleConfigArgument {
|
|
||||||
type Parser = ConfigArgumentParser;
|
|
||||||
|
|
||||||
fn value_parser() -> Self::Parser {
|
|
||||||
ConfigArgumentParser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypedValueParser for ConfigArgumentParser {
|
|
||||||
type Value = SingleConfigArgument;
|
|
||||||
|
|
||||||
fn parse_ref(
|
|
||||||
&self,
|
|
||||||
cmd: &clap::Command,
|
|
||||||
arg: Option<&clap::Arg>,
|
|
||||||
value: &std::ffi::OsStr,
|
|
||||||
) -> Result<Self::Value, clap::Error> {
|
|
||||||
// Convert to UTF-8.
|
|
||||||
let Some(value) = value.to_str() else {
|
|
||||||
// But respect non-UTF-8 paths.
|
|
||||||
let path_to_config_file = PathBuf::from(value);
|
|
||||||
if path_to_config_file.is_file() {
|
|
||||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
|
||||||
}
|
|
||||||
return Err(clap::Error::new(clap::error::ErrorKind::InvalidUtf8));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Expand environment variables and tildes.
|
|
||||||
if let Ok(path_to_config_file) =
|
|
||||||
shellexpand::full(value).map(|config| PathBuf::from(&*config))
|
|
||||||
{
|
|
||||||
if path_to_config_file.is_file() {
|
|
||||||
return Ok(SingleConfigArgument::FilePath(path_to_config_file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_parse_error = match toml::Table::from_str(value) {
|
|
||||||
Ok(table) => match table.try_into::<Options>() {
|
|
||||||
Ok(option) => {
|
|
||||||
if option.extend.is_none() {
|
|
||||||
return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option)));
|
|
||||||
}
|
|
||||||
InvalidConfigFlagReason::ExtendPassedViaConfigFlag
|
|
||||||
}
|
|
||||||
Err(underlying_error) => {
|
|
||||||
InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(underlying_error) => InvalidConfigFlagReason::InvalidToml(underlying_error),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
|
||||||
if let Some(arg) = arg {
|
|
||||||
new_error.insert(
|
|
||||||
clap::error::ContextKind::InvalidArg,
|
|
||||||
clap::error::ContextValue::String(arg.to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
new_error.insert(
|
|
||||||
clap::error::ContextKind::InvalidValue,
|
|
||||||
clap::error::ContextValue::String(value.to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let underlying_error = match &config_parse_error {
|
|
||||||
InvalidConfigFlagReason::ExtendPassedViaConfigFlag => {
|
|
||||||
let tip = config_parse_error.description().into();
|
|
||||||
new_error.insert(
|
|
||||||
clap::error::ContextKind::Suggested,
|
|
||||||
clap::error::ContextValue::StyledStrs(vec![tip]),
|
|
||||||
);
|
|
||||||
return Err(new_error);
|
|
||||||
}
|
|
||||||
InvalidConfigFlagReason::InvalidToml(underlying_error)
|
|
||||||
| InvalidConfigFlagReason::ValidTomlButInvalidRuffSchema(underlying_error) => {
|
|
||||||
underlying_error
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// small hack so that multiline tips
|
|
||||||
// have the same indent on the left-hand side:
|
|
||||||
let tip_indent = " ".repeat(" tip: ".len());
|
|
||||||
|
|
||||||
let mut tip = format!(
|
|
||||||
"\
|
|
||||||
A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
{tip_indent}or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
{tip_indent}option"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Here we do some heuristics to try to figure out whether
|
|
||||||
// the user was trying to pass in a path to a configuration file
|
|
||||||
// or some inline TOML.
|
|
||||||
// We want to display the most helpful error to the user as possible.
|
|
||||||
if std::path::Path::new(value)
|
|
||||||
.extension()
|
|
||||||
.map_or(false, |ext| ext.eq_ignore_ascii_case("toml"))
|
|
||||||
{
|
|
||||||
if !value.contains('=') {
|
|
||||||
tip.push_str(&format!(
|
|
||||||
"
|
|
||||||
|
|
||||||
It looks like you were trying to pass a path to a configuration file.
|
|
||||||
The path `{value}` does not point to a configuration file"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if value.contains('=') {
|
|
||||||
tip.push_str(&format!(
|
|
||||||
"\n\n{}:\n\n{underlying_error}",
|
|
||||||
config_parse_error.description()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let tip = tip.trim_end().to_owned().into();
|
|
||||||
|
|
||||||
new_error.insert(
|
|
||||||
clap::error::ContextKind::Suggested,
|
|
||||||
clap::error::ContextValue::StyledStrs(vec![tip]),
|
|
||||||
);
|
|
||||||
|
|
||||||
Err(new_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_output_format(
|
fn resolve_output_format(
|
||||||
output_format: Option<SerializationFormat>,
|
output_format: Option<SerializationFormat>,
|
||||||
show_sources: Option<bool>,
|
show_sources: Option<bool>,
|
||||||
@@ -968,12 +642,14 @@ fn resolve_output_format(
|
|||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct CheckArguments {
|
pub struct CheckArguments {
|
||||||
pub add_noqa: bool,
|
pub add_noqa: bool,
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
pub diff: bool,
|
pub diff: bool,
|
||||||
pub ecosystem_ci: bool,
|
pub ecosystem_ci: bool,
|
||||||
pub exit_non_zero_on_fix: bool,
|
pub exit_non_zero_on_fix: bool,
|
||||||
pub exit_zero: bool,
|
pub exit_zero: bool,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub ignore_noqa: bool,
|
pub ignore_noqa: bool,
|
||||||
|
pub isolated: bool,
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
pub output_file: Option<PathBuf>,
|
pub output_file: Option<PathBuf>,
|
||||||
pub show_files: bool,
|
pub show_files: bool,
|
||||||
@@ -990,234 +666,45 @@ pub struct FormatArguments {
|
|||||||
pub check: bool,
|
pub check: bool,
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
pub diff: bool,
|
pub diff: bool,
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
|
pub isolated: bool,
|
||||||
pub stdin_filename: Option<PathBuf>,
|
pub stdin_filename: Option<PathBuf>,
|
||||||
pub range: Option<FormatRange>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A text range specified by line and column numbers.
|
/// CLI settings that function as configuration overrides.
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct FormatRange {
|
|
||||||
start: LineColumn,
|
|
||||||
end: LineColumn,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FormatRange {
|
|
||||||
/// Converts the line:column range to a byte offset range specific for `source`.
|
|
||||||
///
|
|
||||||
/// Returns an empty range if the start range is past the end of `source`.
|
|
||||||
pub(super) fn to_text_range(self, source: &str, line_index: &LineIndex) -> TextRange {
|
|
||||||
let start_byte_offset = line_index.offset(self.start.line, self.start.column, source);
|
|
||||||
let end_byte_offset = line_index.offset(self.end.line, self.end.column, source);
|
|
||||||
|
|
||||||
TextRange::new(start_byte_offset, end_byte_offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FormatRange {
|
|
||||||
type Err = FormatRangeParseError;
|
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
|
||||||
let (start, end) = value.split_once('-').unwrap_or((value, ""));
|
|
||||||
|
|
||||||
let start = if start.is_empty() {
|
|
||||||
LineColumn::default()
|
|
||||||
} else {
|
|
||||||
start.parse().map_err(FormatRangeParseError::InvalidStart)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let end = if end.is_empty() {
|
|
||||||
LineColumn {
|
|
||||||
line: OneIndexed::MAX,
|
|
||||||
column: OneIndexed::MAX,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
end.parse().map_err(FormatRangeParseError::InvalidEnd)?
|
|
||||||
};
|
|
||||||
|
|
||||||
if start > end {
|
|
||||||
return Err(FormatRangeParseError::StartGreaterThanEnd(start, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(FormatRange { start, end })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum FormatRangeParseError {
|
|
||||||
InvalidStart(LineColumnParseError),
|
|
||||||
InvalidEnd(LineColumnParseError),
|
|
||||||
|
|
||||||
StartGreaterThanEnd(LineColumn, LineColumn),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for FormatRangeParseError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let tip = " tip:".bold().green();
|
|
||||||
match self {
|
|
||||||
FormatRangeParseError::StartGreaterThanEnd(start, end) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"the start position '{start_invalid}' is greater than the end position '{end_invalid}'.\n {tip} Try switching start and end: '{end}-{start}'",
|
|
||||||
start_invalid=start.to_string().bold().yellow(),
|
|
||||||
end_invalid=end.to_string().bold().yellow(),
|
|
||||||
start=start.to_string().green().bold(),
|
|
||||||
end=end.to_string().green().bold()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
FormatRangeParseError::InvalidStart(inner) => inner.write(f, true),
|
|
||||||
FormatRangeParseError::InvalidEnd(inner) => inner.write(f, false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for FormatRangeParseError {}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct LineColumn {
|
|
||||||
pub line: OneIndexed,
|
|
||||||
pub column: OneIndexed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for LineColumn {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{line}:{column}", line = self.line, column = self.column)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LineColumn {
|
|
||||||
fn default() -> Self {
|
|
||||||
LineColumn {
|
|
||||||
line: OneIndexed::MIN,
|
|
||||||
column: OneIndexed::MIN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for LineColumn {
|
|
||||||
#[inline]
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for LineColumn {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
self.line
|
|
||||||
.cmp(&other.line)
|
|
||||||
.then(self.column.cmp(&other.column))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for LineColumn {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.cmp(other) == Ordering::Equal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for LineColumn {}
|
|
||||||
|
|
||||||
impl FromStr for LineColumn {
|
|
||||||
type Err = LineColumnParseError;
|
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
|
||||||
let (line, column) = value.split_once(':').unwrap_or((value, "1"));
|
|
||||||
|
|
||||||
let line: usize = line.parse().map_err(LineColumnParseError::LineParseError)?;
|
|
||||||
let column: usize = column
|
|
||||||
.parse()
|
|
||||||
.map_err(LineColumnParseError::ColumnParseError)?;
|
|
||||||
|
|
||||||
match (OneIndexed::new(line), OneIndexed::new(column)) {
|
|
||||||
(Some(line), Some(column)) => Ok(LineColumn { line, column }),
|
|
||||||
(Some(line), None) => Err(LineColumnParseError::ZeroColumnIndex { line }),
|
|
||||||
(None, Some(column)) => Err(LineColumnParseError::ZeroLineIndex { column }),
|
|
||||||
(None, None) => Err(LineColumnParseError::ZeroLineAndColumnIndex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum LineColumnParseError {
|
|
||||||
ZeroLineIndex { column: OneIndexed },
|
|
||||||
ZeroColumnIndex { line: OneIndexed },
|
|
||||||
ZeroLineAndColumnIndex,
|
|
||||||
LineParseError(std::num::ParseIntError),
|
|
||||||
ColumnParseError(std::num::ParseIntError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineColumnParseError {
|
|
||||||
fn write(&self, f: &mut std::fmt::Formatter, start_range: bool) -> std::fmt::Result {
|
|
||||||
let tip = "tip:".bold().green();
|
|
||||||
|
|
||||||
let range = if start_range { "start" } else { "end" };
|
|
||||||
|
|
||||||
match self {
|
|
||||||
LineColumnParseError::ColumnParseError(inner) => {
|
|
||||||
write!(f, "the {range}s column is not a valid number ({inner})'\n {tip} The format is 'line:column'.")
|
|
||||||
}
|
|
||||||
LineColumnParseError::LineParseError(inner) => {
|
|
||||||
write!(f, "the {range} line is not a valid number ({inner})\n {tip} The format is 'line:column'.")
|
|
||||||
}
|
|
||||||
LineColumnParseError::ZeroColumnIndex { line } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"the {range} column is 0, but it should be 1 or greater.\n {tip} The column numbers start at 1.\n {tip} Try {suggestion} instead.",
|
|
||||||
suggestion=format!("{line}:1").green().bold()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
LineColumnParseError::ZeroLineIndex { column } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"the {range} line is 0, but it should be 1 or greater.\n {tip} The line numbers start at 1.\n {tip} Try {suggestion} instead.",
|
|
||||||
suggestion=format!("1:{column}").green().bold()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
LineColumnParseError::ZeroLineAndColumnIndex => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"the {range} line and column are both 0, but they should be 1 or greater.\n {tip} The line and column numbers start at 1.\n {tip} Try {suggestion} instead.",
|
|
||||||
suggestion="1:1".to_string().green().bold()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration overrides provided via dedicated CLI flags:
|
|
||||||
/// `--line-length`, `--respect-gitignore`, etc.
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
struct ExplicitConfigOverrides {
|
pub struct CliOverrides {
|
||||||
dummy_variable_rgx: Option<Regex>,
|
pub dummy_variable_rgx: Option<Regex>,
|
||||||
exclude: Option<Vec<FilePattern>>,
|
pub exclude: Option<Vec<FilePattern>>,
|
||||||
extend_exclude: Option<Vec<FilePattern>>,
|
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||||
extend_fixable: Option<Vec<RuleSelector>>,
|
pub extend_fixable: Option<Vec<RuleSelector>>,
|
||||||
extend_ignore: Option<Vec<RuleSelector>>,
|
pub extend_ignore: Option<Vec<RuleSelector>>,
|
||||||
extend_select: Option<Vec<RuleSelector>>,
|
pub extend_select: Option<Vec<RuleSelector>>,
|
||||||
extend_unfixable: Option<Vec<RuleSelector>>,
|
pub extend_unfixable: Option<Vec<RuleSelector>>,
|
||||||
fixable: Option<Vec<RuleSelector>>,
|
pub fixable: Option<Vec<RuleSelector>>,
|
||||||
ignore: Option<Vec<RuleSelector>>,
|
pub ignore: Option<Vec<RuleSelector>>,
|
||||||
line_length: Option<LineLength>,
|
pub line_length: Option<LineLength>,
|
||||||
per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||||
extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||||
preview: Option<PreviewMode>,
|
pub preview: Option<PreviewMode>,
|
||||||
respect_gitignore: Option<bool>,
|
pub respect_gitignore: Option<bool>,
|
||||||
select: Option<Vec<RuleSelector>>,
|
pub select: Option<Vec<RuleSelector>>,
|
||||||
target_version: Option<PythonVersion>,
|
pub target_version: Option<PythonVersion>,
|
||||||
unfixable: Option<Vec<RuleSelector>>,
|
pub unfixable: Option<Vec<RuleSelector>>,
|
||||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||||
cache_dir: Option<PathBuf>,
|
pub cache_dir: Option<PathBuf>,
|
||||||
fix: Option<bool>,
|
pub fix: Option<bool>,
|
||||||
fix_only: Option<bool>,
|
pub fix_only: Option<bool>,
|
||||||
unsafe_fixes: Option<UnsafeFixes>,
|
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||||
force_exclude: Option<bool>,
|
pub force_exclude: Option<bool>,
|
||||||
output_format: Option<SerializationFormat>,
|
pub output_format: Option<SerializationFormat>,
|
||||||
show_fixes: Option<bool>,
|
pub show_fixes: Option<bool>,
|
||||||
extension: Option<Vec<ExtensionPair>>,
|
pub extension: Option<Vec<ExtensionPair>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurationTransformer for ExplicitConfigOverrides {
|
impl ConfigurationTransformer for CliOverrides {
|
||||||
fn transform(&self, mut config: Configuration) -> Configuration {
|
fn transform(&self, mut config: Configuration) -> Configuration {
|
||||||
if let Some(cache_dir) = &self.cache_dir {
|
if let Some(cache_dir) = &self.cache_dir {
|
||||||
config.cache_dir = Some(cache_dir.clone());
|
config.cache_dir = Some(cache_dir.clone());
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::io::{self, BufReader, Write};
|
use std::io::{self, BufReader, BufWriter, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@@ -15,7 +15,6 @@ use rayon::iter::ParallelIterator;
|
|||||||
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
use rayon::iter::{IntoParallelIterator, ParallelBridge};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||||
use ruff_diagnostics::{DiagnosticKind, Fix};
|
use ruff_diagnostics::{DiagnosticKind, Fix};
|
||||||
@@ -166,29 +165,15 @@ impl Cache {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the cache to a temporary file first and then rename it for an "atomic" write.
|
let file = File::create(&self.path)
|
||||||
// Protects against data loss if the process is killed during the write and races between different ruff
|
.with_context(|| format!("Failed to create cache file '{}'", self.path.display()))?;
|
||||||
// processes, resulting in a corrupted cache file. https://github.com/astral-sh/ruff/issues/8147#issuecomment-1943345964
|
let writer = BufWriter::new(file);
|
||||||
let mut temp_file =
|
bincode::serialize_into(writer, &self.package).with_context(|| {
|
||||||
NamedTempFile::new_in(self.path.parent().expect("Write path must have a parent"))
|
|
||||||
.context("Failed to create temporary file")?;
|
|
||||||
|
|
||||||
// Serialize to in-memory buffer because hyperfine benchmark showed that it's faster than
|
|
||||||
// using a `BufWriter` and our cache files are small enough that streaming isn't necessary.
|
|
||||||
let serialized =
|
|
||||||
bincode::serialize(&self.package).context("Failed to serialize cache data")?;
|
|
||||||
temp_file
|
|
||||||
.write_all(&serialized)
|
|
||||||
.context("Failed to write serialized cache to temporary file.")?;
|
|
||||||
|
|
||||||
temp_file.persist(&self.path).with_context(|| {
|
|
||||||
format!(
|
format!(
|
||||||
"Failed to rename temporary cache file to {}",
|
"Failed to serialise cache to file '{}'",
|
||||||
self.path.display()
|
self.path.display()
|
||||||
)
|
)
|
||||||
})?;
|
})
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the pending changes without storing the cache to disk.
|
/// Applies the pending changes without storing the cache to disk.
|
||||||
@@ -383,7 +368,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
|||||||
let gitignore_path = path.join(".gitignore");
|
let gitignore_path = path.join(".gitignore");
|
||||||
if !gitignore_path.exists() {
|
if !gitignore_path.exists() {
|
||||||
let mut file = fs::File::create(gitignore_path)?;
|
let mut file = fs::File::create(gitignore_path)?;
|
||||||
file.write_all(b"# Automatically created by ruff.\n*\n")?;
|
file.write_all(b"*")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1065,7 +1050,6 @@ mod tests {
|
|||||||
&self.settings.formatter,
|
&self.settings.formatter,
|
||||||
PySourceType::Python,
|
PySourceType::Python,
|
||||||
FormatMode::Write,
|
FormatMode::Write,
|
||||||
None,
|
|
||||||
Some(cache),
|
Some(cache),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ use ruff_linter::warn_user_once;
|
|||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
/// Add `noqa` directives to a collection of files.
|
/// Add `noqa` directives to a collection of files.
|
||||||
pub(crate) fn add_noqa(
|
pub(crate) fn add_noqa(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
// Collect all the files to check.
|
// Collect all the files to check.
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
debug!("Identified files to lint in: {:?}", duration);
|
debug!("Identified files to lint in: {:?}", duration);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use ruff_workspace::resolver::{
|
|||||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
use crate::cache::{Cache, PackageCacheMap, PackageCaches};
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
use crate::panic::catch_unwind;
|
use crate::panic::catch_unwind;
|
||||||
@@ -34,7 +34,7 @@ use crate::panic::catch_unwind;
|
|||||||
pub(crate) fn check(
|
pub(crate) fn check(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
cache: flags::Cache,
|
cache: flags::Cache,
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
@@ -42,7 +42,7 @@ pub(crate) fn check(
|
|||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
// Collect all the Python files to check.
|
// Collect all the Python files to check.
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||||
debug!("Identified files to lint in: {:?}", start.elapsed());
|
debug!("Identified files to lint in: {:?}", start.elapsed());
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
@@ -233,7 +233,7 @@ mod test {
|
|||||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
use super::check;
|
use super::check;
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ mod test {
|
|||||||
// Notebooks are not included by default
|
// Notebooks are not included by default
|
||||||
&[tempdir.path().to_path_buf(), notebook],
|
&[tempdir.path().to_path_buf(), notebook],
|
||||||
&pyproject_config,
|
&pyproject_config,
|
||||||
&ConfigArguments::default(),
|
&CliOverrides::default(),
|
||||||
flags::Cache::Disabled,
|
flags::Cache::Disabled,
|
||||||
flags::Noqa::Disabled,
|
flags::Noqa::Disabled,
|
||||||
flags::FixMode::Generate,
|
flags::FixMode::Generate,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use ruff_linter::packaging;
|
|||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags;
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
|
|||||||
pub(crate) fn check_stdin(
|
pub(crate) fn check_stdin(
|
||||||
filename: Option<&Path>,
|
filename: Option<&Path>,
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
overrides: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ use ruff_linter::rules::flake8_quotes::settings::Quote;
|
|||||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_python_formatter::{format_module_source, format_range, FormatModuleError, QuoteStyle};
|
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||||
use ruff_source_file::LineIndex;
|
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
|
||||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
use crate::args::{CliOverrides, FormatArguments};
|
||||||
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
|
||||||
use crate::panic::{catch_unwind, PanicError};
|
use crate::panic::{catch_unwind, PanicError};
|
||||||
use crate::resolve::resolve;
|
use crate::resolve::resolve;
|
||||||
@@ -60,25 +59,24 @@ impl FormatMode {
|
|||||||
/// Format a set of files, and return the exit status.
|
/// Format a set of files, and return the exit status.
|
||||||
pub(crate) fn format(
|
pub(crate) fn format(
|
||||||
cli: FormatArguments,
|
cli: FormatArguments,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
|
log_level: LogLevel,
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
let pyproject_config = resolve(
|
||||||
|
cli.isolated,
|
||||||
|
cli.config.as_deref(),
|
||||||
|
overrides,
|
||||||
|
cli.stdin_filename.as_deref(),
|
||||||
|
)?;
|
||||||
let mode = FormatMode::from_cli(&cli);
|
let mode = FormatMode::from_cli(&cli);
|
||||||
let files = resolve_default_files(cli.files, false);
|
let files = resolve_default_files(cli.files, false);
|
||||||
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
|
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, overrides)?;
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
warn_user_once!("No Python files found under the given path(s)");
|
warn_user_once!("No Python files found under the given path(s)");
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.range.is_some() && paths.len() > 1 {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"The `--range` option is only supported when formatting a single file but the specified paths resolve to {} files.",
|
|
||||||
paths.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
warn_incompatible_formatter_settings(&resolver);
|
warn_incompatible_formatter_settings(&resolver);
|
||||||
|
|
||||||
// Discover the package root for each Python file.
|
// Discover the package root for each Python file.
|
||||||
@@ -141,14 +139,7 @@ pub(crate) fn format(
|
|||||||
|
|
||||||
Some(
|
Some(
|
||||||
match catch_unwind(|| {
|
match catch_unwind(|| {
|
||||||
format_path(
|
format_path(path, &settings.formatter, source_type, mode, cache)
|
||||||
path,
|
|
||||||
&settings.formatter,
|
|
||||||
source_type,
|
|
||||||
mode,
|
|
||||||
cli.range,
|
|
||||||
cache,
|
|
||||||
)
|
|
||||||
}) {
|
}) {
|
||||||
Ok(inner) => inner.map(|result| FormatPathResult {
|
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||||
path: resolved_file.path().to_path_buf(),
|
path: resolved_file.path().to_path_buf(),
|
||||||
@@ -197,7 +188,7 @@ pub(crate) fn format(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Report on the formatting changes.
|
// Report on the formatting changes.
|
||||||
if config_arguments.log_level >= LogLevel::Default {
|
if log_level >= LogLevel::Default {
|
||||||
if mode.is_diff() {
|
if mode.is_diff() {
|
||||||
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
// Allow piping the diff to e.g. a file by writing the summary to stderr
|
||||||
results.write_summary(&mut stderr().lock())?;
|
results.write_summary(&mut stderr().lock())?;
|
||||||
@@ -235,7 +226,6 @@ pub(crate) fn format_path(
|
|||||||
settings: &FormatterSettings,
|
settings: &FormatterSettings,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
mode: FormatMode,
|
mode: FormatMode,
|
||||||
range: Option<FormatRange>,
|
|
||||||
cache: Option<&Cache>,
|
cache: Option<&Cache>,
|
||||||
) -> Result<FormatResult, FormatCommandError> {
|
) -> Result<FormatResult, FormatCommandError> {
|
||||||
if let Some(cache) = cache {
|
if let Some(cache) = cache {
|
||||||
@@ -260,12 +250,8 @@ pub(crate) fn format_path(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't write back to the cache if formatting a range.
|
|
||||||
let cache = cache.filter(|_| range.is_none());
|
|
||||||
|
|
||||||
// Format the source.
|
// Format the source.
|
||||||
let format_result = match format_source(&unformatted, source_type, Some(path), settings, range)?
|
let format_result = match format_source(&unformatted, source_type, Some(path), settings)? {
|
||||||
{
|
|
||||||
FormattedSource::Formatted(formatted) => match mode {
|
FormattedSource::Formatted(formatted) => match mode {
|
||||||
FormatMode::Write => {
|
FormatMode::Write => {
|
||||||
let mut writer = File::create(path).map_err(|err| {
|
let mut writer = File::create(path).map_err(|err| {
|
||||||
@@ -333,31 +319,12 @@ pub(crate) fn format_source(
|
|||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
path: Option<&Path>,
|
path: Option<&Path>,
|
||||||
settings: &FormatterSettings,
|
settings: &FormatterSettings,
|
||||||
range: Option<FormatRange>,
|
|
||||||
) -> Result<FormattedSource, FormatCommandError> {
|
) -> Result<FormattedSource, FormatCommandError> {
|
||||||
match &source_kind {
|
match &source_kind {
|
||||||
SourceKind::Python(unformatted) => {
|
SourceKind::Python(unformatted) => {
|
||||||
let options = settings.to_format_options(source_type, unformatted);
|
let options = settings.to_format_options(source_type, unformatted);
|
||||||
|
|
||||||
let formatted = if let Some(range) = range {
|
let formatted = format_module_source(unformatted, options).map_err(|err| {
|
||||||
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.to_string();
|
|
||||||
formatted.replace_range(
|
|
||||||
std::ops::Range::<usize>::from(formatted_range.source_range()),
|
|
||||||
formatted_range.as_code(),
|
|
||||||
);
|
|
||||||
|
|
||||||
formatted
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
|
|
||||||
#[allow(clippy::redundant_closure_for_method_calls)]
|
|
||||||
format_module_source(unformatted, options).map(|formatted| formatted.into_code())
|
|
||||||
};
|
|
||||||
|
|
||||||
let formatted = formatted.map_err(|err| {
|
|
||||||
if let FormatModuleError::ParseError(err) = err {
|
if let FormatModuleError::ParseError(err) = err {
|
||||||
DisplayParseError::from_source_kind(
|
DisplayParseError::from_source_kind(
|
||||||
err,
|
err,
|
||||||
@@ -370,6 +337,7 @@ pub(crate) fn format_source(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let formatted = formatted.into_code();
|
||||||
if formatted.len() == unformatted.len() && formatted == *unformatted {
|
if formatted.len() == unformatted.len() && formatted == *unformatted {
|
||||||
Ok(FormattedSource::Unchanged)
|
Ok(FormattedSource::Unchanged)
|
||||||
} else {
|
} else {
|
||||||
@@ -381,12 +349,6 @@ pub(crate) fn format_source(
|
|||||||
return Ok(FormattedSource::Unchanged);
|
return Ok(FormattedSource::Unchanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
if range.is_some() {
|
|
||||||
return Err(FormatCommandError::RangeFormatNotebook(
|
|
||||||
path.map(Path::to_path_buf),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = settings.to_format_options(source_type, notebook.source_code());
|
let options = settings.to_format_options(source_type, notebook.source_code());
|
||||||
|
|
||||||
let mut output: Option<String> = None;
|
let mut output: Option<String> = None;
|
||||||
@@ -527,7 +489,7 @@ impl<'a> FormatResults<'a> {
|
|||||||
})
|
})
|
||||||
.sorted_unstable_by_key(|(path, _, _)| *path)
|
.sorted_unstable_by_key(|(path, _, _)| *path)
|
||||||
{
|
{
|
||||||
write!(f, "{}", unformatted.diff(formatted, Some(path)).unwrap())?;
|
unformatted.diff(formatted, Some(path), f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -627,7 +589,6 @@ pub(crate) enum FormatCommandError {
|
|||||||
Format(Option<PathBuf>, FormatModuleError),
|
Format(Option<PathBuf>, FormatModuleError),
|
||||||
Write(Option<PathBuf>, SourceError),
|
Write(Option<PathBuf>, SourceError),
|
||||||
Diff(Option<PathBuf>, io::Error),
|
Diff(Option<PathBuf>, io::Error),
|
||||||
RangeFormatNotebook(Option<PathBuf>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatCommandError {
|
impl FormatCommandError {
|
||||||
@@ -645,8 +606,7 @@ impl FormatCommandError {
|
|||||||
| Self::Read(path, _)
|
| Self::Read(path, _)
|
||||||
| Self::Format(path, _)
|
| Self::Format(path, _)
|
||||||
| Self::Write(path, _)
|
| Self::Write(path, _)
|
||||||
| Self::Diff(path, _)
|
| Self::Diff(path, _) => path.as_deref(),
|
||||||
| Self::RangeFormatNotebook(path) => path.as_deref(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -668,10 +628,9 @@ impl Display for FormatCommandError {
|
|||||||
} else {
|
} else {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{header} {error}",
|
"{} {}",
|
||||||
header = "Encountered error:".bold(),
|
"Encountered error:".bold(),
|
||||||
error = err
|
err.io_error()
|
||||||
.io_error()
|
|
||||||
.map_or_else(|| err.to_string(), std::string::ToString::to_string)
|
.map_or_else(|| err.to_string(), std::string::ToString::to_string)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -689,7 +648,7 @@ impl Display for FormatCommandError {
|
|||||||
":".bold()
|
":".bold()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{header} {err}", header = "Failed to read:".bold())
|
write!(f, "{}{} {err}", "Failed to read".bold(), ":".bold())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Write(path, err) => {
|
Self::Write(path, err) => {
|
||||||
@@ -702,7 +661,7 @@ impl Display for FormatCommandError {
|
|||||||
":".bold()
|
":".bold()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{header} {err}", header = "Failed to write:".bold())
|
write!(f, "{}{} {err}", "Failed to write".bold(), ":".bold())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Format(path, err) => {
|
Self::Format(path, err) => {
|
||||||
@@ -715,7 +674,7 @@ impl Display for FormatCommandError {
|
|||||||
":".bold()
|
":".bold()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{header} {err}", header = "Failed to format:".bold())
|
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Diff(path, err) => {
|
Self::Diff(path, err) => {
|
||||||
@@ -730,25 +689,9 @@ impl Display for FormatCommandError {
|
|||||||
} else {
|
} else {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{header} {err}",
|
"{}{} {err}",
|
||||||
header = "Failed to generate diff:".bold(),
|
"Failed to generate diff".bold(),
|
||||||
)
|
":".bold()
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::RangeFormatNotebook(path) => {
|
|
||||||
if let Some(path) = path {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{header}{path}{colon} Range formatting isn't supported for notebooks.",
|
|
||||||
header = "Failed to format ".bold(),
|
|
||||||
path = fs::relativize_path(path).bold(),
|
|
||||||
colon = ":".bold()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{header} Range formatting isn't supported for notebooks",
|
|
||||||
header = "Failed to format:".bold()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ruff_python_ast::{PySourceType, SourceType};
|
|||||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
|
||||||
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
|
use crate::args::{CliOverrides, FormatArguments};
|
||||||
use crate::commands::format::{
|
use crate::commands::format::{
|
||||||
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
|
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
|
||||||
FormatResult, FormattedSource,
|
FormatResult, FormattedSource,
|
||||||
@@ -19,11 +19,13 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
|
|||||||
use crate::ExitStatus;
|
use crate::ExitStatus;
|
||||||
|
|
||||||
/// Run the formatter over a single file, read from `stdin`.
|
/// Run the formatter over a single file, read from `stdin`.
|
||||||
pub(crate) fn format_stdin(
|
pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> Result<ExitStatus> {
|
||||||
cli: &FormatArguments,
|
let pyproject_config = resolve(
|
||||||
config_arguments: &ConfigArguments,
|
cli.isolated,
|
||||||
) -> Result<ExitStatus> {
|
cli.config.as_deref(),
|
||||||
let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
overrides,
|
||||||
|
cli.stdin_filename.as_deref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut resolver = Resolver::new(&pyproject_config);
|
let mut resolver = Resolver::new(&pyproject_config);
|
||||||
warn_incompatible_formatter_settings(&resolver);
|
warn_incompatible_formatter_settings(&resolver);
|
||||||
@@ -32,7 +34,7 @@ pub(crate) fn format_stdin(
|
|||||||
|
|
||||||
if resolver.force_exclude() {
|
if resolver.force_exclude() {
|
||||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||||
if !python_file_at_path(filename, &mut resolver, config_arguments)? {
|
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||||
if mode.is_write() {
|
if mode.is_write() {
|
||||||
parrot_stdin()?;
|
parrot_stdin()?;
|
||||||
}
|
}
|
||||||
@@ -67,7 +69,7 @@ pub(crate) fn format_stdin(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Format the file.
|
// Format the file.
|
||||||
match format_source_code(path, cli.range, settings, source_type, mode) {
|
match format_source_code(path, settings, source_type, mode) {
|
||||||
Ok(result) => match mode {
|
Ok(result) => match mode {
|
||||||
FormatMode::Write => Ok(ExitStatus::Success),
|
FormatMode::Write => Ok(ExitStatus::Success),
|
||||||
FormatMode::Check | FormatMode::Diff => {
|
FormatMode::Check | FormatMode::Diff => {
|
||||||
@@ -88,7 +90,6 @@ pub(crate) fn format_stdin(
|
|||||||
/// Format source code read from `stdin`.
|
/// Format source code read from `stdin`.
|
||||||
fn format_source_code(
|
fn format_source_code(
|
||||||
path: Option<&Path>,
|
path: Option<&Path>,
|
||||||
range: Option<FormatRange>,
|
|
||||||
settings: &FormatterSettings,
|
settings: &FormatterSettings,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
mode: FormatMode,
|
mode: FormatMode,
|
||||||
@@ -106,7 +107,7 @@ fn format_source_code(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Format the source.
|
// Format the source.
|
||||||
let formatted = format_source(&source_kind, source_type, path, settings, range)?;
|
let formatted = format_source(&source_kind, source_type, path, settings)?;
|
||||||
|
|
||||||
match &formatted {
|
match &formatted {
|
||||||
FormattedSource::Formatted(formatted) => match mode {
|
FormattedSource::Formatted(formatted) => match mode {
|
||||||
@@ -118,12 +119,8 @@ fn format_source_code(
|
|||||||
}
|
}
|
||||||
FormatMode::Check => {}
|
FormatMode::Check => {}
|
||||||
FormatMode::Diff => {
|
FormatMode::Diff => {
|
||||||
use std::io::Write;
|
source_kind
|
||||||
write!(
|
.diff(formatted, path, &mut stdout().lock())
|
||||||
&mut stdout().lock(),
|
|
||||||
"{}",
|
|
||||||
source_kind.diff(formatted, path).unwrap()
|
|
||||||
)
|
|
||||||
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ pub(crate) mod format;
|
|||||||
pub(crate) mod format_stdin;
|
pub(crate) mod format_stdin;
|
||||||
pub(crate) mod linter;
|
pub(crate) mod linter;
|
||||||
pub(crate) mod rule;
|
pub(crate) mod rule;
|
||||||
pub(crate) mod server;
|
|
||||||
pub(crate) mod show_files;
|
pub(crate) mod show_files;
|
||||||
pub(crate) mod show_settings;
|
pub(crate) mod show_settings;
|
||||||
pub(crate) mod version;
|
pub(crate) mod version;
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
use crate::ExitStatus;
|
|
||||||
use anyhow::Result;
|
|
||||||
use ruff_linter::logging::LogLevel;
|
|
||||||
use ruff_server::Server;
|
|
||||||
use tracing::{level_filters::LevelFilter, metadata::Level, subscriber::Interest, Metadata};
|
|
||||||
use tracing_subscriber::{
|
|
||||||
layer::{Context, Filter, SubscriberExt},
|
|
||||||
Layer, Registry,
|
|
||||||
};
|
|
||||||
use tracing_tree::time::Uptime;
|
|
||||||
|
|
||||||
pub(crate) fn run_server(preview: bool, log_level: LogLevel) -> Result<ExitStatus> {
|
|
||||||
if !preview {
|
|
||||||
tracing::error!("--preview needs to be provided as a command line argument while the server is still unstable.\nFor example: `ruff server --preview`");
|
|
||||||
return Ok(ExitStatus::Error);
|
|
||||||
}
|
|
||||||
let trace_level = if log_level == LogLevel::Verbose {
|
|
||||||
Level::TRACE
|
|
||||||
} else {
|
|
||||||
Level::DEBUG
|
|
||||||
};
|
|
||||||
|
|
||||||
let subscriber = Registry::default().with(
|
|
||||||
tracing_tree::HierarchicalLayer::default()
|
|
||||||
.with_indent_lines(true)
|
|
||||||
.with_indent_amount(2)
|
|
||||||
.with_bracketed_fields(true)
|
|
||||||
.with_targets(true)
|
|
||||||
.with_writer(|| Box::new(std::io::stderr()))
|
|
||||||
.with_timer(Uptime::default())
|
|
||||||
.with_filter(LoggingFilter { trace_level }),
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::subscriber::set_global_default(subscriber)?;
|
|
||||||
|
|
||||||
let server = Server::new()?;
|
|
||||||
|
|
||||||
server.run().map(|()| ExitStatus::Success)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LoggingFilter {
|
|
||||||
trace_level: Level,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LoggingFilter {
|
|
||||||
fn is_enabled(&self, meta: &Metadata<'_>) -> bool {
|
|
||||||
let filter = if meta.target().starts_with("ruff") {
|
|
||||||
self.trace_level
|
|
||||||
} else {
|
|
||||||
Level::INFO
|
|
||||||
};
|
|
||||||
|
|
||||||
meta.level() <= &filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Filter<S> for LoggingFilter {
|
|
||||||
fn enabled(&self, meta: &Metadata<'_>, _cx: &Context<'_, S>) -> bool {
|
|
||||||
self.is_enabled(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
|
|
||||||
if self.is_enabled(meta) {
|
|
||||||
Interest::always()
|
|
||||||
} else {
|
|
||||||
Interest::never()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_level_hint(&self) -> Option<LevelFilter> {
|
|
||||||
Some(LevelFilter::from_level(self.trace_level))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,17 +7,17 @@ use itertools::Itertools;
|
|||||||
use ruff_linter::warn_user_once;
|
use ruff_linter::warn_user_once;
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
/// Show the list of files to be checked based on current settings.
|
/// Show the list of files to be checked based on current settings.
|
||||||
pub(crate) fn show_files(
|
pub(crate) fn show_files(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
writer: &mut impl Write,
|
writer: &mut impl Write,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Collect all files in the hierarchy.
|
// Collect all files in the hierarchy.
|
||||||
let (paths, _resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
let (paths, _resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
warn_user_once!("No Python files found under the given path(s)");
|
warn_user_once!("No Python files found under the given path(s)");
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ use itertools::Itertools;
|
|||||||
|
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
/// Print the user-facing configuration settings.
|
/// Print the user-facing configuration settings.
|
||||||
pub(crate) fn show_settings(
|
pub(crate) fn show_settings(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
writer: &mut impl Write,
|
writer: &mut impl Write,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Collect all files in the hierarchy.
|
// Collect all files in the hierarchy.
|
||||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
|
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||||
|
|
||||||
// Print the list of files.
|
// Print the list of files.
|
||||||
let Some(path) = paths
|
let Some(path) = paths
|
||||||
@@ -31,9 +31,9 @@ pub(crate) fn show_settings(
|
|||||||
|
|
||||||
let settings = resolver.resolve(&path);
|
let settings = resolver.resolve(&path);
|
||||||
|
|
||||||
writeln!(writer, "Resolved settings for: \"{}\"", path.display())?;
|
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||||
writeln!(writer, "Settings path: \"{}\"", settings_path.display())?;
|
writeln!(writer, "Settings path: {settings_path:?}")?;
|
||||||
}
|
}
|
||||||
write!(writer, "{settings}")?;
|
write!(writer, "{settings}")?;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -290,10 +289,10 @@ pub(crate) fn lint_path(
|
|||||||
match fix_mode {
|
match fix_mode {
|
||||||
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
write!(
|
source_kind.diff(
|
||||||
|
transformed.as_ref(),
|
||||||
|
Some(path),
|
||||||
&mut io::stdout().lock(),
|
&mut io::stdout().lock(),
|
||||||
"{}",
|
|
||||||
source_kind.diff(&transformed, Some(path)).unwrap()
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
@@ -443,11 +442,7 @@ pub(crate) fn lint_stdin(
|
|||||||
flags::FixMode::Diff => {
|
flags::FixMode::Diff => {
|
||||||
// But only write a diff if it's non-empty.
|
// But only write a diff if it's non-empty.
|
||||||
if !fixed.is_empty() {
|
if !fixed.is_empty() {
|
||||||
write!(
|
source_kind.diff(transformed.as_ref(), path, &mut io::stdout().lock())?;
|
||||||
&mut io::stdout().lock(),
|
|
||||||
"{}",
|
|
||||||
source_kind.diff(&transformed, path).unwrap()
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flags::FixMode::Generate => {}
|
flags::FixMode::Generate => {}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use std::process::ExitCode;
|
|||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use args::{GlobalConfigArgs, ServerCommand};
|
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@@ -19,7 +18,7 @@ use ruff_linter::settings::types::SerializationFormat;
|
|||||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
use crate::args::{Args, CheckCommand, Command, FormatCommand};
|
use crate::args::{Args, CheckCommand, Command, FormatCommand, HelpFormat};
|
||||||
use crate::printer::{Flags as PrinterFlags, Printer};
|
use crate::printer::{Flags as PrinterFlags, Printer};
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
@@ -115,12 +114,20 @@ fn resolve_default_files(files: Vec<PathBuf>, is_stdin: bool) -> Vec<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the actual value of the `format` desired from either `output_format`
|
||||||
|
/// or `format`, and warn the user if they're using the deprecated form.
|
||||||
|
fn resolve_help_output_format(output_format: HelpFormat, format: Option<HelpFormat>) -> HelpFormat {
|
||||||
|
if format.is_some() {
|
||||||
|
warn_user!("The `--format` argument is deprecated. Use `--output-format` instead.");
|
||||||
|
}
|
||||||
|
format.unwrap_or(output_format)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
Args {
|
Args {
|
||||||
command,
|
command,
|
||||||
global_options,
|
log_level_args,
|
||||||
}: Args,
|
}: Args,
|
||||||
deprecated_alias_warning: Option<&'static str>,
|
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
{
|
{
|
||||||
let default_panic_hook = std::panic::take_hook();
|
let default_panic_hook = std::panic::take_hook();
|
||||||
@@ -148,11 +155,8 @@ pub fn run(
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
assert!(colored::control::set_virtual_terminal(true).is_ok());
|
||||||
|
|
||||||
set_up_logging(global_options.log_level())?;
|
let log_level = LogLevel::from(&log_level_args);
|
||||||
|
set_up_logging(&log_level)?;
|
||||||
if let Some(deprecated_alias_warning) = deprecated_alias_warning {
|
|
||||||
warn_user!("{}", deprecated_alias_warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
Command::Version { output_format } => {
|
Command::Version { output_format } => {
|
||||||
@@ -162,8 +166,10 @@ pub fn run(
|
|||||||
Command::Rule {
|
Command::Rule {
|
||||||
rule,
|
rule,
|
||||||
all,
|
all,
|
||||||
output_format,
|
format,
|
||||||
|
mut output_format,
|
||||||
} => {
|
} => {
|
||||||
|
output_format = resolve_help_output_format(output_format, format);
|
||||||
if all {
|
if all {
|
||||||
commands::rule::rules(output_format)?;
|
commands::rule::rules(output_format)?;
|
||||||
}
|
}
|
||||||
@@ -176,46 +182,48 @@ pub fn run(
|
|||||||
commands::config::config(option.as_deref())?;
|
commands::config::config(option.as_deref())?;
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Linter { output_format } => {
|
Command::Linter {
|
||||||
|
format,
|
||||||
|
mut output_format,
|
||||||
|
} => {
|
||||||
|
output_format = resolve_help_output_format(output_format, format);
|
||||||
commands::linter::linter(output_format)?;
|
commands::linter::linter(output_format)?;
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Clean => {
|
Command::Clean => {
|
||||||
commands::clean::clean(global_options.log_level())?;
|
commands::clean::clean(log_level)?;
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::GenerateShellCompletion { shell } => {
|
Command::GenerateShellCompletion { shell } => {
|
||||||
shell.generate(&mut Args::command(), &mut stdout());
|
shell.generate(&mut Args::command(), &mut stdout());
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
Command::Check(args) => check(args, global_options),
|
Command::Check(args) => check(args, log_level),
|
||||||
Command::Format(args) => format(args, global_options),
|
Command::Format(args) => format(args, log_level),
|
||||||
Command::Server(args) => server(args, global_options.log_level()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
let (cli, config_arguments) = args.partition(global_options)?;
|
let (cli, overrides) = args.partition();
|
||||||
|
|
||||||
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
|
||||||
commands::format_stdin::format_stdin(&cli, &config_arguments)
|
commands::format_stdin::format_stdin(&cli, &overrides)
|
||||||
} else {
|
} else {
|
||||||
commands::format::format(cli, &config_arguments)
|
commands::format::format(cli, &overrides, log_level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)] // TODO: remove once we start taking arguments from here
|
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||||
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
let (cli, overrides) = args.partition();
|
||||||
let ServerCommand { preview } = args;
|
|
||||||
commands::server::run_server(preview, log_level)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
|
||||||
let (cli, config_arguments) = args.partition(global_options)?;
|
|
||||||
|
|
||||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||||
// files are present, or files are injected from outside of the hierarchy.
|
// files are present, or files are injected from outside of the hierarchy.
|
||||||
let pyproject_config = resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
|
let pyproject_config = resolve::resolve(
|
||||||
|
cli.isolated,
|
||||||
|
cli.config.as_deref(),
|
||||||
|
&overrides,
|
||||||
|
cli.stdin_filename.as_deref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut writer: Box<dyn Write> = match cli.output_file {
|
let mut writer: Box<dyn Write> = match cli.output_file {
|
||||||
Some(path) if !cli.watch => {
|
Some(path) if !cli.watch => {
|
||||||
@@ -231,21 +239,11 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
let files = resolve_default_files(cli.files, is_stdin);
|
let files = resolve_default_files(cli.files, is_stdin);
|
||||||
|
|
||||||
if cli.show_settings {
|
if cli.show_settings {
|
||||||
commands::show_settings::show_settings(
|
commands::show_settings::show_settings(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||||
&files,
|
|
||||||
&pyproject_config,
|
|
||||||
&config_arguments,
|
|
||||||
&mut writer,
|
|
||||||
)?;
|
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
if cli.show_files {
|
if cli.show_files {
|
||||||
commands::show_files::show_files(
|
commands::show_files::show_files(&files, &pyproject_config, &overrides, &mut writer)?;
|
||||||
&files,
|
|
||||||
&pyproject_config,
|
|
||||||
&config_arguments,
|
|
||||||
&mut writer,
|
|
||||||
)?;
|
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,9 +302,8 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
if !fix_mode.is_generate() {
|
if !fix_mode.is_generate() {
|
||||||
warn_user!("--fix is incompatible with --add-noqa.");
|
warn_user!("--fix is incompatible with --add-noqa.");
|
||||||
}
|
}
|
||||||
let modifications =
|
let modifications = commands::add_noqa::add_noqa(&files, &pyproject_config, &overrides)?;
|
||||||
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
|
if modifications > 0 && log_level >= LogLevel::Default {
|
||||||
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
|
|
||||||
let s = if modifications == 1 { "" } else { "s" };
|
let s = if modifications == 1 { "" } else { "s" };
|
||||||
#[allow(clippy::print_stderr)]
|
#[allow(clippy::print_stderr)]
|
||||||
{
|
{
|
||||||
@@ -318,17 +315,13 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
|
|
||||||
let printer = Printer::new(
|
let printer = Printer::new(
|
||||||
output_format,
|
output_format,
|
||||||
config_arguments.log_level,
|
log_level,
|
||||||
fix_mode,
|
fix_mode,
|
||||||
unsafe_fixes,
|
unsafe_fixes,
|
||||||
printer_flags,
|
printer_flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
// the settings should already be combined with the CLI overrides at this point
|
let preview = overrides.preview.unwrap_or_default().is_enabled();
|
||||||
// TODO(jane): let's make this `PreviewMode`
|
|
||||||
// TODO: this should reference the global preview mode once https://github.com/astral-sh/ruff/issues/8232
|
|
||||||
// is resolved.
|
|
||||||
let preview = pyproject_config.settings.linter.preview.is_enabled();
|
|
||||||
|
|
||||||
if cli.watch {
|
if cli.watch {
|
||||||
if output_format != SerializationFormat::default(preview) {
|
if output_format != SerializationFormat::default(preview) {
|
||||||
@@ -355,7 +348,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
let messages = commands::check::check(
|
let messages = commands::check::check(
|
||||||
&files,
|
&files,
|
||||||
&pyproject_config,
|
&pyproject_config,
|
||||||
&config_arguments,
|
&overrides,
|
||||||
cache.into(),
|
cache.into(),
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
@@ -375,8 +368,12 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
};
|
};
|
||||||
|
|
||||||
if matches!(change_kind, ChangeKind::Configuration) {
|
if matches!(change_kind, ChangeKind::Configuration) {
|
||||||
pyproject_config =
|
pyproject_config = resolve::resolve(
|
||||||
resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
|
cli.isolated,
|
||||||
|
cli.config.as_deref(),
|
||||||
|
&overrides,
|
||||||
|
cli.stdin_filename.as_deref(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Printer::clear_screen()?;
|
Printer::clear_screen()?;
|
||||||
printer.write_to_user("File change detected...\n");
|
printer.write_to_user("File change detected...\n");
|
||||||
@@ -384,7 +381,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
let messages = commands::check::check(
|
let messages = commands::check::check(
|
||||||
&files,
|
&files,
|
||||||
&pyproject_config,
|
&pyproject_config,
|
||||||
&config_arguments,
|
&overrides,
|
||||||
cache.into(),
|
cache.into(),
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
@@ -401,7 +398,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
commands::check_stdin::check_stdin(
|
commands::check_stdin::check_stdin(
|
||||||
cli.stdin_filename.map(fs::normalize_path).as_deref(),
|
cli.stdin_filename.map(fs::normalize_path).as_deref(),
|
||||||
&pyproject_config,
|
&pyproject_config,
|
||||||
&config_arguments,
|
&overrides,
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
)?
|
)?
|
||||||
@@ -409,7 +406,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
|
|||||||
commands::check::check(
|
commands::check::check(
|
||||||
&files,
|
&files,
|
||||||
&pyproject_config,
|
&pyproject_config,
|
||||||
&config_arguments,
|
&overrides,
|
||||||
cache.into(),
|
cache.into(),
|
||||||
noqa.into(),
|
noqa.into(),
|
||||||
fix_mode,
|
fix_mode,
|
||||||
|
|||||||
@@ -27,42 +27,26 @@ pub fn main() -> ExitCode {
|
|||||||
let mut args =
|
let mut args =
|
||||||
argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
|
argfile::expand_args_from(args, argfile::parse_fromfile, argfile::PREFIX).unwrap();
|
||||||
|
|
||||||
// We can't use `warn_user` here because logging isn't set up at this point
|
|
||||||
// and we also don't know if the user runs ruff with quiet.
|
|
||||||
// Keep the message and pass it to `run` that is responsible for emitting the warning.
|
|
||||||
let deprecated_alias_warning = match args.get(1).and_then(|arg| arg.to_str()) {
|
|
||||||
// Deprecated aliases that are handled by clap
|
|
||||||
Some("--explain") => {
|
|
||||||
Some("`ruff --explain <RULE>` is deprecated. Use `ruff rule <RULE>` instead.")
|
|
||||||
}
|
|
||||||
Some("--clean") => {
|
|
||||||
Some("`ruff --clean` is deprecated. Use `ruff clean` instead.")
|
|
||||||
}
|
|
||||||
Some("--generate-shell-completion") => {
|
|
||||||
Some("`ruff --generate-shell-completion <SHELL>` is deprecated. Use `ruff generate-shell-completion <SHELL>` instead.")
|
|
||||||
}
|
|
||||||
// Deprecated `ruff` alias to `ruff check`
|
|
||||||
// Clap doesn't support default subcommands but we want to run `check` by
|
// Clap doesn't support default subcommands but we want to run `check` by
|
||||||
// default for convenience and backwards-compatibility, so we just
|
// default for convenience and backwards-compatibility, so we just
|
||||||
// preprocess the arguments accordingly before passing them to Clap.
|
// preprocess the arguments accordingly before passing them to Clap.
|
||||||
Some(arg) if !Command::has_subcommand(arg)
|
if let Some(arg) = args.get(1) {
|
||||||
|
if arg
|
||||||
|
.to_str()
|
||||||
|
.is_some_and(|arg| !Command::has_subcommand(rewrite_legacy_subcommand(arg)))
|
||||||
&& arg != "-h"
|
&& arg != "-h"
|
||||||
&& arg != "--help"
|
&& arg != "--help"
|
||||||
&& arg != "-V"
|
&& arg != "-V"
|
||||||
&& arg != "--version"
|
&& arg != "--version"
|
||||||
&& arg != "help" => {
|
&& arg != "help"
|
||||||
|
|
||||||
{
|
{
|
||||||
args.insert(1, "check".into());
|
args.insert(1, "check".into());
|
||||||
Some("`ruff <path>` is deprecated. Use `ruff check <path>` instead.")
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = Args::parse_from(args);
|
let args = Args::parse_from(args);
|
||||||
|
|
||||||
match run(args, deprecated_alias_warning) {
|
match run(args) {
|
||||||
Ok(code) => code.into(),
|
Ok(code) => code.into(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
#[allow(clippy::print_stderr)]
|
#[allow(clippy::print_stderr)]
|
||||||
@@ -81,3 +65,12 @@ pub fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rewrite_legacy_subcommand(cmd: &str) -> &str {
|
||||||
|
match cmd {
|
||||||
|
"--explain" => "rule",
|
||||||
|
"--clean" => "clean",
|
||||||
|
"--generate-shell-completion" => "generate-shell-completion",
|
||||||
|
cmd => cmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -118,8 +118,6 @@ impl Printer {
|
|||||||
} else if remaining > 0 {
|
} else if remaining > 0 {
|
||||||
let s = if remaining == 1 { "" } else { "s" };
|
let s = if remaining == 1 { "" } else { "s" };
|
||||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||||
} else if remaining == 0 {
|
|
||||||
writeln!(writer, "All checks passed!")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fixables) = fixables {
|
if let Some(fixables) = fixables {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
@@ -11,17 +11,19 @@ use ruff_workspace::resolver::{
|
|||||||
Relativity,
|
Relativity,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::args::ConfigArguments;
|
use crate::args::CliOverrides;
|
||||||
|
|
||||||
/// Resolve the relevant settings strategy and defaults for the current
|
/// Resolve the relevant settings strategy and defaults for the current
|
||||||
/// invocation.
|
/// invocation.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
config_arguments: &ConfigArguments,
|
isolated: bool,
|
||||||
|
config: Option<&Path>,
|
||||||
|
overrides: &CliOverrides,
|
||||||
stdin_filename: Option<&Path>,
|
stdin_filename: Option<&Path>,
|
||||||
) -> Result<PyprojectConfig> {
|
) -> Result<PyprojectConfig> {
|
||||||
// First priority: if we're running in isolated mode, use the default settings.
|
// First priority: if we're running in isolated mode, use the default settings.
|
||||||
if config_arguments.isolated {
|
if isolated {
|
||||||
let config = config_arguments.transform(Configuration::default());
|
let config = overrides.transform(Configuration::default());
|
||||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||||
debug!("Isolated mode, not reading any pyproject.toml");
|
debug!("Isolated mode, not reading any pyproject.toml");
|
||||||
return Ok(PyprojectConfig::new(
|
return Ok(PyprojectConfig::new(
|
||||||
@@ -34,8 +36,12 @@ pub fn resolve(
|
|||||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||||
// current working directory. (This matches ESLint's behavior.)
|
// current working directory. (This matches ESLint's behavior.)
|
||||||
if let Some(pyproject) = config_arguments.config_file() {
|
if let Some(pyproject) = config
|
||||||
let settings = resolve_root_settings(pyproject, Relativity::Cwd, config_arguments)?;
|
.map(|config| config.display().to_string())
|
||||||
|
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
|
||||||
|
.transpose()?
|
||||||
|
{
|
||||||
|
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
|
||||||
debug!(
|
debug!(
|
||||||
"Using user-specified configuration file at: {}",
|
"Using user-specified configuration file at: {}",
|
||||||
pyproject.display()
|
pyproject.display()
|
||||||
@@ -43,7 +49,7 @@ pub fn resolve(
|
|||||||
return Ok(PyprojectConfig::new(
|
return Ok(PyprojectConfig::new(
|
||||||
PyprojectDiscoveryStrategy::Fixed,
|
PyprojectDiscoveryStrategy::Fixed,
|
||||||
settings,
|
settings,
|
||||||
Some(pyproject.to_path_buf()),
|
Some(pyproject),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +67,7 @@ pub fn resolve(
|
|||||||
"Using configuration file (via parent) at: {}",
|
"Using configuration file (via parent) at: {}",
|
||||||
pyproject.display()
|
pyproject.display()
|
||||||
);
|
);
|
||||||
let settings = resolve_root_settings(&pyproject, Relativity::Parent, config_arguments)?;
|
let settings = resolve_root_settings(&pyproject, Relativity::Parent, overrides)?;
|
||||||
return Ok(PyprojectConfig::new(
|
return Ok(PyprojectConfig::new(
|
||||||
PyprojectDiscoveryStrategy::Hierarchical,
|
PyprojectDiscoveryStrategy::Hierarchical,
|
||||||
settings,
|
settings,
|
||||||
@@ -78,7 +84,7 @@ pub fn resolve(
|
|||||||
"Using configuration file (via cwd) at: {}",
|
"Using configuration file (via cwd) at: {}",
|
||||||
pyproject.display()
|
pyproject.display()
|
||||||
);
|
);
|
||||||
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
|
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
|
||||||
return Ok(PyprojectConfig::new(
|
return Ok(PyprojectConfig::new(
|
||||||
PyprojectDiscoveryStrategy::Hierarchical,
|
PyprojectDiscoveryStrategy::Hierarchical,
|
||||||
settings,
|
settings,
|
||||||
@@ -91,7 +97,7 @@ pub fn resolve(
|
|||||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||||
// as the "default" settings.)
|
// as the "default" settings.)
|
||||||
debug!("Using Ruff default settings");
|
debug!("Using Ruff default settings");
|
||||||
let config = config_arguments.transform(Configuration::default());
|
let config = overrides.transform(Configuration::default());
|
||||||
let settings = config.into_settings(&path_dedot::CWD)?;
|
let settings = config.into_settings(&path_dedot::CWD)?;
|
||||||
Ok(PyprojectConfig::new(
|
Ok(PyprojectConfig::new(
|
||||||
PyprojectDiscoveryStrategy::Hierarchical,
|
PyprojectDiscoveryStrategy::Hierarchical,
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ const STDIN: &str = "l = 1";
|
|||||||
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
|
fn ruff_check(show_source: Option<bool>, output_format: Option<String>) -> Command {
|
||||||
let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
|
let mut cmd = Command::new(get_cargo_bin(BIN_NAME));
|
||||||
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
|
let output_format = output_format.unwrap_or(format!("{}", SerializationFormat::default(false)));
|
||||||
cmd.arg("check")
|
cmd.arg("--output-format");
|
||||||
.arg("--output-format")
|
cmd.arg(output_format);
|
||||||
.arg(output_format)
|
cmd.arg("--no-cache");
|
||||||
.arg("--no-cache");
|
|
||||||
match show_source {
|
match show_source {
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
cmd.arg("--show-source");
|
cmd.arg("--show-source");
|
||||||
|
|||||||
@@ -7,15 +7,10 @@ use std::str;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||||
use regex::escape;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
const BIN_NAME: &str = "ruff";
|
const BIN_NAME: &str = "ruff";
|
||||||
|
|
||||||
fn tempdir_filter(tempdir: &TempDir) -> String {
|
|
||||||
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_options() {
|
fn default_options() {
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
@@ -23,7 +18,7 @@ fn default_options() {
|
|||||||
.arg("-")
|
.arg("-")
|
||||||
.pass_stdin(r#"
|
.pass_stdin(r#"
|
||||||
def foo(arg1, arg2,):
|
def foo(arg1, arg2,):
|
||||||
print('Shouldn\'t change quotes')
|
print('Should\'t change quotes')
|
||||||
|
|
||||||
|
|
||||||
if condition:
|
if condition:
|
||||||
@@ -38,7 +33,7 @@ if condition:
|
|||||||
arg1,
|
arg1,
|
||||||
arg2,
|
arg2,
|
||||||
):
|
):
|
||||||
print("Shouldn't change quotes")
|
print("Should't change quotes")
|
||||||
|
|
||||||
|
|
||||||
if condition:
|
if condition:
|
||||||
@@ -95,182 +90,6 @@ fn format_warn_stdin_filename_with_files() {
|
|||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonexistent_config_file() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--config", "foo.toml", "."]), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'foo.toml' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
It looks like you were trying to pass a path to a configuration file.
|
|
||||||
The path `foo.toml` does not point to a configuration file
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_override_rejected_if_invalid_toml() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--config", "foo = bar", "."]), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
The supplied argument is not valid TOML:
|
|
||||||
|
|
||||||
TOML parse error at line 1, column 7
|
|
||||||
|
|
|
||||||
1 | foo = bar
|
|
||||||
| ^
|
|
||||||
invalid string
|
|
||||||
expected `"`, `'`
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn too_many_config_files() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
|
||||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
|
||||||
fs::File::create(&ruff_dot_toml)?;
|
|
||||||
fs::File::create(&ruff2_dot_toml)?;
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.arg("format")
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_dot_toml)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff2_dot_toml)
|
|
||||||
.arg("."), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: You cannot specify more than one configuration file on the command line.
|
|
||||||
|
|
||||||
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
|
|
||||||
For more information, try `--help`.
|
|
||||||
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_file_and_isolated() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::File::create(&ruff_dot_toml)?;
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.arg("format")
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_dot_toml)
|
|
||||||
.arg("--isolated")
|
|
||||||
.arg("."), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
|
|
||||||
|
|
||||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
|
||||||
as `--isolated` causes ruff to ignore all configuration files.
|
|
||||||
For more information, try `--help`.
|
|
||||||
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_override_via_cli() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(&ruff_toml, "line-length = 100")?;
|
|
||||||
let fixture = r#"
|
|
||||||
def foo():
|
|
||||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
|
||||||
|
|
||||||
"#;
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.arg("format")
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
// This overrides the long line length set in the config file
|
|
||||||
.args(["--config", "line-length=80"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(fixture), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
def foo():
|
|
||||||
print(
|
|
||||||
"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
|
|
||||||
)
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_doubly_overridden_via_cli() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(&ruff_toml, "line-length = 70")?;
|
|
||||||
let fixture = r#"
|
|
||||||
def foo():
|
|
||||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
|
||||||
|
|
||||||
"#;
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.arg("format")
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
// This overrides the long line length set in the config file...
|
|
||||||
.args(["--config", "line-length=80"])
|
|
||||||
// ...but this overrides them both:
|
|
||||||
.args(["--line-length", "100"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(fixture), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
def foo():
|
|
||||||
print("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string")
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_options() -> Result<()> {
|
fn format_options() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
@@ -358,52 +177,58 @@ def f(x):
|
|||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
"#), @r###"
|
"#), @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
def f(x):
|
def f(x):
|
||||||
"""
|
"""
|
||||||
Something about `f`. And an example:
|
Something about `f`. And an example:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
foo, bar, quux = (
|
(
|
||||||
this_is_a_long_line(
|
foo,
|
||||||
|
bar,
|
||||||
|
quux,
|
||||||
|
) = this_is_a_long_line(
|
||||||
lion,
|
lion,
|
||||||
hippo,
|
hippo,
|
||||||
lemur,
|
lemur,
|
||||||
bear,
|
bear,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
Another example:
|
Another example:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
foo, bar, quux = (
|
(
|
||||||
this_is_a_long_line(
|
foo,
|
||||||
|
bar,
|
||||||
|
quux,
|
||||||
|
) = this_is_a_long_line(
|
||||||
lion,
|
lion,
|
||||||
hippo,
|
hippo,
|
||||||
lemur,
|
lemur,
|
||||||
bear,
|
bear,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And another:
|
And another:
|
||||||
|
|
||||||
>>> foo, bar, quux = (
|
>>> (
|
||||||
... this_is_a_long_line(
|
... foo,
|
||||||
|
... bar,
|
||||||
|
... quux,
|
||||||
|
... ) = this_is_a_long_line(
|
||||||
... lion,
|
... lion,
|
||||||
... hippo,
|
... hippo,
|
||||||
... lemur,
|
... lemur,
|
||||||
... bear,
|
... bear,
|
||||||
... )
|
... )
|
||||||
... )
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,9 +508,11 @@ if __name__ == '__main__':
|
|||||||
say_hy("dear Ruff contributor")
|
say_hy("dear Ruff contributor")
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
- 'ignore' -> 'lint.ignore'
|
- 'ignore' -> 'lint.ignore'
|
||||||
|
|
||||||
|
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -724,9 +551,11 @@ if __name__ == '__main__':
|
|||||||
say_hy("dear Ruff contributor")
|
say_hy("dear Ruff contributor")
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
- 'ignore' -> 'lint.ignore'
|
- 'ignore' -> 'lint.ignore'
|
||||||
|
|
||||||
|
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1719,322 +1548,3 @@ include = ["*.ipy"]
|
|||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_formatting() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=2:8-2:14"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
def foo(
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_formatting_unicode() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=2:21-3"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1="👋🏽" ): print("Format this" )
|
|
||||||
"#), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
def foo(arg1="👋🏽" ):
|
|
||||||
print("Format this")
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_formatting_multiple_files() -> std::io::Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let file1 = tempdir.path().join("file1.py");
|
|
||||||
|
|
||||||
fs::write(
|
|
||||||
&file1,
|
|
||||||
r#"
|
|
||||||
def file1(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let file2 = tempdir.path().join("file2.py");
|
|
||||||
|
|
||||||
fs::write(
|
|
||||||
&file2,
|
|
||||||
r#"
|
|
||||||
def file2(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--range=1:8-1:15"])
|
|
||||||
.arg(file1)
|
|
||||||
.arg(file2), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: The `--range` option is only supported when formatting a single file but the specified paths resolve to 2 files.
|
|
||||||
"###);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_formatting_out_of_bounds() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=100:40-200:1"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_start_larger_than_end() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=90-50"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value '90-50' for '--range <RANGE>': the start position '90:1' is greater than the end position '50:1'.
|
|
||||||
tip: Try switching start and end: '50:1-90:1'
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_line_numbers_only() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=2-3"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
def foo(
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
):
|
|
||||||
print("Shouldn't format this" )
|
|
||||||
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_start_only() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=3"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Should format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Should format this")
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_end_only() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=-3"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Should format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
def foo(
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
):
|
|
||||||
print("Should format this" )
|
|
||||||
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_missing_line() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=1-:20"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Should format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value '1-:20' for '--range <RANGE>': the end line is not a valid number (cannot parse integer from empty string)
|
|
||||||
tip: The format is 'line:column'.
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn zero_line_number() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=0:2"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Should format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value '0:2' for '--range <RANGE>': the start line is 0, but it should be 1 or greater.
|
|
||||||
tip: The line numbers start at 1.
|
|
||||||
tip: Try 1:2 instead.
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn column_and_line_zero() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--stdin-filename", "test.py", "--range=0:0"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
def foo(arg1, arg2,):
|
|
||||||
print("Should format this" )
|
|
||||||
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value '0:0' for '--range <RANGE>': the start line and column are both 0, but they should be 1 or greater.
|
|
||||||
tip: The line and column numbers start at 1.
|
|
||||||
tip: Try 1:1 instead.
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn range_formatting_notebook() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(["format", "--isolated", "--no-cache", "--stdin-filename", "main.ipynb", "--range=1-2"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"x=1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: Failed to format main.ipynb: Range formatting isn't supported for notebooks.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#![cfg(not(target_family = "wasm"))]
|
#![cfg(not(target_family = "wasm"))]
|
||||||
|
|
||||||
use regex::escape;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str;
|
use std::str;
|
||||||
@@ -12,11 +11,7 @@ use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
const BIN_NAME: &str = "ruff";
|
const BIN_NAME: &str = "ruff";
|
||||||
const STDIN_BASE_OPTIONS: &[&str] = &["check", "--no-cache", "--output-format", "concise"];
|
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "concise"];
|
||||||
|
|
||||||
fn tempdir_filter(tempdir: &TempDir) -> String {
|
|
||||||
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn top_level_options() -> Result<()> {
|
fn top_level_options() -> Result<()> {
|
||||||
@@ -32,9 +27,6 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
@@ -52,12 +44,12 @@ inline-quotes = "single"
|
|||||||
[*] 2 fixable with the `--fix` option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
- 'flake8-quotes' -> 'lint.flake8-quotes'
|
- 'flake8-quotes' -> 'lint.flake8-quotes'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +68,6 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
@@ -96,8 +85,6 @@ inline-quotes = "single"
|
|||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +103,6 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
@@ -135,11 +119,11 @@ inline-quotes = "single"
|
|||||||
[*] 2 fixable with the `--fix` option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,9 +146,6 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
@@ -181,11 +162,11 @@ inline-quotes = "single"
|
|||||||
[*] 2 fixable with the `--fix` option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'flake8-quotes' -> 'lint.flake8-quotes'
|
- 'flake8-quotes' -> 'lint.flake8-quotes'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,11 +222,9 @@ OTHER = "OTHER"
|
|||||||
|
|
||||||
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
|
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.current_dir(tempdir.path())
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
|
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
|
||||||
@@ -262,11 +241,11 @@ OTHER = "OTHER"
|
|||||||
[*] 3 fixable with the `--fix` option.
|
[*] 3 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,11 +266,9 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.current_dir(tempdir.path())
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
.args(["--stdin-filename", "generated.py"])
|
.args(["--stdin-filename", "generated.py"])
|
||||||
@@ -311,11 +288,11 @@ if __name__ == "__main__":
|
|||||||
[*] 2 fixable with the `--fix` option.
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +311,6 @@ max-line-length = 100
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
@@ -356,12 +330,12 @@ _ = "---------------------------------------------------------------------------
|
|||||||
Found 1 error.
|
Found 1 error.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `[TMP]/ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'select' -> 'lint.select'
|
- 'select' -> 'lint.select'
|
||||||
- 'pycodestyle' -> 'lint.pycodestyle'
|
- 'pycodestyle' -> 'lint.pycodestyle'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,11 +353,9 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.current_dir(tempdir.path())
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
.args(["--stdin-filename", "generated.py"])
|
.args(["--stdin-filename", "generated.py"])
|
||||||
@@ -405,11 +377,11 @@ if __name__ == "__main__":
|
|||||||
[*] 1 fixable with the `--fix` option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,11 +399,9 @@ inline-quotes = "single"
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.current_dir(tempdir.path())
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
.args(["--stdin-filename", "generated.py"])
|
.args(["--stdin-filename", "generated.py"])
|
||||||
@@ -453,11 +423,11 @@ if __name__ == "__main__":
|
|||||||
[*] 1 fixable with the `--fix` option.
|
[*] 1 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `ruff.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'extend-select' -> 'lint.extend-select'
|
- 'extend-select' -> 'lint.extend-select'
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,380 +456,21 @@ ignore = ["D203", "D212"]
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.current_dir(sub_dir)
|
.current_dir(sub_dir)
|
||||||
|
.arg("check")
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
, @r###"
|
, @r###"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
All checks passed!
|
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: No Python files found under the given path(s)
|
warning: No Python files found under the given path(s)
|
||||||
"###);
|
"###);
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonexistent_config_file() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.args(["--config", "foo.toml", "."]), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'foo.toml' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
It looks like you were trying to pass a path to a configuration file.
|
|
||||||
The path `foo.toml` does not point to a configuration file
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_override_rejected_if_invalid_toml() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.args(["--config", "foo = bar", "."]), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
The supplied argument is not valid TOML:
|
|
||||||
|
|
||||||
TOML parse error at line 1, column 7
|
|
||||||
|
|
|
||||||
1 | foo = bar
|
|
||||||
| ^
|
|
||||||
invalid string
|
|
||||||
expected `"`, `'`
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn too_many_config_files() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
|
||||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
|
||||||
fs::File::create(&ruff_dot_toml)?;
|
|
||||||
fs::File::create(&ruff2_dot_toml)?;
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_dot_toml)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff2_dot_toml)
|
|
||||||
.arg("."), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: You cannot specify more than one configuration file on the command line.
|
|
||||||
|
|
||||||
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
|
|
||||||
For more information, try `--help`.
|
|
||||||
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extend_passed_via_config_argument() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.args(["--config", "extend = 'foo.toml'", "."]), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'extend = 'foo.toml'' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: Cannot include `extend` in a --config flag value
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_file_and_isolated() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::File::create(&ruff_dot_toml)?;
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_dot_toml)
|
|
||||||
.arg("--isolated")
|
|
||||||
.arg("."), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
|
|
||||||
|
|
||||||
tip: You cannot specify a configuration file and also specify `--isolated`,
|
|
||||||
as `--isolated` causes ruff to ignore all configuration files.
|
|
||||||
For more information, try `--help`.
|
|
||||||
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_override_via_cli() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
r#"
|
|
||||||
line-length = 100
|
|
||||||
|
|
||||||
[lint]
|
|
||||||
select = ["I"]
|
|
||||||
|
|
||||||
[lint.isort]
|
|
||||||
combine-as-imports = true
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
let fixture = r#"
|
|
||||||
from foo import (
|
|
||||||
aaaaaaaaaaaaaaaaaaa,
|
|
||||||
bbbbbbbbbbb as bbbbbbbbbbbbbbbb,
|
|
||||||
cccccccccccccccc,
|
|
||||||
ddddddddddd as ddddddddddddd,
|
|
||||||
eeeeeeeeeeeeeee,
|
|
||||||
ffffffffffff as ffffffffffffff,
|
|
||||||
ggggggggggggg,
|
|
||||||
hhhhhhh as hhhhhhhhhhh,
|
|
||||||
iiiiiiiiiiiiii,
|
|
||||||
jjjjjjjjjjjjj as jjjjjj,
|
|
||||||
)
|
|
||||||
|
|
||||||
x = "longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"
|
|
||||||
"#;
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.args(["--config", "line-length=90"])
|
|
||||||
.args(["--config", "lint.extend-select=['E501', 'F841']"])
|
|
||||||
.args(["--config", "lint.isort.combine-as-imports = false"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(fixture), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:2:1: I001 [*] Import block is un-sorted or un-formatted
|
|
||||||
-:15:91: E501 Line too long (97 > 90)
|
|
||||||
Found 2 errors.
|
|
||||||
[*] 1 fixable with the `--fix` option.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn valid_toml_but_nonexistent_option_provided_via_config_argument() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.args([".", "--config", "extend-select=['F481']"]), // No such code as F481!
|
|
||||||
@r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'extend-select=['F481']' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
Could not parse the supplied argument as a `ruff.toml` configuration option:
|
|
||||||
|
|
||||||
Unknown rule selector: `F481`
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn each_toml_option_requires_a_new_flag_1() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
// commas can't be used to delimit different config overrides;
|
|
||||||
// you need a new --config flag for each override
|
|
||||||
.args([".", "--config", "extend-select=['F841'], line-length=90"]),
|
|
||||||
@r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'extend-select=['F841'], line-length=90' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
The supplied argument is not valid TOML:
|
|
||||||
|
|
||||||
TOML parse error at line 1, column 23
|
|
||||||
|
|
|
||||||
1 | extend-select=['F841'], line-length=90
|
|
||||||
| ^
|
|
||||||
expected newline, `#`
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn each_toml_option_requires_a_new_flag_2() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
// spaces *also* can't be used to delimit different config overrides;
|
|
||||||
// you need a new --config flag for each override
|
|
||||||
.args([".", "--config", "extend-select=['F841'] line-length=90"]),
|
|
||||||
@r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'extend-select=['F841'] line-length=90' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
The supplied argument is not valid TOML:
|
|
||||||
|
|
||||||
TOML parse error at line 1, column 24
|
|
||||||
|
|
|
||||||
1 | extend-select=['F841'] line-length=90
|
|
||||||
| ^
|
|
||||||
expected newline, `#`
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_doubly_overridden_via_cli() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
r#"
|
|
||||||
line-length = 100
|
|
||||||
|
|
||||||
[lint]
|
|
||||||
select=["E501"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
let fixture = "x = 'longer_than_90_charactersssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss'";
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
// The --line-length flag takes priority over both the config file
|
|
||||||
// and the `--config="line-length=110"` flag,
|
|
||||||
// despite them both being specified after this flag on the command line:
|
|
||||||
.args(["--line-length", "90"])
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.args(["--config", "line-length=110"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(fixture), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:1:91: E501 Line too long (97 > 90)
|
|
||||||
Found 1 error.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn complex_config_setting_overridden_via_cli() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(&ruff_toml, "lint.select = ['N801']")?;
|
|
||||||
let fixture = "class violates_n801: pass";
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.args(["--config", "lint.per-file-ignores = {'generated.py' = ['N801']}"])
|
|
||||||
.args(["--stdin-filename", "generated.py"])
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(fixture), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
All checks passed!
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deprecated_config_option_overridden_via_cli() {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.args(["--config", "select=['N801']", "-"])
|
|
||||||
.pass_stdin("class lowercase: ..."),
|
|
||||||
@r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:1:7: N801 Class name `lowercase` should use CapWords convention
|
|
||||||
Found 1 error.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your `--config` CLI arguments:
|
|
||||||
- 'select' -> 'lint.select'
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extension() -> Result<()> {
|
fn extension() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
@@ -913,11 +524,9 @@ include = ["*.ipy"]
|
|||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.current_dir(tempdir.path())
|
.current_dir(tempdir.path())
|
||||||
|
.arg("check")
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||||
.args(["--extension", "ipy:ipynb"])
|
.args(["--extension", "ipy:ipynb"])
|
||||||
@@ -931,240 +540,5 @@ include = ["*.ipy"]
|
|||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn file_noqa_external() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
r#"
|
|
||||||
[lint]
|
|
||||||
external = ["AAA"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
# flake8: noqa: AAA101, BBB102
|
|
||||||
import os
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:3:8: F401 [*] `os` imported but unused
|
|
||||||
Found 1 error.
|
|
||||||
[*] 1 fixable with the `--fix` option.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
warning: Invalid rule code provided to `# ruff: noqa` at -:2: BBB102
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn required_version_exact_mismatch() -> Result<()> {
|
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
r#"
|
|
||||||
required-version = "0.1.0"
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
import os
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: Required version `==0.1.0` does not match the running version `[VERSION]`
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn required_version_exact_match() -> Result<()> {
|
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
format!(
|
|
||||||
r#"
|
|
||||||
required-version = "{version}"
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
import os
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:2:8: F401 [*] `os` imported but unused
|
|
||||||
Found 1 error.
|
|
||||||
[*] 1 fixable with the `--fix` option.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn required_version_bound_mismatch() -> Result<()> {
|
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
format!(
|
|
||||||
r#"
|
|
||||||
required-version = ">{version}"
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
import os
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
ruff failed
|
|
||||||
Cause: Required version `>[VERSION]` does not match the running version `[VERSION]`
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn required_version_bound_match() -> Result<()> {
|
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
&ruff_toml,
|
|
||||||
r#"
|
|
||||||
required-version = ">=0.1.0"
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
insta::with_settings!({
|
|
||||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/"), (version, "[VERSION]")]
|
|
||||||
}, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_toml)
|
|
||||||
.arg("-")
|
|
||||||
.pass_stdin(r#"
|
|
||||||
import os
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:2:8: F401 [*] `os` imported but unused
|
|
||||||
Found 1 error.
|
|
||||||
[*] 1 fixable with the `--fix` option.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expand environment variables in `--config` paths provided via the CLI.
|
|
||||||
#[test]
|
|
||||||
fn config_expand() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::write(
|
|
||||||
ruff_toml,
|
|
||||||
r#"
|
|
||||||
[lint]
|
|
||||||
select = ["F"]
|
|
||||||
ignore = ["F841"]
|
|
||||||
"#,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.args(STDIN_BASE_OPTIONS)
|
|
||||||
.arg("--config")
|
|
||||||
.arg("${NAME}.toml")
|
|
||||||
.env("NAME", "ruff")
|
|
||||||
.arg("-")
|
|
||||||
.current_dir(tempdir.path())
|
|
||||||
.pass_stdin(r#"
|
|
||||||
import os
|
|
||||||
|
|
||||||
def func():
|
|
||||||
x = 1
|
|
||||||
"#), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 1
|
|
||||||
----- stdout -----
|
|
||||||
-:2:8: F401 [*] `os` imported but unused
|
|
||||||
Found 1 error.
|
|
||||||
[*] 1 fixable with the `--fix` option.
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ fn check_project_include_defaults() {
|
|||||||
[BASEPATH]/include-test/subdirectory/c.py
|
[BASEPATH]/include-test/subdirectory/c.py
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `nested-project/pyproject.toml`:
|
warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in your configuration:
|
||||||
- 'select' -> 'lint.select'
|
- 'select' -> 'lint.select'
|
||||||
|
|
||||||
|
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,29 +4,25 @@ use std::process::Command;
|
|||||||
|
|
||||||
const BIN_NAME: &str = "ruff";
|
const BIN_NAME: &str = "ruff";
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||||
|
("\"[^\\*\"]*/pyproject.toml", "\"[BASEPATH]/pyproject.toml"),
|
||||||
|
("\".*/crates", "\"[BASEPATH]/crates"),
|
||||||
|
("\".*/\\.ruff_cache", "\"[BASEPATH]/.ruff_cache"),
|
||||||
|
("\".*/ruff\"", "\"[BASEPATH]\""),
|
||||||
|
];
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||||
|
(r#""[^\*"]*\\pyproject.toml"#, "\"[BASEPATH]/pyproject.toml"),
|
||||||
|
(r#"".*\\crates"#, "\"[BASEPATH]/crates"),
|
||||||
|
(r#"".*\\\.ruff_cache"#, "\"[BASEPATH]/.ruff_cache"),
|
||||||
|
(r#"".*\\ruff""#, "\"[BASEPATH]\""),
|
||||||
|
(r#"\\+(\w\w|\s|")"#, "/$1"),
|
||||||
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_default_settings() {
|
fn display_default_settings() {
|
||||||
// Navigate from the crate directory to the workspace root.
|
insta::with_settings!({ filters => TEST_FILTERS.to_vec() }, {
|
||||||
let base_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.parent()
|
|
||||||
.unwrap();
|
|
||||||
let base_path = base_path.to_string_lossy();
|
|
||||||
|
|
||||||
// Escape the backslashes for the regex.
|
|
||||||
let base_path = regex::escape(&base_path);
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
let test_filters = &[(base_path.as_ref(), "[BASEPATH]")];
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let test_filters = &[
|
|
||||||
(base_path.as_ref(), "[BASEPATH]"),
|
|
||||||
(r#"\\+(\w\w|\s|\.|")"#, "/$1"),
|
|
||||||
];
|
|
||||||
|
|
||||||
insta::with_settings!({ filters => test_filters.to_vec() }, {
|
|
||||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
.args(["check", "--show-settings", "unformatted.py"]).current_dir(Path::new("./resources/test/fixtures")));
|
.args(["check", "--show-settings", "unformatted.py"]).current_dir(Path::new("./resources/test/fixtures")));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ source: crates/ruff/tests/integration_test.rs
|
|||||||
info:
|
info:
|
||||||
program: ruff
|
program: ruff
|
||||||
args:
|
args:
|
||||||
- rule
|
- "--explain"
|
||||||
- F401
|
- F401
|
||||||
---
|
---
|
||||||
success: true
|
success: true
|
||||||
@@ -25,20 +25,6 @@ import cycles. They also increase the cognitive load of reading the code.
|
|||||||
If an import statement is used to check for the availability or existence
|
If an import statement is used to check for the availability or existence
|
||||||
of a module, consider using `importlib.util.find_spec` instead.
|
of a module, consider using `importlib.util.find_spec` instead.
|
||||||
|
|
||||||
If an import statement is used to re-export a symbol as part of a module's
|
|
||||||
public interface, consider using a "redundant" import alias, which
|
|
||||||
instructs Ruff (and other tools) to respect the re-export, and avoid
|
|
||||||
marking it as unused, as in:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from module import member as member
|
|
||||||
```
|
|
||||||
|
|
||||||
## Fix safety
|
|
||||||
|
|
||||||
When `ignore_init_module_imports` is disabled, fixes can remove for unused imports in `__init__` files.
|
|
||||||
These fixes are considered unsafe because they can change the public interface.
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
```python
|
```python
|
||||||
import numpy as np # unused import
|
import numpy as np # unused import
|
||||||
@@ -65,11 +51,11 @@ else:
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
- `lint.ignore-init-module-imports`
|
- `lint.pyflakes.extend-generics`
|
||||||
|
|
||||||
## References
|
## References
|
||||||
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
|
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
|
||||||
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
|
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
|
||||||
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
|
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ file_resolver.exclude = [
|
|||||||
"__pypackages__",
|
"__pypackages__",
|
||||||
"_build",
|
"_build",
|
||||||
"buck-out",
|
"buck-out",
|
||||||
|
"build",
|
||||||
"dist",
|
"dist",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"site-packages",
|
"site-packages",
|
||||||
@@ -201,12 +202,10 @@ linter.allowed_confusables = []
|
|||||||
linter.builtins = []
|
linter.builtins = []
|
||||||
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
||||||
linter.external = []
|
linter.external = []
|
||||||
linter.ignore_init_module_imports = true
|
linter.ignore_init_module_imports = false
|
||||||
linter.logger_objects = []
|
linter.logger_objects = []
|
||||||
linter.namespace_packages = []
|
linter.namespace_packages = []
|
||||||
linter.src = [
|
linter.src = ["[BASEPATH]"]
|
||||||
"[BASEPATH]",
|
|
||||||
]
|
|
||||||
linter.tab_size = 4
|
linter.tab_size = 4
|
||||||
linter.line_length = 88
|
linter.line_length = 88
|
||||||
linter.task_tags = [
|
linter.task_tags = [
|
||||||
@@ -231,7 +230,7 @@ linter.flake8_bandit.check_typed_exception = false
|
|||||||
linter.flake8_bugbear.extend_immutable_calls = []
|
linter.flake8_bugbear.extend_immutable_calls = []
|
||||||
linter.flake8_builtins.builtins_ignorelist = []
|
linter.flake8_builtins.builtins_ignorelist = []
|
||||||
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
||||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}(-\d{4})*
|
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*
|
||||||
linter.flake8_copyright.author = none
|
linter.flake8_copyright.author = none
|
||||||
linter.flake8_copyright.min_file_size = 0
|
linter.flake8_copyright.min_file_size = 0
|
||||||
linter.flake8_errmsg.max_string_length = 0
|
linter.flake8_errmsg.max_string_length = 0
|
||||||
@@ -241,22 +240,7 @@ linter.flake8_gettext.functions_names = [
|
|||||||
ngettext,
|
ngettext,
|
||||||
]
|
]
|
||||||
linter.flake8_implicit_str_concat.allow_multiline = true
|
linter.flake8_implicit_str_concat.allow_multiline = true
|
||||||
linter.flake8_import_conventions.aliases = {
|
linter.flake8_import_conventions.aliases = {"matplotlib": "mpl", "matplotlib.pyplot": "plt", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "networkx": "nx", "plotly.express": "px", "polars": "pl", "numpy": "np", "panel": "pn", "pyarrow": "pa", "altair": "alt", "tkinter": "tk", "holoviews": "hv"}
|
||||||
altair = alt,
|
|
||||||
holoviews = hv,
|
|
||||||
matplotlib = mpl,
|
|
||||||
matplotlib.pyplot = plt,
|
|
||||||
networkx = nx,
|
|
||||||
numpy = np,
|
|
||||||
pandas = pd,
|
|
||||||
panel = pn,
|
|
||||||
plotly.express = px,
|
|
||||||
polars = pl,
|
|
||||||
pyarrow = pa,
|
|
||||||
seaborn = sns,
|
|
||||||
tensorflow = tf,
|
|
||||||
tkinter = tk,
|
|
||||||
}
|
|
||||||
linter.flake8_import_conventions.banned_aliases = {}
|
linter.flake8_import_conventions.banned_aliases = {}
|
||||||
linter.flake8_import_conventions.banned_from = []
|
linter.flake8_import_conventions.banned_from = []
|
||||||
linter.flake8_pytest_style.fixture_parentheses = true
|
linter.flake8_pytest_style.fixture_parentheses = true
|
||||||
@@ -326,7 +310,6 @@ linter.isort.section_order = [
|
|||||||
known { type = first_party },
|
known { type = first_party },
|
||||||
known { type = local_folder },
|
known { type = local_folder },
|
||||||
]
|
]
|
||||||
linter.isort.default_section = known { type = third_party }
|
|
||||||
linter.isort.no_sections = false
|
linter.isort.no_sections = false
|
||||||
linter.isort.from_first = false
|
linter.isort.from_first = false
|
||||||
linter.isort.length_sort = false
|
linter.isort.length_sort = false
|
||||||
@@ -381,3 +364,4 @@ formatter.docstring_code_format = disabled
|
|||||||
formatter.docstring_code_line_width = dynamic
|
formatter.docstring_code_line_width = dynamic
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
//! Tests for the --version command
|
|
||||||
use std::fs;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
|
||||||
use tempfile::TempDir;
|
|
||||||
|
|
||||||
const BIN_NAME: &str = "ruff";
|
|
||||||
const VERSION_FILTER: [(&str, &str); 1] = [(
|
|
||||||
r"\d+\.\d+\.\d+(\+\d+)?( \(\w{9} \d\d\d\d-\d\d-\d\d\))?",
|
|
||||||
"[VERSION]",
|
|
||||||
)];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn version_basics() {
|
|
||||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
|
||||||
assert_cmd_snapshot!(
|
|
||||||
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
ruff [VERSION]
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `--config` is a global option,
|
|
||||||
/// so it's allowed to pass --config to subcommands such as `version`
|
|
||||||
/// -- the flag is simply ignored
|
|
||||||
#[test]
|
|
||||||
fn config_option_allowed_but_ignored() -> Result<()> {
|
|
||||||
let tempdir = TempDir::new()?;
|
|
||||||
let ruff_dot_toml = tempdir.path().join("ruff.toml");
|
|
||||||
fs::File::create(&ruff_dot_toml)?;
|
|
||||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
|
||||||
assert_cmd_snapshot!(
|
|
||||||
Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.arg("version")
|
|
||||||
.arg("--config")
|
|
||||||
.arg(&ruff_dot_toml)
|
|
||||||
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
ruff [VERSION]
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn config_option_ignored_but_validated() {
|
|
||||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
|
||||||
assert_cmd_snapshot!(
|
|
||||||
Command::new(get_cargo_bin(BIN_NAME))
|
|
||||||
.arg("version")
|
|
||||||
.args(["--config", "foo = bar"]), @r###"
|
|
||||||
success: false
|
|
||||||
exit_code: 2
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
|
|
||||||
|
|
||||||
tip: A `--config` flag must either be a path to a `.toml` configuration file
|
|
||||||
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
|
|
||||||
option
|
|
||||||
|
|
||||||
The supplied argument is not valid TOML:
|
|
||||||
|
|
||||||
TOML parse error at line 1, column 7
|
|
||||||
|
|
|
||||||
1 | foo = bar
|
|
||||||
| ^
|
|
||||||
invalid string
|
|
||||||
expected `"`, `'`
|
|
||||||
|
|
||||||
For more information, try '--help'.
|
|
||||||
"###
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `--isolated` is also a global option,
|
|
||||||
#[test]
|
|
||||||
fn isolated_option_allowed() {
|
|
||||||
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
|
|
||||||
assert_cmd_snapshot!(
|
|
||||||
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @r###"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
ruff [VERSION]
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
"###
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ license = { workspace = true }
|
|||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "linter"
|
name = "linter"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
|||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, GlobalConfigArgs, LogLevelArgs};
|
use ruff::args::{CliOverrides, FormatArguments, FormatCommand, LogLevelArgs};
|
||||||
use ruff::resolve::resolve;
|
use ruff::resolve::resolve;
|
||||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
@@ -38,21 +38,26 @@ use ruff_python_formatter::{
|
|||||||
use ruff_python_parser::ParseError;
|
use ruff_python_parser::ParseError;
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||||
|
|
||||||
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
|
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, CliOverrides)> {
|
||||||
let args_matches = FormatCommand::command()
|
let args_matches = FormatCommand::command()
|
||||||
.no_binary_name(true)
|
.no_binary_name(true)
|
||||||
.get_matches_from(dirs);
|
.get_matches_from(dirs);
|
||||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||||
let (cli, config_arguments) = arguments.partition(GlobalConfigArgs::default())?;
|
let (cli, overrides) = arguments.partition();
|
||||||
Ok((cli, config_arguments))
|
Ok((cli, overrides))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the [`PyprojectConfig`] to use for formatting.
|
/// Find the [`PyprojectConfig`] to use for formatting.
|
||||||
fn find_pyproject_config(
|
fn find_pyproject_config(
|
||||||
cli: &FormatArguments,
|
cli: &FormatArguments,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
) -> anyhow::Result<PyprojectConfig> {
|
) -> anyhow::Result<PyprojectConfig> {
|
||||||
let mut pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
|
let mut pyproject_config = resolve(
|
||||||
|
cli.isolated,
|
||||||
|
cli.config.as_deref(),
|
||||||
|
overrides,
|
||||||
|
cli.stdin_filename.as_deref(),
|
||||||
|
)?;
|
||||||
// We don't want to format pyproject.toml
|
// We don't want to format pyproject.toml
|
||||||
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
|
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
|
||||||
FilePattern::Builtin("*.py"),
|
FilePattern::Builtin("*.py"),
|
||||||
@@ -67,9 +72,9 @@ fn find_pyproject_config(
|
|||||||
fn ruff_check_paths<'a>(
|
fn ruff_check_paths<'a>(
|
||||||
pyproject_config: &'a PyprojectConfig,
|
pyproject_config: &'a PyprojectConfig,
|
||||||
cli: &FormatArguments,
|
cli: &FormatArguments,
|
||||||
config_arguments: &ConfigArguments,
|
overrides: &CliOverrides,
|
||||||
) -> anyhow::Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
) -> anyhow::Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
||||||
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, config_arguments)?;
|
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, overrides)?;
|
||||||
Ok((paths, resolver))
|
Ok((paths, resolver))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use ruff::{args::GlobalConfigArgs, check};
|
use ruff::check;
|
||||||
use ruff_linter::logging::set_up_logging;
|
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
mod format_dev;
|
mod format_dev;
|
||||||
@@ -28,8 +28,6 @@ const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
|
|||||||
struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Command,
|
command: Command,
|
||||||
#[clap(flatten)]
|
|
||||||
global_options: GlobalConfigArgs,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
@@ -59,6 +57,8 @@ enum Command {
|
|||||||
Repeat {
|
Repeat {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
args: ruff::args::CheckCommand,
|
args: ruff::args::CheckCommand,
|
||||||
|
#[clap(flatten)]
|
||||||
|
log_level_args: ruff::args::LogLevelArgs,
|
||||||
/// Run this many times
|
/// Run this many times
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
repeat: usize,
|
repeat: usize,
|
||||||
@@ -75,12 +75,9 @@ enum Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<ExitCode> {
|
fn main() -> Result<ExitCode> {
|
||||||
let Args {
|
let args = Args::parse();
|
||||||
command,
|
|
||||||
global_options,
|
|
||||||
} = Args::parse();
|
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
match command {
|
match args.command {
|
||||||
Command::GenerateAll(args) => generate_all::main(&args)?,
|
Command::GenerateAll(args) => generate_all::main(&args)?,
|
||||||
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
|
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
|
||||||
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
|
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
|
||||||
@@ -92,12 +89,14 @@ fn main() -> Result<ExitCode> {
|
|||||||
Command::PrintTokens(args) => print_tokens::main(&args)?,
|
Command::PrintTokens(args) => print_tokens::main(&args)?,
|
||||||
Command::RoundTrip(args) => round_trip::main(&args)?,
|
Command::RoundTrip(args) => round_trip::main(&args)?,
|
||||||
Command::Repeat {
|
Command::Repeat {
|
||||||
args: subcommand_args,
|
args,
|
||||||
repeat,
|
repeat,
|
||||||
|
log_level_args,
|
||||||
} => {
|
} => {
|
||||||
set_up_logging(global_options.log_level())?;
|
let log_level = LogLevel::from(&log_level_args);
|
||||||
|
set_up_logging(&log_level)?;
|
||||||
for _ in 0..repeat {
|
for _ in 0..repeat {
|
||||||
check(subcommand_args.clone(), global_options.clone())?;
|
check(args.clone(), log_level)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::FormatDev(args) => {
|
Command::FormatDev(args) => {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ repository = { workspace = true }
|
|||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ruff_text_size = { path = "../ruff_text_size" }
|
ruff_text_size = { path = "../ruff_text_size" }
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub trait Buffer {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn elements(&self) -> &[FormatElement];
|
fn elements(&self) -> &[FormatElement];
|
||||||
|
|
||||||
/// Glue for usage of the [`write!`] macro with implementers of this trait.
|
/// Glue for usage of the [`write!`] macro with implementors of this trait.
|
||||||
///
|
///
|
||||||
/// This method should generally not be invoked manually, but rather through the [`write!`] macro itself.
|
/// This method should generally not be invoked manually, but rather through the [`write!`] macro itself.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -308,8 +308,11 @@ impl std::fmt::Debug for Token {
|
|||||||
/// assert_eq!(printed.as_code(), r#""Hello 'Ruff'""#);
|
/// assert_eq!(printed.as_code(), r#""Hello 'Ruff'""#);
|
||||||
/// assert_eq!(printed.sourcemap(), [
|
/// assert_eq!(printed.sourcemap(), [
|
||||||
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(0) },
|
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(0) },
|
||||||
|
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(7) },
|
||||||
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(7) },
|
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(7) },
|
||||||
|
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(13) },
|
||||||
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(13) },
|
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(13) },
|
||||||
|
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(14) },
|
||||||
/// SourceMarker { source: TextSize::new(20), dest: TextSize::new(14) },
|
/// SourceMarker { source: TextSize::new(20), dest: TextSize::new(14) },
|
||||||
/// ]);
|
/// ]);
|
||||||
///
|
///
|
||||||
@@ -325,30 +328,24 @@ pub struct SourcePosition(TextSize);
|
|||||||
|
|
||||||
impl<Context> Format<Context> for SourcePosition {
|
impl<Context> Format<Context> for SourcePosition {
|
||||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||||
if let Some(FormatElement::SourcePosition(last_position)) = f.buffer.elements().last() {
|
|
||||||
if *last_position == self.0 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_element(FormatElement::SourcePosition(self.0));
|
f.write_element(FormatElement::SourcePosition(self.0));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a text from a dynamic string.
|
/// Creates a text from a dynamic string with its optional start-position in the source document.
|
||||||
///
|
|
||||||
/// This is done by allocating a new string internally.
|
/// This is done by allocating a new string internally.
|
||||||
pub fn text(text: &str) -> Text {
|
pub fn text(text: &str, position: Option<TextSize>) -> Text {
|
||||||
debug_assert_no_newlines(text);
|
debug_assert_no_newlines(text);
|
||||||
|
|
||||||
Text { text }
|
Text { text, position }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
pub struct Text<'a> {
|
pub struct Text<'a> {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
|
position: Option<TextSize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Context> Format<Context> for Text<'_>
|
impl<Context> Format<Context> for Text<'_>
|
||||||
@@ -356,6 +353,10 @@ where
|
|||||||
Context: FormatContext,
|
Context: FormatContext,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||||
|
if let Some(source_position) = self.position {
|
||||||
|
f.write_element(FormatElement::SourcePosition(source_position));
|
||||||
|
}
|
||||||
|
|
||||||
f.write_element(FormatElement::Text {
|
f.write_element(FormatElement::Text {
|
||||||
text: self.text.to_string().into_boxed_str(),
|
text: self.text.to_string().into_boxed_str(),
|
||||||
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
|
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
|
||||||
@@ -2285,7 +2286,7 @@ impl<Context, T> std::fmt::Debug for FormatWith<Context, T> {
|
|||||||
/// let mut join = f.join_with(&separator);
|
/// let mut join = f.join_with(&separator);
|
||||||
///
|
///
|
||||||
/// for item in &self.items {
|
/// for item in &self.items {
|
||||||
/// join.entry(&format_with(|f| write!(f, [text(item)])));
|
/// join.entry(&format_with(|f| write!(f, [text(item, None)])));
|
||||||
/// }
|
/// }
|
||||||
/// join.finish()
|
/// join.finish()
|
||||||
/// })),
|
/// })),
|
||||||
@@ -2370,7 +2371,7 @@ where
|
|||||||
/// let mut count = 0;
|
/// let mut count = 0;
|
||||||
///
|
///
|
||||||
/// let value = format_once(|f| {
|
/// let value = format_once(|f| {
|
||||||
/// write!(f, [text(&std::format!("Formatted {count}."))])
|
/// write!(f, [text(&std::format!("Formatted {count}."), None)])
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine");
|
/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine");
|
||||||
|
|||||||
@@ -346,7 +346,10 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::SourcePosition(position) => {
|
FormatElement::SourcePosition(position) => {
|
||||||
write!(f, [text(&std::format!("source_position({position:?})"))])?;
|
write!(
|
||||||
|
f,
|
||||||
|
[text(&std::format!("source_position({position:?})"), None)]
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::LineSuffixBoundary => {
|
FormatElement::LineSuffixBoundary => {
|
||||||
@@ -357,7 +360,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
write!(f, [token("best_fitting(")])?;
|
write!(f, [token("best_fitting(")])?;
|
||||||
|
|
||||||
if *mode != BestFittingMode::FirstLine {
|
if *mode != BestFittingMode::FirstLine {
|
||||||
write!(f, [text(&std::format!("mode: {mode:?}, "))])?;
|
write!(f, [text(&std::format!("mode: {mode:?}, "), None)])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, [token("[")])?;
|
write!(f, [token("[")])?;
|
||||||
@@ -389,14 +392,17 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
text(&std::format!("<interned {index}>")),
|
text(&std::format!("<interned {index}>"), None),
|
||||||
space(),
|
space(),
|
||||||
&&**interned,
|
&&**interned,
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Some(reference) => {
|
Some(reference) => {
|
||||||
write!(f, [text(&std::format!("<ref interned *{reference}>"))])?;
|
write!(
|
||||||
|
f,
|
||||||
|
[text(&std::format!("<ref interned *{reference}>"), None)]
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,7 +421,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("<END_TAG_WITHOUT_START<"),
|
token("<END_TAG_WITHOUT_START<"),
|
||||||
text(&std::format!("{:?}", tag.kind())),
|
text(&std::format!("{:?}", tag.kind()), None),
|
||||||
token(">>"),
|
token(">>"),
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
@@ -430,9 +436,9 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
token(")"),
|
token(")"),
|
||||||
soft_line_break_or_space(),
|
soft_line_break_or_space(),
|
||||||
token("ERROR<START_END_TAG_MISMATCH<start: "),
|
token("ERROR<START_END_TAG_MISMATCH<start: "),
|
||||||
text(&std::format!("{start_kind:?}")),
|
text(&std::format!("{start_kind:?}"), None),
|
||||||
token(", end: "),
|
token(", end: "),
|
||||||
text(&std::format!("{:?}", tag.kind())),
|
text(&std::format!("{:?}", tag.kind()), None),
|
||||||
token(">>")
|
token(">>")
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
@@ -464,7 +470,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("align("),
|
token("align("),
|
||||||
text(&count.to_string()),
|
text(&count.to_string(), None),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
@@ -476,7 +482,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("line_suffix("),
|
token("line_suffix("),
|
||||||
text(&std::format!("{reserved_width:?}")),
|
text(&std::format!("{reserved_width:?}"), None),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
@@ -493,7 +499,11 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
if let Some(group_id) = group.id() {
|
if let Some(group_id) = group.id() {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[text(&std::format!("\"{group_id:?}\"")), token(","), space(),]
|
[
|
||||||
|
text(&std::format!("\"{group_id:?}\""), None),
|
||||||
|
token(","),
|
||||||
|
space(),
|
||||||
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +524,11 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
if let Some(group_id) = id {
|
if let Some(group_id) = id {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[text(&std::format!("\"{group_id:?}\"")), token(","), space(),]
|
[
|
||||||
|
text(&std::format!("\"{group_id:?}\""), None),
|
||||||
|
token(","),
|
||||||
|
space(),
|
||||||
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,7 +561,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("indent_if_group_breaks("),
|
token("indent_if_group_breaks("),
|
||||||
text(&std::format!("\"{id:?}\"")),
|
text(&std::format!("\"{id:?}\""), None),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
@@ -567,7 +581,11 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
if let Some(group_id) = condition.group_id {
|
if let Some(group_id) = condition.group_id {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[text(&std::format!("\"{group_id:?}\"")), token(","), space()]
|
[
|
||||||
|
text(&std::format!("\"{group_id:?}\""), None),
|
||||||
|
token(","),
|
||||||
|
space(),
|
||||||
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,7 +595,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("label("),
|
token("label("),
|
||||||
text(&std::format!("\"{label_id:?}\"")),
|
text(&std::format!("\"{label_id:?}\""), None),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
@@ -646,7 +664,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||||||
ContentArrayEnd,
|
ContentArrayEnd,
|
||||||
token(")"),
|
token(")"),
|
||||||
soft_line_break_or_space(),
|
soft_line_break_or_space(),
|
||||||
text(&std::format!("<START_WITHOUT_END<{top:?}>>")),
|
text(&std::format!("<START_WITHOUT_END<{top:?}>>"), None),
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@@ -789,7 +807,7 @@ impl Format<IrFormatContext<'_>> for Condition {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("if_group_fits_on_line("),
|
token("if_group_fits_on_line("),
|
||||||
text(&std::format!("\"{id:?}\"")),
|
text(&std::format!("\"{id:?}\""), None),
|
||||||
token(")")
|
token(")")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@@ -798,7 +816,7 @@ impl Format<IrFormatContext<'_>> for Condition {
|
|||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("if_group_breaks("),
|
token("if_group_breaks("),
|
||||||
text(&std::format!("\"{id:?}\"")),
|
text(&std::format!("\"{id:?}\""), None),
|
||||||
token(")")
|
token(")")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub trait MemoizeFormat<Context> {
|
|||||||
/// let value = self.value.get();
|
/// let value = self.value.get();
|
||||||
/// self.value.set(value + 1);
|
/// self.value.set(value + 1);
|
||||||
///
|
///
|
||||||
/// write!(f, [text(&std::format!("Formatted {value} times."))])
|
/// write!(f, [text(&std::format!("Formatted {value} times."), None)])
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@@ -110,7 +110,7 @@ where
|
|||||||
/// write!(f, [
|
/// write!(f, [
|
||||||
/// token("Count:"),
|
/// token("Count:"),
|
||||||
/// space(),
|
/// space(),
|
||||||
/// text(&std::format!("{current}")),
|
/// text(&std::format!("{current}"), None),
|
||||||
/// hard_line_break()
|
/// hard_line_break()
|
||||||
/// ])?;
|
/// ])?;
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ use std::marker::PhantomData;
|
|||||||
use std::num::{NonZeroU16, NonZeroU8, TryFromIntError};
|
use std::num::{NonZeroU16, NonZeroU8, TryFromIntError};
|
||||||
|
|
||||||
use crate::format_element::document::Document;
|
use crate::format_element::document::Document;
|
||||||
use crate::printer::{Printer, PrinterOptions};
|
use crate::printer::{Printer, PrinterOptions, SourceMapGeneration};
|
||||||
pub use arguments::{Argument, Arguments};
|
pub use arguments::{Argument, Arguments};
|
||||||
pub use buffer::{
|
pub use buffer::{
|
||||||
Buffer, BufferExtensions, BufferSnapshot, Inspect, RemoveSoftLinesBuffer, VecBuffer,
|
Buffer, BufferExtensions, BufferSnapshot, Inspect, RemoveSoftLinesBuffer, VecBuffer,
|
||||||
@@ -53,7 +53,7 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri
|
|||||||
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
|
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
|
||||||
pub use group_id::GroupId;
|
pub use group_id::GroupId;
|
||||||
use ruff_macros::CacheKey;
|
use ruff_macros::CacheKey;
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -269,6 +269,7 @@ impl FormatOptions for SimpleFormatOptions {
|
|||||||
line_width: self.line_width,
|
line_width: self.line_width,
|
||||||
indent_style: self.indent_style,
|
indent_style: self.indent_style,
|
||||||
indent_width: self.indent_width,
|
indent_width: self.indent_width,
|
||||||
|
source_map_generation: SourceMapGeneration::Enabled,
|
||||||
..PrinterOptions::default()
|
..PrinterOptions::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,133 +432,6 @@ impl Printed {
|
|||||||
pub fn take_verbatim_ranges(&mut self) -> Vec<TextRange> {
|
pub fn take_verbatim_ranges(&mut self) -> Vec<TextRange> {
|
||||||
std::mem::take(&mut self.verbatim_ranges)
|
std::mem::take(&mut self.verbatim_ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Slices the formatted code to the sub-slices that covers the passed `source_range` in `source`.
|
|
||||||
///
|
|
||||||
/// The implementation uses the source map generated during formatting to find the closest range
|
|
||||||
/// in the formatted document that covers `source_range` or more. The returned slice
|
|
||||||
/// matches the `source_range` exactly (except indent, see below) if the formatter emits [`FormatElement::SourcePosition`] for
|
|
||||||
/// the range's offsets.
|
|
||||||
///
|
|
||||||
/// ## Indentation
|
|
||||||
/// The indentation before `source_range.start` is replaced with the indentation returned by the formatter
|
|
||||||
/// to fix up incorrectly intended code.
|
|
||||||
///
|
|
||||||
/// Returns the entire document if the source map is empty.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// If `source_range` points to offsets that are not in the bounds of `source`.
|
|
||||||
#[must_use]
|
|
||||||
pub fn slice_range(self, source_range: TextRange, source: &str) -> PrintedRange {
|
|
||||||
let mut start_marker: Option<SourceMarker> = None;
|
|
||||||
let mut end_marker: Option<SourceMarker> = None;
|
|
||||||
|
|
||||||
// Note: The printer can generate multiple source map entries for the same source position.
|
|
||||||
// For example if you have:
|
|
||||||
// * token("a + b")
|
|
||||||
// * `source_position(276)`
|
|
||||||
// * `token(")")`
|
|
||||||
// * `source_position(276)`
|
|
||||||
// * `hard_line_break`
|
|
||||||
// The printer uses the source position 276 for both the tokens `)` and the `\n` because
|
|
||||||
// there were multiple `source_position` entries in the IR with the same offset.
|
|
||||||
// This can happen if multiple nodes start or end at the same position. A common example
|
|
||||||
// for this are expressions and expression statement that always end at the same offset.
|
|
||||||
//
|
|
||||||
// Warning: Source markers are often emitted sorted by their source position but it's not guaranteed
|
|
||||||
// and depends on the emitted `IR`.
|
|
||||||
// They are only guaranteed to be sorted in increasing order by their destination position.
|
|
||||||
for marker in self.sourcemap {
|
|
||||||
// Take the closest start marker, but skip over start_markers that have the same start.
|
|
||||||
if marker.source <= source_range.start()
|
|
||||||
&& !start_marker.is_some_and(|existing| existing.source >= marker.source)
|
|
||||||
{
|
|
||||||
start_marker = Some(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
if marker.source >= source_range.end()
|
|
||||||
&& !end_marker.is_some_and(|existing| existing.source <= marker.source)
|
|
||||||
{
|
|
||||||
end_marker = Some(marker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (source_start, formatted_start) = start_marker
|
|
||||||
.map(|marker| (marker.source, marker.dest))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let (source_end, formatted_end) = end_marker
|
|
||||||
.map_or((source.text_len(), self.code.text_len()), |marker| {
|
|
||||||
(marker.source, marker.dest)
|
|
||||||
});
|
|
||||||
|
|
||||||
let source_range = TextRange::new(source_start, source_end);
|
|
||||||
let formatted_range = TextRange::new(formatted_start, formatted_end);
|
|
||||||
|
|
||||||
// Extend both ranges to include the indentation
|
|
||||||
let source_range = extend_range_to_include_indent(source_range, source);
|
|
||||||
let formatted_range = extend_range_to_include_indent(formatted_range, &self.code);
|
|
||||||
|
|
||||||
PrintedRange {
|
|
||||||
code: self.code[formatted_range].to_string(),
|
|
||||||
source_range,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extends `range` backwards (by reducing `range.start`) to include any directly preceding whitespace (`\t` or ` `).
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// If `range.start` is out of `source`'s bounds.
|
|
||||||
fn extend_range_to_include_indent(range: TextRange, source: &str) -> TextRange {
|
|
||||||
let whitespace_len: TextSize = source[..usize::from(range.start())]
|
|
||||||
.chars()
|
|
||||||
.rev()
|
|
||||||
.take_while(|c| matches!(c, ' ' | '\t'))
|
|
||||||
.map(TextLen::text_len)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
TextRange::new(range.start() - whitespace_len, range.end())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
||||||
pub struct PrintedRange {
|
|
||||||
code: String,
|
|
||||||
source_range: TextRange,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrintedRange {
|
|
||||||
pub fn new(code: String, source_range: TextRange) -> Self {
|
|
||||||
Self { code, source_range }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
code: String::new(),
|
|
||||||
source_range: TextRange::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The formatted code.
|
|
||||||
pub fn as_code(&self) -> &str {
|
|
||||||
&self.code
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_code(self) -> String {
|
|
||||||
self.code
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The range the formatted code corresponds to in the source document.
|
|
||||||
pub fn source_range(&self) -> TextRange {
|
|
||||||
self.source_range
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn with_code(self, code: String) -> Self {
|
|
||||||
Self { code, ..self }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Public return type of the formatter
|
/// Public return type of the formatter
|
||||||
@@ -579,7 +453,7 @@ pub type FormatResult<F> = Result<F, FormatError>;
|
|||||||
/// impl Format<SimpleFormatContext> for Paragraph {
|
/// impl Format<SimpleFormatContext> for Paragraph {
|
||||||
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
||||||
/// write!(f, [
|
/// write!(f, [
|
||||||
/// text(&self.0),
|
/// text(&self.0, None),
|
||||||
/// hard_line_break(),
|
/// hard_line_break(),
|
||||||
/// ])
|
/// ])
|
||||||
/// }
|
/// }
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ impl<'a> LineSuffixes<'a> {
|
|||||||
/// Takes all the pending line suffixes.
|
/// Takes all the pending line suffixes.
|
||||||
pub(super) fn take_pending<'l>(
|
pub(super) fn take_pending<'l>(
|
||||||
&'l mut self,
|
&'l mut self,
|
||||||
) -> impl DoubleEndedIterator<Item = LineSuffixEntry<'a>> + 'l + ExactSizeIterator {
|
) -> impl Iterator<Item = LineSuffixEntry<'a>> + DoubleEndedIterator + 'l + ExactSizeIterator
|
||||||
|
{
|
||||||
self.suffixes.drain(..)
|
self.suffixes.drain(..)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use drop_bomb::DebugDropBomb;
|
|||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
pub use printer_options::*;
|
pub use printer_options::*;
|
||||||
use ruff_text_size::{TextLen, TextSize};
|
use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||||
|
|
||||||
use crate::format_element::document::Document;
|
use crate::format_element::document::Document;
|
||||||
use crate::format_element::tag::{Condition, GroupMode};
|
use crate::format_element::tag::{Condition, GroupMode};
|
||||||
@@ -60,10 +60,7 @@ impl<'a> Printer<'a> {
|
|||||||
document: &'a Document,
|
document: &'a Document,
|
||||||
indent: u16,
|
indent: u16,
|
||||||
) -> PrintResult<Printed> {
|
) -> PrintResult<Printed> {
|
||||||
let indentation = Indention::Level(indent);
|
let mut stack = PrintCallStack::new(PrintElementArgs::new(Indention::Level(indent)));
|
||||||
self.state.pending_indent = indentation;
|
|
||||||
|
|
||||||
let mut stack = PrintCallStack::new(PrintElementArgs::new(indentation));
|
|
||||||
let mut queue: PrintQueue<'a> = PrintQueue::new(document.as_ref());
|
let mut queue: PrintQueue<'a> = PrintQueue::new(document.as_ref());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -76,9 +73,6 @@ impl<'a> Printer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push any pending marker
|
|
||||||
self.push_marker();
|
|
||||||
|
|
||||||
Ok(Printed::new(
|
Ok(Printed::new(
|
||||||
self.state.buffer,
|
self.state.buffer,
|
||||||
None,
|
None,
|
||||||
@@ -100,38 +94,42 @@ impl<'a> Printer<'a> {
|
|||||||
let args = stack.top();
|
let args = stack.top();
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
FormatElement::Space => self.print_text(Text::Token(" ")),
|
FormatElement::Space => self.print_text(Text::Token(" "), None),
|
||||||
FormatElement::Token { text } => self.print_text(Text::Token(text)),
|
FormatElement::Token { text } => self.print_text(Text::Token(text), None),
|
||||||
FormatElement::Text { text, text_width } => self.print_text(Text::Text {
|
FormatElement::Text { text, text_width } => self.print_text(
|
||||||
|
Text::Text {
|
||||||
text,
|
text,
|
||||||
text_width: *text_width,
|
text_width: *text_width,
|
||||||
}),
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
FormatElement::SourceCodeSlice { slice, text_width } => {
|
FormatElement::SourceCodeSlice { slice, text_width } => {
|
||||||
let text = slice.text(self.source_code);
|
let text = slice.text(self.source_code);
|
||||||
self.print_text(Text::Text {
|
self.print_text(
|
||||||
|
Text::Text {
|
||||||
text,
|
text,
|
||||||
text_width: *text_width,
|
text_width: *text_width,
|
||||||
});
|
},
|
||||||
|
Some(slice.range()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
FormatElement::Line(line_mode) => {
|
FormatElement::Line(line_mode) => {
|
||||||
if args.mode().is_flat()
|
if args.mode().is_flat()
|
||||||
&& matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace)
|
&& matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace)
|
||||||
{
|
{
|
||||||
if line_mode == &LineMode::SoftOrSpace {
|
if line_mode == &LineMode::SoftOrSpace {
|
||||||
self.print_text(Text::Token(" "));
|
self.print_text(Text::Token(" "), None);
|
||||||
}
|
}
|
||||||
} else if self.state.line_suffixes.has_pending() {
|
} else if self.state.line_suffixes.has_pending() {
|
||||||
self.flush_line_suffixes(queue, stack, Some(element));
|
self.flush_line_suffixes(queue, stack, Some(element));
|
||||||
} else {
|
} else {
|
||||||
// Only print a newline if the current line isn't already empty
|
// Only print a newline if the current line isn't already empty
|
||||||
if self.state.line_width > 0 {
|
if self.state.line_width > 0 {
|
||||||
self.push_marker();
|
|
||||||
self.print_char('\n');
|
self.print_char('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print a second line break if this is an empty line
|
// Print a second line break if this is an empty line
|
||||||
if line_mode == &LineMode::Empty {
|
if line_mode == &LineMode::Empty {
|
||||||
self.push_marker();
|
|
||||||
self.print_char('\n');
|
self.print_char('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +142,8 @@ impl<'a> Printer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::SourcePosition(position) => {
|
FormatElement::SourcePosition(position) => {
|
||||||
// The printer defers printing indents until the next text
|
self.state.source_position = *position;
|
||||||
// is printed. Pushing the marker now would mean that the
|
self.push_marker();
|
||||||
// mapped range includes the indent range, which we don't want.
|
|
||||||
// Queue the source map position and emit it when printing the next character
|
|
||||||
self.state.pending_source_position = Some(*position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::LineSuffixBoundary => {
|
FormatElement::LineSuffixBoundary => {
|
||||||
@@ -440,7 +435,7 @@ impl<'a> Printer<'a> {
|
|||||||
Ok(print_mode)
|
Ok(print_mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_text(&mut self, text: Text) {
|
fn print_text(&mut self, text: Text, source_range: Option<TextRange>) {
|
||||||
if !self.state.pending_indent.is_empty() {
|
if !self.state.pending_indent.is_empty() {
|
||||||
let (indent_char, repeat_count) = match self.options.indent_style() {
|
let (indent_char, repeat_count) = match self.options.indent_style() {
|
||||||
IndentStyle::Tab => ('\t', 1),
|
IndentStyle::Tab => ('\t', 1),
|
||||||
@@ -463,6 +458,19 @@ impl<'a> Printer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert source map markers before and after the token
|
||||||
|
//
|
||||||
|
// If the token has source position information the start marker
|
||||||
|
// will use the start position of the original token, and the end
|
||||||
|
// marker will use that position + the text length of the token
|
||||||
|
//
|
||||||
|
// If the token has no source position (was created by the formatter)
|
||||||
|
// both the start and end marker will use the last known position
|
||||||
|
// in the input source (from state.source_position)
|
||||||
|
if let Some(range) = source_range {
|
||||||
|
self.state.source_position = range.start();
|
||||||
|
}
|
||||||
|
|
||||||
self.push_marker();
|
self.push_marker();
|
||||||
|
|
||||||
match text {
|
match text {
|
||||||
@@ -485,24 +493,29 @@ impl<'a> Printer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(range) = source_range {
|
||||||
|
self.state.source_position = range.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.push_marker();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_marker(&mut self) {
|
fn push_marker(&mut self) {
|
||||||
let Some(source_position) = self.state.pending_source_position.take() else {
|
if self.options.source_map_generation.is_disabled() {
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
let marker = SourceMarker {
|
let marker = SourceMarker {
|
||||||
source: source_position,
|
source: self.state.source_position,
|
||||||
dest: self.state.buffer.text_len(),
|
dest: self.state.buffer.text_len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if self
|
if let Some(last) = self.state.source_markers.last() {
|
||||||
.state
|
if last != &marker {
|
||||||
.source_markers
|
self.state.source_markers.push(marker);
|
||||||
.last()
|
}
|
||||||
.map_or(true, |last| last != &marker)
|
} else {
|
||||||
{
|
|
||||||
self.state.source_markers.push(marker);
|
self.state.source_markers.push(marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,7 +887,7 @@ enum FillPairLayout {
|
|||||||
struct PrinterState<'a> {
|
struct PrinterState<'a> {
|
||||||
buffer: String,
|
buffer: String,
|
||||||
source_markers: Vec<SourceMarker>,
|
source_markers: Vec<SourceMarker>,
|
||||||
pending_source_position: Option<TextSize>,
|
source_position: TextSize,
|
||||||
pending_indent: Indention,
|
pending_indent: Indention,
|
||||||
measured_group_fits: bool,
|
measured_group_fits: bool,
|
||||||
line_width: u32,
|
line_width: u32,
|
||||||
@@ -1729,7 +1742,7 @@ a",
|
|||||||
let result = format_with_options(
|
let result = format_with_options(
|
||||||
&format_args![
|
&format_args![
|
||||||
token("function main() {"),
|
token("function main() {"),
|
||||||
block_indent(&text("let x = `This is a multiline\nstring`;")),
|
block_indent(&text("let x = `This is a multiline\nstring`;", None)),
|
||||||
token("}"),
|
token("}"),
|
||||||
hard_line_break()
|
hard_line_break()
|
||||||
],
|
],
|
||||||
@@ -1746,7 +1759,7 @@ a",
|
|||||||
fn it_breaks_a_group_if_a_string_contains_a_newline() {
|
fn it_breaks_a_group_if_a_string_contains_a_newline() {
|
||||||
let result = format(&FormatArrayElements {
|
let result = format(&FormatArrayElements {
|
||||||
items: vec![
|
items: vec![
|
||||||
&text("`This is a string spanning\ntwo lines`"),
|
&text("`This is a string spanning\ntwo lines`", None),
|
||||||
&token("\"b\""),
|
&token("\"b\""),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ pub struct PrinterOptions {
|
|||||||
|
|
||||||
/// The type of line ending to apply to the printed input
|
/// The type of line ending to apply to the printed input
|
||||||
pub line_ending: LineEnding,
|
pub line_ending: LineEnding,
|
||||||
|
|
||||||
|
/// Whether the printer should build a source map that allows mapping positions in the source document
|
||||||
|
/// to positions in the formatted document.
|
||||||
|
pub source_map_generation: SourceMapGeneration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, O> From<&'a O> for PrinterOptions
|
impl<'a, O> From<&'a O> for PrinterOptions
|
||||||
|
|||||||
@@ -78,28 +78,27 @@ impl<'a> PrintQueue<'a> {
|
|||||||
impl<'a> Queue<'a> for PrintQueue<'a> {
|
impl<'a> Queue<'a> for PrintQueue<'a> {
|
||||||
fn pop(&mut self) -> Option<&'a FormatElement> {
|
fn pop(&mut self) -> Option<&'a FormatElement> {
|
||||||
let elements = self.element_slices.last_mut()?;
|
let elements = self.element_slices.last_mut()?;
|
||||||
elements.next().or_else(
|
elements.next().or_else(|| {
|
||||||
#[cold]
|
|
||||||
|| {
|
|
||||||
self.element_slices.pop();
|
self.element_slices.pop();
|
||||||
let elements = self.element_slices.last_mut()?;
|
let elements = self.element_slices.last_mut()?;
|
||||||
elements.next()
|
elements.next()
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
||||||
let mut slices = self.element_slices.iter().rev();
|
let mut slices = self.element_slices.iter().rev();
|
||||||
let slice = slices.next()?;
|
let slice = slices.next()?;
|
||||||
|
|
||||||
slice.as_slice().first().or_else(
|
match slice.as_slice().first() {
|
||||||
#[cold]
|
Some(element) => Some(element),
|
||||||
|| {
|
None => {
|
||||||
slices
|
if let Some(next_elements) = slices.next() {
|
||||||
.next()
|
next_elements.as_slice().first()
|
||||||
.and_then(|next_elements| next_elements.as_slice().first())
|
} else {
|
||||||
},
|
None
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
||||||
@@ -147,30 +146,24 @@ impl<'a, 'print> FitsQueue<'a, 'print> {
|
|||||||
|
|
||||||
impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
|
impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
|
||||||
fn pop(&mut self) -> Option<&'a FormatElement> {
|
fn pop(&mut self) -> Option<&'a FormatElement> {
|
||||||
self.queue.pop().or_else(
|
self.queue.pop().or_else(|| {
|
||||||
#[cold]
|
|
||||||
|| {
|
|
||||||
if let Some(next_slice) = self.rest_elements.next_back() {
|
if let Some(next_slice) = self.rest_elements.next_back() {
|
||||||
self.queue.extend_back(next_slice.as_slice());
|
self.queue.extend_back(next_slice.as_slice());
|
||||||
self.queue.pop()
|
self.queue.pop()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
||||||
self.queue.top_with_interned().or_else(
|
self.queue.top_with_interned().or_else(|| {
|
||||||
#[cold]
|
|
||||||
|| {
|
|
||||||
if let Some(next_elements) = self.rest_elements.as_slice().last() {
|
if let Some(next_elements) = self.rest_elements.as_slice().last() {
|
||||||
next_elements.as_slice().first()
|
next_elements.as_slice().first()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ repository = { workspace = true }
|
|||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ruff_macros = { path = "../ruff_macros" }
|
ruff_macros = { path = "../ruff_macros" }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ruff_linter"
|
name = "ruff_linter"
|
||||||
version = "0.3.2"
|
version = "0.1.15"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -60,6 +60,7 @@ regex = { workspace = true }
|
|||||||
result-like = { workspace = true }
|
result-like = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
similar = { workspace = true }
|
similar = { workspace = true }
|
||||||
@@ -84,8 +85,6 @@ tempfile = { workspace = true }
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
schemars = ["dep:schemars"]
|
schemars = ["dep:schemars"]
|
||||||
# Enables rules for internal integration tests
|
|
||||||
test-rules = []
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -28,11 +28,3 @@ dictionary = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#import os # noqa
|
#import os # noqa
|
||||||
|
|
||||||
# case 1:
|
|
||||||
# try:
|
|
||||||
# try: # with comment
|
|
||||||
# try: print()
|
|
||||||
# except:
|
|
||||||
# except Foo:
|
|
||||||
# except Exception as e: print(e)
|
|
||||||
|
|||||||
@@ -18,7 +18,3 @@ func("0.0.0.0")
|
|||||||
def my_func():
|
def my_func():
|
||||||
x = "0.0.0.0"
|
x = "0.0.0.0"
|
||||||
print(x)
|
print(x)
|
||||||
|
|
||||||
|
|
||||||
# Implicit string concatenation
|
|
||||||
"0.0.0.0" f"0.0.0.0{expr}0.0.0.0"
|
|
||||||
|
|||||||
@@ -18,13 +18,6 @@ with open("/dev/shm/unit/test", "w") as f:
|
|||||||
with open("/foo/bar", "w") as f:
|
with open("/foo/bar", "w") as f:
|
||||||
f.write("def")
|
f.write("def")
|
||||||
|
|
||||||
# Implicit string concatenation
|
|
||||||
with open("/tmp/" "abc", "w") as f:
|
|
||||||
f.write("def")
|
|
||||||
|
|
||||||
with open("/tmp/abc" f"/tmp/abc", "w") as f:
|
|
||||||
f.write("def")
|
|
||||||
|
|
||||||
# Using `tempfile` module should be ok
|
# Using `tempfile` module should be ok
|
||||||
import tempfile
|
import tempfile
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
|
|
||||||
def some_func():
|
|
||||||
return mark_safe('<script>alert("evil!")</script>')
|
|
||||||
|
|
||||||
|
|
||||||
@mark_safe
|
|
||||||
def some_func():
|
|
||||||
return '<script>alert("evil!")</script>'
|
|
||||||
|
|
||||||
|
|
||||||
from django.utils.html import mark_safe
|
|
||||||
|
|
||||||
|
|
||||||
def some_func():
|
|
||||||
return mark_safe('<script>alert("evil!")</script>')
|
|
||||||
|
|
||||||
|
|
||||||
@mark_safe
|
|
||||||
def some_func():
|
|
||||||
return '<script>alert("evil!")</script>'
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import os
|
|
||||||
import random
|
|
||||||
|
|
||||||
import a_lib
|
|
||||||
|
|
||||||
# OK
|
|
||||||
random.SystemRandom()
|
|
||||||
|
|
||||||
# Errors
|
|
||||||
random.Random()
|
|
||||||
random.random()
|
|
||||||
random.randrange()
|
|
||||||
random.randint()
|
|
||||||
random.choice()
|
|
||||||
random.choices()
|
|
||||||
random.uniform()
|
|
||||||
random.triangular()
|
|
||||||
random.randbytes()
|
|
||||||
|
|
||||||
# Unrelated
|
|
||||||
os.urandom()
|
|
||||||
a_lib.random()
|
|
||||||
@@ -1,47 +1,52 @@
|
|||||||
import crypt
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from hashlib import new as hashlib_new
|
from hashlib import new as hashlib_new
|
||||||
from hashlib import sha1 as hashlib_sha1
|
from hashlib import sha1 as hashlib_sha1
|
||||||
|
|
||||||
# Errors
|
# Invalid
|
||||||
|
|
||||||
hashlib.new('md5')
|
hashlib.new('md5')
|
||||||
|
|
||||||
hashlib.new('md4', b'test')
|
hashlib.new('md4', b'test')
|
||||||
|
|
||||||
hashlib.new(name='md5', data=b'test')
|
hashlib.new(name='md5', data=b'test')
|
||||||
|
|
||||||
hashlib.new('MD4', data=b'test')
|
hashlib.new('MD4', data=b'test')
|
||||||
|
|
||||||
hashlib.new('sha1')
|
hashlib.new('sha1')
|
||||||
|
|
||||||
hashlib.new('sha1', data=b'test')
|
hashlib.new('sha1', data=b'test')
|
||||||
|
|
||||||
hashlib.new('sha', data=b'test')
|
hashlib.new('sha', data=b'test')
|
||||||
|
|
||||||
hashlib.new(name='SHA', data=b'test')
|
hashlib.new(name='SHA', data=b'test')
|
||||||
|
|
||||||
hashlib.sha(data=b'test')
|
hashlib.sha(data=b'test')
|
||||||
|
|
||||||
hashlib.md5()
|
hashlib.md5()
|
||||||
|
|
||||||
hashlib_new('sha1')
|
hashlib_new('sha1')
|
||||||
|
|
||||||
hashlib_sha1('sha1')
|
hashlib_sha1('sha1')
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib.new('sha1', usedforsecurity=True)
|
hashlib.new('sha1', usedforsecurity=True)
|
||||||
|
|
||||||
crypt.crypt("test", salt=crypt.METHOD_CRYPT)
|
# Valid
|
||||||
crypt.crypt("test", salt=crypt.METHOD_MD5)
|
|
||||||
crypt.crypt("test", salt=crypt.METHOD_BLOWFISH)
|
|
||||||
crypt.crypt("test", crypt.METHOD_BLOWFISH)
|
|
||||||
|
|
||||||
crypt.mksalt(crypt.METHOD_CRYPT)
|
|
||||||
crypt.mksalt(crypt.METHOD_MD5)
|
|
||||||
crypt.mksalt(crypt.METHOD_BLOWFISH)
|
|
||||||
|
|
||||||
# OK
|
|
||||||
hashlib.new('sha256')
|
hashlib.new('sha256')
|
||||||
|
|
||||||
hashlib.new('SHA512')
|
hashlib.new('SHA512')
|
||||||
|
|
||||||
hashlib.sha256(data=b'test')
|
hashlib.sha256(data=b'test')
|
||||||
|
|
||||||
# usedforsecurity arg only available in Python 3.9+
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib_new(name='sha1', usedforsecurity=False)
|
hashlib_new(name='sha1', usedforsecurity=False)
|
||||||
|
|
||||||
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib_sha1(name='sha1', usedforsecurity=False)
|
hashlib_sha1(name='sha1', usedforsecurity=False)
|
||||||
|
|
||||||
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib.md4(usedforsecurity=False)
|
hashlib.md4(usedforsecurity=False)
|
||||||
|
|
||||||
|
# usedforsecurity arg only available in Python 3.9+
|
||||||
hashlib.new(name='sha256', usedforsecurity=False)
|
hashlib.new(name='sha256', usedforsecurity=False)
|
||||||
|
|
||||||
crypt.crypt("test")
|
|
||||||
crypt.crypt("test", salt=crypt.METHOD_SHA256)
|
|
||||||
crypt.crypt("test", salt=crypt.METHOD_SHA512)
|
|
||||||
|
|
||||||
crypt.mksalt()
|
|
||||||
crypt.mksalt(crypt.METHOD_SHA256)
|
|
||||||
crypt.mksalt(crypt.METHOD_SHA512)
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import commands
|
import commands
|
||||||
import popen2
|
import popen2
|
||||||
@@ -17,8 +16,6 @@ popen2.Popen3("true")
|
|||||||
popen2.Popen4("true")
|
popen2.Popen4("true")
|
||||||
commands.getoutput("true")
|
commands.getoutput("true")
|
||||||
commands.getstatusoutput("true")
|
commands.getstatusoutput("true")
|
||||||
subprocess.getoutput("true")
|
|
||||||
subprocess.getstatusoutput("true")
|
|
||||||
|
|
||||||
|
|
||||||
# Check command argument looks unsafe.
|
# Check command argument looks unsafe.
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
# Errors
|
|
||||||
User.objects.filter(username='admin').extra(dict(could_be='insecure'))
|
|
||||||
User.objects.filter(username='admin').extra(select=dict(could_be='insecure'))
|
|
||||||
User.objects.filter(username='admin').extra(select={'test': '%secure' % 'nos'})
|
|
||||||
User.objects.filter(username='admin').extra(select={'test': '{}secure'.format('nos')})
|
|
||||||
User.objects.filter(username='admin').extra(where=['%secure' % 'nos'])
|
|
||||||
User.objects.filter(username='admin').extra(where=['{}secure'.format('no')])
|
|
||||||
|
|
||||||
query = '"username") AS "username", * FROM "auth_user" WHERE 1=1 OR "username"=? --'
|
|
||||||
User.objects.filter(username='admin').extra(select={'test': query})
|
|
||||||
|
|
||||||
where_var = ['1=1) OR 1=1 AND (1=1']
|
|
||||||
User.objects.filter(username='admin').extra(where=where_var)
|
|
||||||
|
|
||||||
where_str = '1=1) OR 1=1 AND (1=1'
|
|
||||||
User.objects.filter(username='admin').extra(where=[where_str])
|
|
||||||
|
|
||||||
tables_var = ['django_content_type" WHERE "auth_user"."username"="admin']
|
|
||||||
User.objects.all().extra(tables=tables_var).distinct()
|
|
||||||
|
|
||||||
tables_str = 'django_content_type" WHERE "auth_user"."username"="admin'
|
|
||||||
User.objects.all().extra(tables=[tables_str]).distinct()
|
|
||||||
|
|
||||||
# OK
|
|
||||||
User.objects.filter(username='admin').extra(
|
|
||||||
select={'test': 'secure'},
|
|
||||||
where=['secure'],
|
|
||||||
tables=['secure']
|
|
||||||
)
|
|
||||||
User.objects.filter(username='admin').extra({'test': 'secure'})
|
|
||||||
User.objects.filter(username='admin').extra(select={'test': 'secure'})
|
|
||||||
User.objects.filter(username='admin').extra(where=['secure'])
|
|
||||||
@@ -119,16 +119,3 @@ def func(x: bool):
|
|||||||
|
|
||||||
|
|
||||||
settings(True)
|
settings(True)
|
||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass, InitVar
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Fit:
|
|
||||||
force: InitVar[bool] = False
|
|
||||||
|
|
||||||
def __post_init__(self, force: bool) -> None:
|
|
||||||
print(force)
|
|
||||||
|
|
||||||
Fit(force=True)
|
|
||||||
|
|||||||
@@ -13,11 +13,3 @@ s = f"{set([f(x) for x in 'ab'])}"
|
|||||||
|
|
||||||
s = f"{ set([x for x in 'ab']) | set([x for x in 'ab']) }"
|
s = f"{ set([x for x in 'ab']) | set([x for x in 'ab']) }"
|
||||||
s = f"{set([x for x in 'ab']) | set([x for x in 'ab'])}"
|
s = f"{set([x for x in 'ab']) | set([x for x in 'ab'])}"
|
||||||
|
|
||||||
s = set( # comment
|
|
||||||
[x for x in range(3)]
|
|
||||||
)
|
|
||||||
|
|
||||||
s = set([ # comment
|
|
||||||
x for x in range(3)
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -20,10 +20,3 @@ f"{dict(x='y') | dict(y='z')}"
|
|||||||
f"{ dict(x='y') | dict(y='z') }"
|
f"{ dict(x='y') | dict(y='z') }"
|
||||||
f"a {dict(x='y') | dict(y='z')} b"
|
f"a {dict(x='y') | dict(y='z')} b"
|
||||||
f"a { dict(x='y') | dict(y='z') } b"
|
f"a { dict(x='y') | dict(y='z') } b"
|
||||||
|
|
||||||
dict(
|
|
||||||
# comment
|
|
||||||
)
|
|
||||||
|
|
||||||
tuple( # comment
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -8,11 +8,3 @@ t4 = tuple([
|
|||||||
t5 = tuple(
|
t5 = tuple(
|
||||||
(1, 2)
|
(1, 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
tuple( # comment
|
|
||||||
[1, 2]
|
|
||||||
)
|
|
||||||
|
|
||||||
tuple([ # comment
|
|
||||||
1, 2
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -2,12 +2,3 @@ l1 = list([1, 2])
|
|||||||
l2 = list((1, 2))
|
l2 = list((1, 2))
|
||||||
l3 = list([])
|
l3 = list([])
|
||||||
l4 = list(())
|
l4 = list(())
|
||||||
|
|
||||||
|
|
||||||
list( # comment
|
|
||||||
[1, 2]
|
|
||||||
)
|
|
||||||
|
|
||||||
list([ # comment
|
|
||||||
1, 2
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ reversed(sorted(x, reverse=not x))
|
|||||||
reversed(sorted(i for i in range(42)))
|
reversed(sorted(i for i in range(42)))
|
||||||
reversed(sorted((i for i in range(42)), reverse=True))
|
reversed(sorted((i for i in range(42)), reverse=True))
|
||||||
|
|
||||||
# Regression test for: https://github.com/astral-sh/ruff/issues/10335
|
|
||||||
reversed(sorted([1, 2, 3], reverse=False or True))
|
def reversed(*args, **kwargs):
|
||||||
reversed(sorted([1, 2, 3], reverse=(False or True)))
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
reversed(sorted(x, reverse=True))
|
||||||
|
|||||||
@@ -7,19 +7,7 @@ from pdb import set_trace as st
|
|||||||
from celery.contrib.rdb import set_trace
|
from celery.contrib.rdb import set_trace
|
||||||
from celery.contrib import rdb
|
from celery.contrib import rdb
|
||||||
import celery.contrib.rdb
|
import celery.contrib.rdb
|
||||||
from debugpy import wait_for_client
|
|
||||||
import debugpy
|
|
||||||
from ptvsd import break_into_debugger
|
|
||||||
from ptvsd import enable_attach
|
|
||||||
from ptvsd import wait_for_attach
|
|
||||||
import ptvsd
|
|
||||||
|
|
||||||
breakpoint()
|
breakpoint()
|
||||||
st()
|
st()
|
||||||
set_trace()
|
set_trace()
|
||||||
debugpy.breakpoint()
|
|
||||||
wait_for_client()
|
|
||||||
debugpy.listen(1234)
|
|
||||||
enable_attach()
|
|
||||||
break_into_debugger()
|
|
||||||
wait_for_attach()
|
|
||||||
|
|||||||
@@ -207,23 +207,3 @@ class Repro:
|
|||||||
def stub(self) -> str:
|
def stub(self) -> str:
|
||||||
"""Docstring"""
|
"""Docstring"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class Repro(Protocol[int]):
|
|
||||||
def func(self) -> str:
|
|
||||||
"""Docstring"""
|
|
||||||
...
|
|
||||||
|
|
||||||
def impl(self) -> str:
|
|
||||||
"""Docstring"""
|
|
||||||
return self.func()
|
|
||||||
|
|
||||||
|
|
||||||
class Repro[int](Protocol):
|
|
||||||
def func(self) -> str:
|
|
||||||
"""Docstring"""
|
|
||||||
...
|
|
||||||
|
|
||||||
def impl(self) -> str:
|
|
||||||
"""Docstring"""
|
|
||||||
return self.func()
|
|
||||||
|
|||||||
@@ -17,19 +17,7 @@ class _UsedTypedDict(TypedDict):
|
|||||||
class _CustomClass(_UsedTypedDict):
|
class _CustomClass(_UsedTypedDict):
|
||||||
bar: list[int]
|
bar: list[int]
|
||||||
|
|
||||||
|
|
||||||
_UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
_UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
||||||
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||||
|
|
||||||
|
|
||||||
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
|
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
# In `.py` files, we don't flag unused definitions in class scopes (unlike in `.pyi`
|
|
||||||
# files).
|
|
||||||
class _CustomClass3:
|
|
||||||
class _UnusedTypeDict4(TypedDict):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def method(self) -> None:
|
|
||||||
_CustomClass3._UnusedTypeDict4()
|
|
||||||
|
|||||||
@@ -35,13 +35,3 @@ _UnusedTypedDict3 = TypedDict("_UnusedTypedDict3", {"foo": int})
|
|||||||
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
_UsedTypedDict3 = TypedDict("_UsedTypedDict3", {"bar": bytes})
|
||||||
|
|
||||||
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
|
def uses_UsedTypedDict3(arg: _UsedTypedDict3) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
# In `.pyi` files, we flag unused definitions in class scopes as well as in the global
|
|
||||||
# scope (unlike in `.py` files).
|
|
||||||
class _CustomClass3:
|
|
||||||
class _UnusedTypeDict4(TypedDict):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def method(self) -> None:
|
|
||||||
_CustomClass3._UnusedTypeDict4()
|
|
||||||
|
|||||||
@@ -64,5 +64,3 @@ def not_warnings_dot_deprecated(
|
|||||||
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
"Not warnings.deprecated, so this one *should* lead to PYI053 in a stub!" # Error: PYI053
|
||||||
)
|
)
|
||||||
def not_a_deprecated_function() -> None: ...
|
def not_a_deprecated_function() -> None: ...
|
||||||
|
|
||||||
fbaz: str = f"51 character {foo} stringgggggggggggggggggggggggggg" # Error: PYI053
|
|
||||||
|
|||||||
@@ -40,7 +40,4 @@ f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
|||||||
|
|
||||||
# Make sure we do not unescape quotes
|
# Make sure we do not unescape quotes
|
||||||
this_is_fine = "This is an \\'escaped\\' quote"
|
this_is_fine = "This is an \\'escaped\\' quote"
|
||||||
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash" # Q004
|
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
|
||||||
|
|
||||||
# Invalid escapes in bytestrings are also triggered:
|
|
||||||
x = b"\xe7\xeb\x0c\xa1\x1b\x83tN\xce=x\xe9\xbe\x01\xb9\x13B_\xba\xe7\x0c2\xce\'rm\x0e\xcd\xe9.\xf8\xd2" # Q004
|
|
||||||
|
|||||||
@@ -93,15 +93,3 @@ def func():
|
|||||||
|
|
||||||
# OK
|
# OK
|
||||||
raise func()
|
raise func()
|
||||||
|
|
||||||
|
|
||||||
# OK
|
|
||||||
future = executor.submit(float, "a")
|
|
||||||
if future.exception():
|
|
||||||
raise future.exception()
|
|
||||||
|
|
||||||
|
|
||||||
# RSE102
|
|
||||||
future = executor.submit(float, "a")
|
|
||||||
if future.exception():
|
|
||||||
raise future.Exception()
|
|
||||||
|
|||||||
@@ -192,49 +192,3 @@ elif x == 2:
|
|||||||
y = "b"
|
y = "b"
|
||||||
else:
|
else:
|
||||||
y = "c"
|
y = "c"
|
||||||
|
|
||||||
|
|
||||||
# Regression test for: https://github.com/astral-sh/ruff/issues/9732
|
|
||||||
def sb(self):
|
|
||||||
if self._sb is not None: return self._sb
|
|
||||||
else: self._sb = '\033[01;%dm'; self._sa = '\033[0;0m';
|
|
||||||
|
|
||||||
|
|
||||||
def indent(x, y, w, z):
|
|
||||||
if x: # [no-else-return]
|
|
||||||
a = 1
|
|
||||||
return y
|
|
||||||
else:
|
|
||||||
|
|
||||||
c = 3
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
def indent(x, y, w, z):
|
|
||||||
if x: # [no-else-return]
|
|
||||||
a = 1
|
|
||||||
return y
|
|
||||||
else:
|
|
||||||
# comment
|
|
||||||
c = 3
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
def indent(x, y, w, z):
|
|
||||||
if x: # [no-else-return]
|
|
||||||
a = 1
|
|
||||||
return y
|
|
||||||
else:
|
|
||||||
# comment
|
|
||||||
c = 3
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
def indent(x, y, w, z):
|
|
||||||
if x: # [no-else-return]
|
|
||||||
a = 1
|
|
||||||
return y
|
|
||||||
else:
|
|
||||||
# comment
|
|
||||||
c = 3
|
|
||||||
return z
|
|
||||||
|
|||||||
@@ -193,11 +193,3 @@ def func():
|
|||||||
for y in range(5):
|
for y in range(5):
|
||||||
g(x, idx)
|
g(x, idx)
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
async def func():
|
|
||||||
# OK (for loop is async)
|
|
||||||
idx = 0
|
|
||||||
|
|
||||||
async for x in async_gen():
|
|
||||||
g(x, idx)
|
|
||||||
idx += 1
|
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
import trio
|
import trio
|
||||||
|
|
||||||
|
|
||||||
async def func():
|
async def foo():
|
||||||
with trio.fail_after():
|
with trio.fail_after():
|
||||||
...
|
...
|
||||||
|
|
||||||
|
async def foo():
|
||||||
async def func():
|
|
||||||
with trio.fail_at():
|
with trio.fail_at():
|
||||||
await ...
|
await ...
|
||||||
|
|
||||||
|
async def foo():
|
||||||
async def func():
|
|
||||||
with trio.move_on_after():
|
with trio.move_on_after():
|
||||||
...
|
...
|
||||||
|
|
||||||
|
async def foo():
|
||||||
async def func():
|
|
||||||
with trio.move_at():
|
with trio.move_at():
|
||||||
await ...
|
await ...
|
||||||
|
|
||||||
|
|
||||||
async def func():
|
|
||||||
with trio.move_at():
|
|
||||||
async with trio.open_nursery() as nursery:
|
|
||||||
...
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ async def func():
|
|||||||
|
|
||||||
trio.sleep(0) # TRIO115
|
trio.sleep(0) # TRIO115
|
||||||
foo = 0
|
foo = 0
|
||||||
trio.sleep(foo) # OK
|
trio.sleep(foo) # TRIO115
|
||||||
trio.sleep(1) # OK
|
trio.sleep(1) # OK
|
||||||
time.sleep(0) # OK
|
time.sleep(0) # OK
|
||||||
|
|
||||||
@@ -20,26 +20,26 @@ async def func():
|
|||||||
trio.sleep(bar)
|
trio.sleep(bar)
|
||||||
|
|
||||||
x, y = 0, 2000
|
x, y = 0, 2000
|
||||||
trio.sleep(x) # OK
|
trio.sleep(x) # TRIO115
|
||||||
trio.sleep(y) # OK
|
trio.sleep(y) # OK
|
||||||
|
|
||||||
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
|
(a, b, [c, (d, e)]) = (1, 2, (0, [4, 0]))
|
||||||
trio.sleep(c) # OK
|
trio.sleep(c) # TRIO115
|
||||||
trio.sleep(d) # OK
|
trio.sleep(d) # OK
|
||||||
trio.sleep(e) # OK
|
trio.sleep(e) # TRIO115
|
||||||
|
|
||||||
m_x, m_y = 0
|
m_x, m_y = 0
|
||||||
trio.sleep(m_y) # OK
|
trio.sleep(m_y) # OK
|
||||||
trio.sleep(m_x) # OK
|
trio.sleep(m_x) # OK
|
||||||
|
|
||||||
m_a = m_b = 0
|
m_a = m_b = 0
|
||||||
trio.sleep(m_a) # OK
|
trio.sleep(m_a) # TRIO115
|
||||||
trio.sleep(m_b) # OK
|
trio.sleep(m_b) # TRIO115
|
||||||
|
|
||||||
m_c = (m_d, m_e) = (0, 0)
|
m_c = (m_d, m_e) = (0, 0)
|
||||||
trio.sleep(m_c) # OK
|
trio.sleep(m_c) # OK
|
||||||
trio.sleep(m_d) # OK
|
trio.sleep(m_d) # TRIO115
|
||||||
trio.sleep(m_e) # OK
|
trio.sleep(m_e) # TRIO115
|
||||||
|
|
||||||
|
|
||||||
def func():
|
def func():
|
||||||
@@ -63,16 +63,4 @@ def func():
|
|||||||
import trio
|
import trio
|
||||||
|
|
||||||
if (walrus := 0) == 0:
|
if (walrus := 0) == 0:
|
||||||
trio.sleep(walrus) # OK
|
trio.sleep(walrus) # TRIO115
|
||||||
|
|
||||||
|
|
||||||
def func():
|
|
||||||
import trio
|
|
||||||
|
|
||||||
async def main() -> None:
|
|
||||||
sleep = 0
|
|
||||||
for _ in range(2):
|
|
||||||
await trio.sleep(sleep) # OK
|
|
||||||
sleep = 10
|
|
||||||
|
|
||||||
trio.run(main)
|
|
||||||
|
|||||||
18
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH006_1.py
vendored
Normal file
18
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH006_1.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
|
||||||
|
x: "int" | str # TCH006
|
||||||
|
x: ("int" | str) | "bool" # TCH006
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
x: "int" | str # OK
|
||||||
|
|
||||||
|
|
||||||
|
z: list[str, str | "int"] = [] # TCH006
|
||||||
|
|
||||||
|
type A = Value["int" | str] # OK
|
||||||
|
|
||||||
|
OldS = TypeVar('OldS', int | 'str', str) # TCH006
|
||||||
16
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH006_2.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH006_2.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
|
||||||
|
x: "int" | str # TCH006
|
||||||
|
x: ("int" | str) | "bool" # TCH006
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
x: "int" | str # OK
|
||||||
|
|
||||||
|
|
||||||
|
z: list[str, str | "int"] = [] # TCH006
|
||||||
|
|
||||||
|
type A = Value["int" | str] # OK
|
||||||
|
|
||||||
|
OldS = TypeVar('OldS', int | 'str', str) # TCH006
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
|
|
||||||
x: "int" | str # TCH010
|
|
||||||
x: ("int" | str) | "bool" # TCH010
|
|
||||||
|
|
||||||
|
|
||||||
def func():
|
|
||||||
x: "int" | str # OK
|
|
||||||
|
|
||||||
|
|
||||||
z: list[str, str | "int"] = [] # TCH010
|
|
||||||
|
|
||||||
type A = Value["int" | str] # OK
|
|
||||||
|
|
||||||
OldS = TypeVar('OldS', int | 'str', str) # TCH010
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
|
|
||||||
x: "int" | str # TCH010
|
|
||||||
x: ("int" | str) | "bool" # TCH010
|
|
||||||
|
|
||||||
|
|
||||||
def func():
|
|
||||||
x: "int" | str # OK
|
|
||||||
|
|
||||||
|
|
||||||
z: list[str, str | "int"] = [] # TCH010
|
|
||||||
|
|
||||||
type A = Value["int" | str] # OK
|
|
||||||
|
|
||||||
OldS = TypeVar('OldS', int | 'str', str) # TCH010
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import django.settings
|
|
||||||
from library import foo
|
|
||||||
import os
|
|
||||||
import pytz
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from . import local
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from requests import Session
|
|
||||||
|
|
||||||
from my_first_party import my_first_party_object
|
|
||||||
|
|
||||||
from . import my_local_folder_object
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Thing(object):
|
|
||||||
name: str
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import django.settings
|
|
||||||
from library import foo
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
from . import local
|
|
||||||
import sys
|
|
||||||
@@ -19,11 +19,8 @@ numpy.random.seed()
|
|||||||
numpy.random.get_state()
|
numpy.random.get_state()
|
||||||
numpy.random.set_state()
|
numpy.random.set_state()
|
||||||
numpy.random.rand()
|
numpy.random.rand()
|
||||||
numpy.random.ranf()
|
|
||||||
numpy.random.sample()
|
|
||||||
numpy.random.randn()
|
numpy.random.randn()
|
||||||
numpy.random.randint()
|
numpy.random.randint()
|
||||||
numpy.random.random()
|
|
||||||
numpy.random.random_integers()
|
numpy.random.random_integers()
|
||||||
numpy.random.random_sample()
|
numpy.random.random_sample()
|
||||||
numpy.random.choice()
|
numpy.random.choice()
|
||||||
@@ -38,6 +35,7 @@ numpy.random.exponential()
|
|||||||
numpy.random.f()
|
numpy.random.f()
|
||||||
numpy.random.gamma()
|
numpy.random.gamma()
|
||||||
numpy.random.geometric()
|
numpy.random.geometric()
|
||||||
|
numpy.random.get_state()
|
||||||
numpy.random.gumbel()
|
numpy.random.gumbel()
|
||||||
numpy.random.hypergeometric()
|
numpy.random.hypergeometric()
|
||||||
numpy.random.laplace()
|
numpy.random.laplace()
|
||||||
|
|||||||
@@ -50,29 +50,6 @@ class MetaClass(ABCMeta):
|
|||||||
def static_method(not_cls) -> bool:
|
def static_method(not_cls) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class ClsInArgsClass(ABCMeta):
|
|
||||||
def cls_as_argument(this, cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cls_as_pos_only_argument(this, cls, /):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cls_as_kw_only_argument(this, *, cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cls_as_varags(this, *cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cls_as_kwargs(this, **cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class RenamingInMethodBodyClass(ABCMeta):
|
|
||||||
def bad_method(this):
|
|
||||||
this = this
|
|
||||||
this
|
|
||||||
|
|
||||||
def bad_method(this):
|
|
||||||
self = this
|
|
||||||
|
|
||||||
def func(x):
|
def func(x):
|
||||||
return x
|
return x
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class PosOnlyClass:
|
|||||||
def good_method_pos_only(self, blah, /, something: str):
|
def good_method_pos_only(self, blah, /, something: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bad_method_pos_only(this, blah, /, something: str):
|
def bad_method_pos_only(this, blah, /, self, something: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -93,27 +93,3 @@ class ModelClass:
|
|||||||
@foobar.thisisstatic
|
@foobar.thisisstatic
|
||||||
def badstatic(foo):
|
def badstatic(foo):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class SelfInArgsClass:
|
|
||||||
def self_as_argument(this, self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def self_as_pos_only_argument(this, self, /):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def self_as_kw_only_argument(this, *, self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def self_as_varags(this, *self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def self_as_kwargs(this, **self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class RenamingInMethodBodyClass:
|
|
||||||
def bad_method(this):
|
|
||||||
this = this
|
|
||||||
this
|
|
||||||
|
|
||||||
def bad_method(this):
|
|
||||||
self = this
|
|
||||||
|
|||||||
@@ -36,47 +36,35 @@ for i in list( # Comment
|
|||||||
): # PERF101
|
): # PERF101
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for i in list(foo_dict): # OK
|
for i in list(foo_dict): # Ok
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for i in list(1): # OK
|
for i in list(1): # Ok
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for i in list(foo_int): # OK
|
for i in list(foo_int): # Ok
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
for i in itertools.product(foo_int): # OK
|
for i in itertools.product(foo_int): # Ok
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for i in list(foo_list): # OK
|
for i in list(foo_list): # Ok
|
||||||
foo_list.append(i + 1)
|
foo_list.append(i + 1)
|
||||||
|
|
||||||
for i in list(foo_list): # PERF101
|
for i in list(foo_list): # PERF101
|
||||||
# Make sure we match the correct list
|
# Make sure we match the correct list
|
||||||
other_list.append(i + 1)
|
other_list.append(i + 1)
|
||||||
|
|
||||||
for i in list(foo_tuple): # OK
|
for i in list(foo_tuple): # Ok
|
||||||
foo_tuple.append(i + 1)
|
foo_tuple.append(i + 1)
|
||||||
|
|
||||||
for i in list(foo_set): # OK
|
for i in list(foo_set): # Ok
|
||||||
foo_set.append(i + 1)
|
foo_set.append(i + 1)
|
||||||
|
|
||||||
x, y, nested_tuple = (1, 2, (3, 4, 5))
|
x, y, nested_tuple = (1, 2, (3, 4, 5))
|
||||||
|
|
||||||
for i in list(nested_tuple): # PERF101
|
for i in list(nested_tuple): # PERF101
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for i in list(foo_list): # OK
|
|
||||||
if True:
|
|
||||||
foo_list.append(i + 1)
|
|
||||||
|
|
||||||
for i in list(foo_list): # OK
|
|
||||||
if True:
|
|
||||||
foo_list[i] = i + 1
|
|
||||||
|
|
||||||
for i in list(foo_list): # OK
|
|
||||||
if True:
|
|
||||||
del foo_list[i + 1]
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# These rules test for intentional "odd" formatting. Using a formatter fixes that
|
|
||||||
[E{1,2,3}*.py]
|
|
||||||
generated_code = true
|
|
||||||
ij_formatter_enabled = false
|
|
||||||
|
|
||||||
[W*.py]
|
|
||||||
generated_code = true
|
|
||||||
ij_formatter_enabled = false
|
|
||||||
@@ -147,15 +147,3 @@ ham[upper : ]
|
|||||||
|
|
||||||
#: E203:1:10
|
#: E203:1:10
|
||||||
ham[upper :]
|
ham[upper :]
|
||||||
|
|
||||||
#: Okay
|
|
||||||
ham[lower +1 :, "columnname"]
|
|
||||||
|
|
||||||
#: E203:1:13
|
|
||||||
ham[lower + 1 :, "columnname"]
|
|
||||||
|
|
||||||
#: Okay
|
|
||||||
f"{ham[lower +1 :, "columnname"]}"
|
|
||||||
|
|
||||||
#: E203:1:13
|
|
||||||
f"{ham[lower + 1 :, "columnname"]}"
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
a = (1 or)
|
|
||||||
@@ -1,871 +0,0 @@
|
|||||||
"""Fixtures for the errors E301, E302, E303, E304, E305 and E306.
|
|
||||||
Since these errors are about new lines, each test starts with either "No error" or "# E30X".
|
|
||||||
Each test's end is signaled by a "# end" line.
|
|
||||||
There should be no E30X error outside of a test's bound.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
class Class:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
class Class:
|
|
||||||
"""Docstring"""
|
|
||||||
def __init__(self) -> None:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
def func():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
# comment
|
|
||||||
class Class:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
# comment
|
|
||||||
def func():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def foo():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Foo(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Bar(object):
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
class Class(object):
|
|
||||||
|
|
||||||
def func1():
|
|
||||||
pass
|
|
||||||
|
|
||||||
def func2():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
class Class(object):
|
|
||||||
|
|
||||||
def func1():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# comment
|
|
||||||
def func2():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
class Class:
|
|
||||||
|
|
||||||
def func1():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# comment
|
|
||||||
def func2():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# This is a
|
|
||||||
# ... multi-line comment
|
|
||||||
|
|
||||||
def func3():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# This is a
|
|
||||||
# ... multi-line comment
|
|
||||||
|
|
||||||
@decorator
|
|
||||||
class Class:
|
|
||||||
|
|
||||||
def func1():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
def func2():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def func3():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
try:
|
|
||||||
from nonexistent import Bar
|
|
||||||
except ImportError:
|
|
||||||
class Bar(object):
|
|
||||||
"""This is a Bar replacement"""
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
def with_feature(f):
|
|
||||||
"""Some decorator"""
|
|
||||||
wrapper = f
|
|
||||||
if has_this_feature(f):
|
|
||||||
def wrapper(*args):
|
|
||||||
call_feature(args[0])
|
|
||||||
return f(*args)
|
|
||||||
return wrapper
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
try:
|
|
||||||
next
|
|
||||||
except NameError:
|
|
||||||
def next(iterator, default):
|
|
||||||
for item in iterator:
|
|
||||||
return item
|
|
||||||
return default
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
def fn():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Foo():
|
|
||||||
"""Class Foo"""
|
|
||||||
|
|
||||||
def fn():
|
|
||||||
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# No error
|
|
||||||
# comment
|
|
||||||
def c():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
|
|
||||||
def d():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# This is a
|
|
||||||
# ... multi-line comment
|
|
||||||
|
|
||||||
# And this one is
|
|
||||||
# ... a second paragraph
|
|
||||||
# ... which spans on 3 lines
|
|
||||||
|
|
||||||
|
|
||||||
# Function `e` is below
|
|
||||||
# NOTE: Hey this is a testcase
|
|
||||||
|
|
||||||
def e():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def fn():
|
|
||||||
print()
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Comment 1
|
|
||||||
|
|
||||||
# Comment 2
|
|
||||||
|
|
||||||
|
|
||||||
# Comment 3
|
|
||||||
|
|
||||||
def fn2():
|
|
||||||
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
if __name__ == '__main__':
|
|
||||||
foo()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
defaults = {}
|
|
||||||
defaults.update({})
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def foo(x):
|
|
||||||
classification = x
|
|
||||||
definitely = not classification
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def bar(): pass
|
|
||||||
def baz(): pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def foo():
|
|
||||||
def bar(): pass
|
|
||||||
def baz(): pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
from typing import overload
|
|
||||||
from typing import Union
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
@overload
|
|
||||||
def f(x: int) -> int: ...
|
|
||||||
@overload
|
|
||||||
def f(x: str) -> str: ...
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def f(x: Union[int, str]) -> Union[int, str]:
|
|
||||||
return x
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
from typing import Protocol
|
|
||||||
|
|
||||||
|
|
||||||
class C(Protocol):
|
|
||||||
@property
|
|
||||||
def f(self) -> int: ...
|
|
||||||
@property
|
|
||||||
def g(self) -> str: ...
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def f(
|
|
||||||
a,
|
|
||||||
):
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
if True:
|
|
||||||
class Class:
|
|
||||||
"""Docstring"""
|
|
||||||
|
|
||||||
def function(self):
|
|
||||||
...
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
if True:
|
|
||||||
def function(self):
|
|
||||||
...
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
@decorator
|
|
||||||
# comment
|
|
||||||
@decorator
|
|
||||||
def function():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
class Class:
|
|
||||||
def method(self):
|
|
||||||
if True:
|
|
||||||
def function():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
@decorator
|
|
||||||
async def function(data: None) -> None:
|
|
||||||
...
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
class Class:
|
|
||||||
def method():
|
|
||||||
"""docstring"""
|
|
||||||
# comment
|
|
||||||
def function():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
try:
|
|
||||||
if True:
|
|
||||||
# comment
|
|
||||||
class Class:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def f():
|
|
||||||
def f():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
class MyClass:
|
|
||||||
# comment
|
|
||||||
def method(self) -> None:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def function1():
|
|
||||||
# Comment
|
|
||||||
def function2():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
async def function1():
|
|
||||||
await function2()
|
|
||||||
async with function3():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
if (
|
|
||||||
cond1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
and cond2
|
|
||||||
):
|
|
||||||
pass
|
|
||||||
#end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
async def function1():
|
|
||||||
await function2()
|
|
||||||
async with function3():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
async def function1():
|
|
||||||
await function2()
|
|
||||||
async with function3():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
async def function1():
|
|
||||||
await function2()
|
|
||||||
async with function3():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
class Test:
|
|
||||||
async
|
|
||||||
|
|
||||||
def a(self): pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
class Test:
|
|
||||||
def a():
|
|
||||||
pass
|
|
||||||
# wrongly indented comment
|
|
||||||
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# no error
|
|
||||||
def test():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Wrongly indented comment
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E301
|
|
||||||
class Class(object):
|
|
||||||
|
|
||||||
def func1():
|
|
||||||
pass
|
|
||||||
def func2():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E301
|
|
||||||
class Class:
|
|
||||||
|
|
||||||
def fn1():
|
|
||||||
pass
|
|
||||||
# comment
|
|
||||||
def fn2():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E301
|
|
||||||
class Class:
|
|
||||||
"""Class for minimal repo."""
|
|
||||||
|
|
||||||
columns = []
|
|
||||||
@classmethod
|
|
||||||
def cls_method(cls) -> None:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E301
|
|
||||||
class Class:
|
|
||||||
"""Class for minimal repo."""
|
|
||||||
|
|
||||||
def method(cls) -> None:
|
|
||||||
pass
|
|
||||||
@classmethod
|
|
||||||
def cls_method(cls) -> None:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
"""Main module."""
|
|
||||||
def fn():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
import sys
|
|
||||||
def get_sys_path():
|
|
||||||
return sys.path
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
def a():
|
|
||||||
pass
|
|
||||||
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
def a():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
def a():
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
async def x():
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def x(y: int = 1):
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
def bar():
|
|
||||||
pass
|
|
||||||
def baz(): pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
def bar(): pass
|
|
||||||
def baz():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
def f():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# comment
|
|
||||||
@decorator
|
|
||||||
def g():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E302
|
|
||||||
class Test:
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def method1():
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def method2():
|
|
||||||
return 22
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
def fn():
|
|
||||||
_ = None
|
|
||||||
|
|
||||||
|
|
||||||
# arbitrary comment
|
|
||||||
|
|
||||||
def inner(): # E306 not expected (pycodestyle detects E306)
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
def fn():
|
|
||||||
_ = None
|
|
||||||
|
|
||||||
|
|
||||||
# arbitrary comment
|
|
||||||
def inner(): # E306 not expected (pycodestyle detects E306)
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303:5:1
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
print()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303:5:5 E303:8:5
|
|
||||||
def a():
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
|
|
||||||
# another comment
|
|
||||||
|
|
||||||
print()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
#!python
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""This class docstring comes on line 5.
|
|
||||||
It gives error E303: too many blank lines (3)
|
|
||||||
"""
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
class Class:
|
|
||||||
def a(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def b(self):
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
if True:
|
|
||||||
a = 1
|
|
||||||
|
|
||||||
|
|
||||||
a = 2
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
class Test:
|
|
||||||
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
|
|
||||||
# another comment
|
|
||||||
|
|
||||||
def test(self): pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
class Test:
|
|
||||||
def a(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# wrongly indented comment
|
|
||||||
|
|
||||||
|
|
||||||
def b(self):
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E303
|
|
||||||
def fn():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E304
|
|
||||||
@decorator
|
|
||||||
|
|
||||||
def function():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E304
|
|
||||||
@decorator
|
|
||||||
|
|
||||||
# comment E304 not expected
|
|
||||||
def function():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E304
|
|
||||||
@decorator
|
|
||||||
|
|
||||||
# comment E304 not expected
|
|
||||||
|
|
||||||
|
|
||||||
# second comment E304 not expected
|
|
||||||
def function():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E305:7:1
|
|
||||||
def fn():
|
|
||||||
print()
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
# another comment
|
|
||||||
fn()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E305
|
|
||||||
class Class():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
# another comment
|
|
||||||
a = 1
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E305:8:1
|
|
||||||
def fn():
|
|
||||||
print()
|
|
||||||
|
|
||||||
# comment
|
|
||||||
|
|
||||||
# another comment
|
|
||||||
|
|
||||||
try:
|
|
||||||
fn()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E305:5:1
|
|
||||||
def a():
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Two spaces before comments, too.
|
|
||||||
if a():
|
|
||||||
a()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
#: E305:8:1
|
|
||||||
# Example from https://github.com/PyCQA/pycodestyle/issues/400
|
|
||||||
import stuff
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
blah, blah
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306:3:5
|
|
||||||
def a():
|
|
||||||
x = 1
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
#: E306:3:5
|
|
||||||
async def a():
|
|
||||||
x = 1
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
#: E306:3:5 E306:5:9
|
|
||||||
def a():
|
|
||||||
x = 2
|
|
||||||
def b():
|
|
||||||
x = 1
|
|
||||||
def c():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306:3:5 E306:6:5
|
|
||||||
def a():
|
|
||||||
x = 1
|
|
||||||
class C:
|
|
||||||
pass
|
|
||||||
x = 2
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306
|
|
||||||
def foo():
|
|
||||||
def bar():
|
|
||||||
pass
|
|
||||||
def baz(): pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306:3:5
|
|
||||||
def foo():
|
|
||||||
def bar(): pass
|
|
||||||
def baz():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306
|
|
||||||
def a():
|
|
||||||
x = 2
|
|
||||||
@decorator
|
|
||||||
def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306
|
|
||||||
def a():
|
|
||||||
x = 2
|
|
||||||
@decorator
|
|
||||||
async def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# E306
|
|
||||||
def a():
|
|
||||||
x = 2
|
|
||||||
async def b():
|
|
||||||
pass
|
|
||||||
# end
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
from typing import Any, Sequence
|
|
||||||
|
|
||||||
class MissingCommand(TypeError): ...
|
|
||||||
class AnoherClass: ...
|
|
||||||
|
|
||||||
def a(): ...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def a(arg: int): ...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def a(arg: int, name: str): ...
|
|
||||||
|
|
||||||
|
|
||||||
def grouped1(): ...
|
|
||||||
def grouped2(): ...
|
|
||||||
def grouped3( ): ...
|
|
||||||
|
|
||||||
|
|
||||||
class BackendProxy:
|
|
||||||
backend_module: str
|
|
||||||
backend_object: str | None
|
|
||||||
backend: Any
|
|
||||||
|
|
||||||
def grouped1(): ...
|
|
||||||
def grouped2(): ...
|
|
||||||
def grouped3( ): ...
|
|
||||||
@decorated
|
|
||||||
|
|
||||||
def with_blank_line(): ...
|
|
||||||
|
|
||||||
|
|
||||||
def ungrouped(): ...
|
|
||||||
a = "test"
|
|
||||||
|
|
||||||
def function_def():
|
|
||||||
pass
|
|
||||||
b = "test"
|
|
||||||
|
|
||||||
|
|
||||||
def outer():
|
|
||||||
def inner():
|
|
||||||
pass
|
|
||||||
def inner2():
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Foo: ...
|
|
||||||
class Bar: ...
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
"""Test where the error is after the module's docstring."""
|
|
||||||
|
|
||||||
def fn():
|
|
||||||
pass
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
"Test where the first line is a comment, " + "and the rule violation follows it."
|
|
||||||
|
|
||||||
def fn():
|
|
||||||
pass
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
def fn1():
|
|
||||||
pass
|
|
||||||
|
|
||||||
def fn2():
|
|
||||||
pass
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user