Compare commits
44 Commits
v0.2.2
...
rc-extensi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f53feee00 | ||
|
|
b793611fa6 | ||
|
|
be61cb3a68 | ||
|
|
8dc22d5793 | ||
|
|
15b87ea8be | ||
|
|
c25f1cd12a | ||
|
|
f5904a20d5 | ||
|
|
77c5561646 | ||
|
|
ab4bd71755 | ||
|
|
5abf662365 | ||
|
|
14fa1c5b52 | ||
|
|
0421c41ff7 | ||
|
|
ad4695d3eb | ||
|
|
8c58ebee37 | ||
|
|
5023874355 | ||
|
|
5554510597 | ||
|
|
1341e064a7 | ||
|
|
bd98d6884b | ||
|
|
761d4d42f1 | ||
|
|
fc8738f52a | ||
|
|
1711bca4a0 | ||
|
|
51ce88bb23 | ||
|
|
36d8b03b5f | ||
|
|
a284c711bf | ||
|
|
8c20f14e62 | ||
|
|
946028e358 | ||
|
|
6fe15e7289 | ||
|
|
7d9ce5049a | ||
|
|
ecd5a7035d | ||
|
|
175c266de3 | ||
|
|
4997c681f1 | ||
|
|
7eafba2a4d | ||
|
|
0f70c99c42 | ||
|
|
ee4efdba96 | ||
|
|
0d363ab239 | ||
|
|
68b8abf9c6 | ||
|
|
1c8851e5fb | ||
|
|
4ac19993cf | ||
|
|
5cd3c6ef07 | ||
|
|
e94a2615a8 | ||
|
|
77f577cba7 | ||
|
|
49a46c2880 | ||
|
|
67e17e2750 | ||
|
|
e1928be36e |
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -7,3 +7,9 @@
|
||||
|
||||
# Jupyter
|
||||
/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
|
||||
|
||||
11
.github/workflows/ci.yaml
vendored
11
.github/workflows/ci.yaml
vendored
@@ -133,7 +133,7 @@ jobs:
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
@@ -238,7 +238,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download comparison Ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
@@ -250,6 +250,7 @@ jobs:
|
||||
with:
|
||||
name: ruff
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
|
||||
- name: Install ruff-ecosystem
|
||||
@@ -324,13 +325,13 @@ jobs:
|
||||
run: |
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload PR Number
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload Results
|
||||
with:
|
||||
name: ecosystem-result
|
||||
@@ -484,7 +485,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download development ruff binary
|
||||
id: ruff-target
|
||||
with:
|
||||
|
||||
74
.github/workflows/release.yaml
vendored
74
.github/workflows/release.yaml
vendored
@@ -52,9 +52,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
|
||||
macos-x86_64:
|
||||
@@ -80,9 +80,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-macos-x86_64
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -90,9 +90,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-macos-x86_64
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -119,9 +119,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-aarch64-apple-darwin
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -129,9 +129,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-aarch64-apple-darwin
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -170,9 +170,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
shell: bash
|
||||
@@ -181,9 +181,9 @@ jobs:
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
|
||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.zip
|
||||
*.sha256
|
||||
@@ -218,9 +218,9 @@ jobs:
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -228,9 +228,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -251,8 +251,12 @@ jobs:
|
||||
arch: s390x
|
||||
- target: powerpc64le-unknown-linux-gnu
|
||||
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
|
||||
arch: ppc64
|
||||
# see https://github.com/astral-sh/ruff/issues/10073
|
||||
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -285,9 +289,9 @@ jobs:
|
||||
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
ruff --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -295,9 +299,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -337,9 +341,9 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/ruff check --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -347,9 +351,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -394,9 +398,9 @@ jobs:
|
||||
.venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
|
||||
.venv/bin/ruff check --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
name: wheels-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
- name: "Archive binary"
|
||||
run: |
|
||||
@@ -404,9 +408,9 @@ jobs:
|
||||
tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff
|
||||
shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
name: binaries-${{ matrix.platform.target }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
*.sha256
|
||||
@@ -463,10 +467,11 @@ jobs:
|
||||
# For pypi trusted publishing
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
@@ -506,10 +511,11 @@ jobs:
|
||||
# For GitHub release publishing
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
pattern: binaries-*
|
||||
path: binaries
|
||||
merge-multiple: true
|
||||
- name: "Publish to GitHub"
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
120
Cargo.lock
generated
120
Cargo.lock
generated
@@ -123,9 +123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -217,9 +217,9 @@ checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.9.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.5",
|
||||
@@ -312,9 +312,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
|
||||
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -322,9 +322,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
|
||||
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -383,7 +383,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -407,9 +407,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eb4ab4dcb6554eb4f590fb16f99d3b102ab76f5f56554c9a5340518b32c499b"
|
||||
checksum = "4b85b056aa0541d1975ebc524149dde72803a5d7352b6aebf9eabc44f9017246"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"libc",
|
||||
@@ -418,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc07a3d3f7e0c8961d0ffdee149d39b231bafdcdc3d978dc5ad790c615f55f3f"
|
||||
checksum = "02ae9de916d6315a5129bca2fc7957285f0b9f77a2f6a8734a0a146caee2b0b6"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"colored",
|
||||
@@ -592,7 +592,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -603,7 +603,7 @@ checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -914,35 +914,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hoot"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df22a4d90f1b0e65fe3e0d6ee6a4608cc4d81f4b2eb3e670f44bb6bde711e452"
|
||||
dependencies = [
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hootbin"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "354e60868e49ea1a39c44b9562ad207c4259dc6eabf9863bf3b0f058c55cfdb2"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"hoot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@@ -1077,9 +1048,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.34.0"
|
||||
version = "1.35.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
|
||||
checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1130,7 +1101,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1709,7 +1680,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1960,7 +1931,7 @@ dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2212,7 +2183,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2612,24 +2583,24 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.196"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132"
|
||||
checksum = "4c1432112bce8b966497ac46519535189a3250a3812cd27a999678a69756f79f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
@@ -2638,13 +2609,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.196"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2707,7 +2678,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2817,7 +2788,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2839,9 +2810,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
version = "2.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2927,7 +2898,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2938,7 +2909,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2959,7 +2930,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3096,7 +3067,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3262,13 +3233,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.9.5"
|
||||
version = "2.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b52731d03d6bb2fd18289d4028aee361d6c28d44977846793b994b13cdcc64d"
|
||||
checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"hootbin",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
@@ -3316,7 +3286,7 @@ checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3410,7 +3380,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3444,7 +3414,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3477,7 +3447,7 @@ checksum = "a5211b7550606857312bba1d978a8ec75692eae187becc5e680444fffc5e6f89"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3742,7 +3712,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
46
Cargo.toml
46
Cargo.toml
@@ -14,39 +14,39 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.79" }
|
||||
anyhow = { version = "1.0.80" }
|
||||
argfile = { version = "0.1.6" }
|
||||
assert_cmd = { version = "2.0.13" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
bstr = { version = "1.9.0" }
|
||||
bstr = { version = "1.9.1" }
|
||||
cachedir = { version = "0.3.1" }
|
||||
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
clap = { version = "4.5.1", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.5.1" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
|
||||
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
|
||||
colored = { version = "2.1.0" }
|
||||
configparser = { version = "3.0.3" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version ="3.0.1"}
|
||||
countme = { version = "3.0.1" }
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
dirs = { version = "5.0.0" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version ="0.10.1"}
|
||||
env_logger = { version = "0.10.1" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.23" }
|
||||
fs-err = { version ="2.11.0"}
|
||||
fs-err = { version = "2.11.0" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
hexf-parse = { version ="0.2.1"}
|
||||
hexf-parse = { version = "0.2.1" }
|
||||
ignore = { version = "0.4.22" }
|
||||
imara-diff ={ version = "0.1.5"}
|
||||
imara-diff = { version = "0.1.5" }
|
||||
imperative = { version = "1.0.4" }
|
||||
indicatif ={ version = "0.17.8"}
|
||||
indoc ={ version = "2.0.4"}
|
||||
insta = { version = "1.34.0", feature = ["filters", "glob"] }
|
||||
indicatif = { version = "0.17.8" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.35.1", feature = ["filters", "glob"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
is-macro = { version = "0.3.5" }
|
||||
is-wsl = { version = "0.4.0" }
|
||||
@@ -57,7 +57,7 @@ lexical-parse-float = { version = "0.8.0", features = ["format"] }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = { version = "2.7.1" }
|
||||
mimalloc = { version ="0.1.39"}
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
@@ -75,35 +75,35 @@ regex = { version = "1.10.2" }
|
||||
result-like = { version = "0.5.0" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version ="4.1.0"}
|
||||
semver = { version = "1.0.21" }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.3" }
|
||||
seahash = { version = "4.1.0" }
|
||||
semver = { version = "1.0.22" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.4" }
|
||||
serde_json = { version = "1.0.113" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
shlex = { version ="1.3.0"}
|
||||
shlex = { version = "1.3.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.1" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.40" }
|
||||
tempfile = { version ="3.9.0"}
|
||||
syn = { version = "2.0.51" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.57" }
|
||||
tikv-jemallocator = { version ="0.5.0"}
|
||||
tikv-jemallocator = { version = "0.5.0" }
|
||||
toml = { version = "0.8.9" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
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-width = { version = "0.1.11" }
|
||||
unicode_names2 = { version = "1.2.1" }
|
||||
ureq = { version = "2.9.1" }
|
||||
ureq = { version = "2.9.6" }
|
||||
url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: chartboost/ruff-action@v1
|
||||
```
|
||||
|
||||
@@ -378,6 +378,7 @@ Ruff is released under the MIT license.
|
||||
|
||||
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))
|
||||
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
|
||||
- [Apache Airflow](https://github.com/apache/airflow)
|
||||
|
||||
@@ -745,38 +745,34 @@ 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 TomlParseFailureKind {
|
||||
SyntaxError,
|
||||
UnknownOption,
|
||||
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 std::fmt::Display for TomlParseFailureKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let display = match self {
|
||||
Self::SyntaxError => "The supplied argument is not valid TOML",
|
||||
Self::UnknownOption => {
|
||||
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"
|
||||
}
|
||||
};
|
||||
write!(f, "{display}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TomlParseFailure {
|
||||
kind: TomlParseFailureKind,
|
||||
underlying_error: toml::de::Error,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TomlParseFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let TomlParseFailure {
|
||||
kind,
|
||||
underlying_error,
|
||||
} = self;
|
||||
let display = format!("{kind}:\n\n{underlying_error}");
|
||||
write!(f, "{}", display.trim_end())
|
||||
Self::ExtendPassedViaConfigFlag => "Cannot include `extend` in a --config flag value",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,18 +823,19 @@ impl TypedValueParser for ConfigArgumentParser {
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
let toml_parse_error = match toml::Table::from_str(value) {
|
||||
Ok(table) => match table.try_into() {
|
||||
Ok(option) => return Ok(SingleConfigArgument::SettingsOverride(Arc::new(option))),
|
||||
Err(underlying_error) => TomlParseFailure {
|
||||
kind: TomlParseFailureKind::UnknownOption,
|
||||
underlying_error,
|
||||
},
|
||||
},
|
||||
Err(underlying_error) => TomlParseFailure {
|
||||
kind: TomlParseFailureKind::SyntaxError,
|
||||
underlying_error,
|
||||
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);
|
||||
@@ -853,6 +850,21 @@ impl TypedValueParser for ConfigArgumentParser {
|
||||
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());
|
||||
@@ -881,12 +893,16 @@ The path `{value}` does not exist"
|
||||
));
|
||||
}
|
||||
} else if value.contains('=') {
|
||||
tip.push_str(&format!("\n\n{toml_parse_error}"));
|
||||
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.into()]),
|
||||
clap::error::ContextValue::StyledStrs(vec![tip]),
|
||||
);
|
||||
|
||||
Err(new_error)
|
||||
|
||||
@@ -7,10 +7,15 @@ use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use regex::escape;
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
fn tempdir_filter(tempdir: &TempDir) -> String {
|
||||
format!(r"{}\\?/?", escape(tempdir.path().to_str().unwrap()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_options() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
@@ -147,28 +152,29 @@ fn too_many_config_files() -> Result<()> {
|
||||
let ruff2_dot_toml = tempdir.path().join("ruff2.toml");
|
||||
fs::File::create(&ruff_dot_toml)?;
|
||||
fs::File::create(&ruff2_dot_toml)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: You cannot specify more than one configuration file on the command line.
|
||||
|
||||
tip: remove either `--config={}` or `--config={}`.
|
||||
For more information, try `--help`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
ruff2_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
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(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
.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(())
|
||||
}
|
||||
|
||||
@@ -177,27 +183,29 @@ 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)?;
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
ruff failed
|
||||
Cause: 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`.
|
||||
|
||||
",
|
||||
ruff_dot_toml.display(),
|
||||
);
|
||||
let cmd = Command::new(get_cargo_bin(BIN_NAME))
|
||||
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(".")
|
||||
.output()?;
|
||||
let stderr = std::str::from_utf8(&cmd.stderr)?;
|
||||
assert_eq!(stderr, expected_stderr);
|
||||
.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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -595,6 +595,24 @@ fn too_many_config_files() -> Result<()> {
|
||||
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()?;
|
||||
|
||||
@@ -232,7 +232,7 @@ linter.flake8_bandit.check_typed_exception = false
|
||||
linter.flake8_bugbear.extend_immutable_calls = []
|
||||
linter.flake8_builtins.builtins_ignorelist = []
|
||||
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.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
|
||||
@@ -78,27 +78,28 @@ impl<'a> PrintQueue<'a> {
|
||||
impl<'a> Queue<'a> for PrintQueue<'a> {
|
||||
fn pop(&mut self) -> Option<&'a FormatElement> {
|
||||
let elements = self.element_slices.last_mut()?;
|
||||
elements.next().or_else(|| {
|
||||
self.element_slices.pop();
|
||||
let elements = self.element_slices.last_mut()?;
|
||||
elements.next()
|
||||
})
|
||||
elements.next().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
self.element_slices.pop();
|
||||
let elements = self.element_slices.last_mut()?;
|
||||
elements.next()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
||||
let mut slices = self.element_slices.iter().rev();
|
||||
let slice = slices.next()?;
|
||||
|
||||
match slice.as_slice().first() {
|
||||
Some(element) => Some(element),
|
||||
None => {
|
||||
if let Some(next_elements) = slices.next() {
|
||||
next_elements.as_slice().first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
slice.as_slice().first().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
slices
|
||||
.next()
|
||||
.and_then(|next_elements| next_elements.as_slice().first())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
||||
@@ -146,24 +147,30 @@ impl<'a, 'print> FitsQueue<'a, 'print> {
|
||||
|
||||
impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
|
||||
fn pop(&mut self) -> Option<&'a FormatElement> {
|
||||
self.queue.pop().or_else(|| {
|
||||
if let Some(next_slice) = self.rest_elements.next_back() {
|
||||
self.queue.extend_back(next_slice.as_slice());
|
||||
self.queue.pop()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.queue.pop().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
if let Some(next_slice) = self.rest_elements.next_back() {
|
||||
self.queue.extend_back(next_slice.as_slice());
|
||||
self.queue.pop()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn top_with_interned(&self) -> Option<&'a FormatElement> {
|
||||
self.queue.top_with_interned().or_else(|| {
|
||||
if let Some(next_elements) = self.rest_elements.as_slice().last() {
|
||||
next_elements.as_slice().first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.queue.top_with_interned().or_else(
|
||||
#[cold]
|
||||
|| {
|
||||
if let Some(next_elements) = self.rest_elements.as_slice().last() {
|
||||
next_elements.as_slice().first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn extend_back(&mut self, elements: &'a [FormatElement]) {
|
||||
|
||||
@@ -28,3 +28,11 @@ dictionary = {
|
||||
}
|
||||
|
||||
#import os # noqa
|
||||
|
||||
# case 1:
|
||||
# try:
|
||||
# try: # with comment
|
||||
# try: print()
|
||||
# except:
|
||||
# except Foo:
|
||||
# except Exception as e: print(e)
|
||||
|
||||
@@ -119,3 +119,16 @@ def func(x: bool):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -147,3 +147,9 @@ ham[upper : ]
|
||||
|
||||
#: E203:1:10
|
||||
ham[upper :]
|
||||
|
||||
#: Okay
|
||||
ham[lower +1 :, "columnname"]
|
||||
|
||||
#: E203:1:13
|
||||
ham[lower + 1 :, "columnname"]
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_2.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_2.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
|
||||
os.environ["WORLD_SIZE"] = "1"
|
||||
os.putenv("CUDA_VISIBLE_DEVICES", "4")
|
||||
del os.environ["WORLD_SIZE"]
|
||||
|
||||
import torch
|
||||
@@ -14,3 +14,6 @@ class Chassis(RobotModuleTemplate):
|
||||
" \
|
||||
\
|
||||
|
||||
'''blank line with whitespace
|
||||
|
||||
inside a multiline string'''
|
||||
|
||||
32
crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py
vendored
Normal file
32
crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
d = {1: 1, 2: 2}
|
||||
d_tuple = {(1, 2): 3, (4, 5): 6}
|
||||
d_tuple_annotated: Any = {(1, 2): 3, (4, 5): 6}
|
||||
d_tuple_incorrect_tuple = {(1,): 3, (4, 5): 6}
|
||||
l = [1, 2]
|
||||
s1 = {1, 2}
|
||||
s2 = {1, 2, 3}
|
||||
|
||||
# Errors
|
||||
for k, v in d:
|
||||
pass
|
||||
|
||||
for k, v in d_tuple_incorrect_tuple:
|
||||
pass
|
||||
|
||||
|
||||
# Non errors
|
||||
for k, v in d.items():
|
||||
pass
|
||||
for k in d.keys():
|
||||
pass
|
||||
for i, v in enumerate(l):
|
||||
pass
|
||||
for i, v in s1.intersection(s2):
|
||||
pass
|
||||
for a, b in d_tuple:
|
||||
pass
|
||||
for a, b in d_tuple_annotated:
|
||||
pass
|
||||
@@ -17,3 +17,14 @@ class Fruit:
|
||||
return choice(Fruit.COLORS)
|
||||
|
||||
pick_one_color = staticmethod(pick_one_color)
|
||||
|
||||
class Class:
|
||||
def class_method(cls):
|
||||
pass
|
||||
|
||||
class_method = classmethod(class_method);another_statement
|
||||
|
||||
def static_method():
|
||||
pass
|
||||
|
||||
static_method = staticmethod(static_method);
|
||||
|
||||
@@ -51,3 +51,7 @@ foo == foo or foo == bar # Self-comparison.
|
||||
foo[0] == "a" or foo[0] == "b" # Subscripts.
|
||||
|
||||
foo() == "a" or foo() == "b" # Calls.
|
||||
|
||||
import sys
|
||||
|
||||
sys.platform == "win32" or sys.platform == "emscripten" # sys attributes
|
||||
|
||||
@@ -33,7 +33,7 @@ bool(b"")
|
||||
bool(1.0)
|
||||
int().denominator
|
||||
|
||||
# These become string or byte literals
|
||||
# These become literals
|
||||
str()
|
||||
str("foo")
|
||||
str("""
|
||||
@@ -53,3 +53,9 @@ bool(False)
|
||||
|
||||
# These become a literal but retain parentheses
|
||||
int(1).denominator
|
||||
|
||||
# These too are literals in spirit
|
||||
int(+1)
|
||||
int(-1)
|
||||
float(+1.0)
|
||||
float(-1.0)
|
||||
|
||||
@@ -57,6 +57,11 @@ revision_heads_map_ast = [
|
||||
list(zip(x, y))[0]
|
||||
[*zip(x, y)][0]
|
||||
|
||||
# RUF015 (pop)
|
||||
list(x).pop(0)
|
||||
|
||||
# OK
|
||||
list(x).pop(1)
|
||||
|
||||
def test():
|
||||
zip = list # Overwrite the builtin zip
|
||||
|
||||
@@ -25,6 +25,10 @@ def negative_cases():
|
||||
json3 = "{ 'positive': 'false' }"
|
||||
alternative_formatter("{a}", a=5)
|
||||
formatted = "{a}".fmt(a=7)
|
||||
partial = "partial sentence"
|
||||
a = _("formatting of {partial} in a translation string is bad practice")
|
||||
_("formatting of {partial} in a translation string is bad practice")
|
||||
print(_("formatting of {partial} in a translation string is bad practice"))
|
||||
print(do_nothing("{a}".format(a=3)))
|
||||
print(do_nothing(alternative_formatter("{a}", a=5)))
|
||||
print(format(do_nothing("{a}"), a=5))
|
||||
|
||||
@@ -116,7 +116,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_simplify::rules::use_capital_environment_variables(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
|
||||
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
|
||||
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidIndexType) {
|
||||
ruff::rules::invalid_index_type(checker, subscript);
|
||||
@@ -134,6 +134,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
})
|
||||
| Expr::List(ast::ExprList {
|
||||
elts,
|
||||
@@ -964,6 +965,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::DefaultFactoryKwarg) {
|
||||
ruff::rules::default_factory_kwarg(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
|
||||
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::Dict(dict) => {
|
||||
if checker.any_enabled(&[
|
||||
@@ -1451,6 +1455,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
generators,
|
||||
elt: _,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
|
||||
@@ -386,10 +386,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::NoClassmethodDecorator) {
|
||||
pylint::rules::no_classmethod_decorator(checker, class_def);
|
||||
pylint::rules::no_classmethod_decorator(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::NoStaticmethodDecorator) {
|
||||
pylint::rules::no_staticmethod_decorator(checker, class_def);
|
||||
pylint::rules::no_staticmethod_decorator(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::DjangoNullableModelStringField) {
|
||||
flake8_django::rules::nullable_model_string_field(checker, body);
|
||||
@@ -1299,6 +1299,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::IterationOverSet) {
|
||||
pylint::rules::iteration_over_set(checker, iter);
|
||||
}
|
||||
if checker.enabled(Rule::DictIterMissingItems) {
|
||||
pylint::rules::dict_iter_missing_items(checker, target, iter);
|
||||
}
|
||||
if checker.enabled(Rule::ManualListComprehension) {
|
||||
perflint::rules::manual_list_comprehension(checker, target, body);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use ruff_python_ast::StmtFunctionDef;
|
||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
|
||||
use crate::rules::flake8_type_checking;
|
||||
@@ -26,6 +27,8 @@ pub(super) enum AnnotationContext {
|
||||
}
|
||||
|
||||
impl AnnotationContext {
|
||||
/// Determine the [`AnnotationContext`] for an annotation based on the current scope of the
|
||||
/// semantic model.
|
||||
pub(super) fn from_model(semantic: &SemanticModel, settings: &LinterSettings) -> Self {
|
||||
// If the annotation is in a class scope (e.g., an annotated assignment for a
|
||||
// class field) or a function scope, and that class or function is marked as
|
||||
@@ -71,4 +74,23 @@ impl AnnotationContext {
|
||||
|
||||
Self::TypingOnly
|
||||
}
|
||||
|
||||
/// Determine the [`AnnotationContext`] to use for annotations in a function signature.
|
||||
pub(super) fn from_function(
|
||||
function_def: &StmtFunctionDef,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> Self {
|
||||
if flake8_type_checking::helpers::runtime_required_function(
|
||||
function_def,
|
||||
&settings.flake8_type_checking.runtime_required_decorators,
|
||||
semantic,
|
||||
) {
|
||||
Self::RuntimeRequired
|
||||
} else if semantic.future_annotations() {
|
||||
Self::TypingOnly
|
||||
} else {
|
||||
Self::RuntimeEvaluated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +374,9 @@ where
|
||||
|| helpers::is_assignment_to_a_dunder(stmt)
|
||||
|| helpers::in_nested_block(self.semantic.current_statements())
|
||||
|| imports::is_matplotlib_activation(stmt, self.semantic())
|
||||
|| imports::is_sys_path_modification(stmt, self.semantic()))
|
||||
|| imports::is_sys_path_modification(stmt, self.semantic())
|
||||
|| (self.settings.preview.is_enabled()
|
||||
&& imports::is_os_environ_modification(stmt, self.semantic())))
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
@@ -582,7 +584,8 @@ where
|
||||
|
||||
// Function annotations are always evaluated at runtime, unless future annotations
|
||||
// are enabled.
|
||||
let runtime_annotation = !self.semantic.future_annotations();
|
||||
let annotation =
|
||||
AnnotationContext::from_function(function_def, &self.semantic, self.settings);
|
||||
|
||||
// The first parameter may be a single dispatch.
|
||||
let mut singledispatch =
|
||||
@@ -606,10 +609,18 @@ where
|
||||
if let Some(expr) = ¶meter_with_default.parameter.annotation {
|
||||
if singledispatch {
|
||||
self.visit_runtime_required_annotation(expr);
|
||||
} else if runtime_annotation {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
} else {
|
||||
self.visit_annotation(expr);
|
||||
match annotation {
|
||||
AnnotationContext::RuntimeRequired => {
|
||||
self.visit_runtime_required_annotation(expr);
|
||||
}
|
||||
AnnotationContext::RuntimeEvaluated => {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
}
|
||||
AnnotationContext::TypingOnly => {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Some(expr) = ¶meter_with_default.default {
|
||||
@@ -619,28 +630,46 @@ where
|
||||
}
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
if runtime_annotation {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
} else {
|
||||
self.visit_annotation(expr);
|
||||
};
|
||||
match annotation {
|
||||
AnnotationContext::RuntimeRequired => {
|
||||
self.visit_runtime_required_annotation(expr);
|
||||
}
|
||||
AnnotationContext::RuntimeEvaluated => {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
}
|
||||
AnnotationContext::TypingOnly => {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(arg) = ¶meters.kwarg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
if runtime_annotation {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
} else {
|
||||
self.visit_annotation(expr);
|
||||
};
|
||||
match annotation {
|
||||
AnnotationContext::RuntimeRequired => {
|
||||
self.visit_runtime_required_annotation(expr);
|
||||
}
|
||||
AnnotationContext::RuntimeEvaluated => {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
}
|
||||
AnnotationContext::TypingOnly => {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for expr in returns {
|
||||
if runtime_annotation {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
} else {
|
||||
self.visit_annotation(expr);
|
||||
};
|
||||
match annotation {
|
||||
AnnotationContext::RuntimeRequired => {
|
||||
self.visit_runtime_required_annotation(expr);
|
||||
}
|
||||
AnnotationContext::RuntimeEvaluated => {
|
||||
self.visit_runtime_evaluated_annotation(expr);
|
||||
}
|
||||
AnnotationContext::TypingOnly => {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
@@ -978,6 +1007,7 @@ where
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
self.visit_generators(generators);
|
||||
self.visit_expr(elt);
|
||||
@@ -1298,6 +1328,7 @@ where
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
let mut iter = elts.iter();
|
||||
|
||||
@@ -244,6 +244,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "E0643") => (RuleGroup::Preview, rules::pylint::rules::PotentialIndexError),
|
||||
(Pylint, "E0704") => (RuleGroup::Preview, rules::pylint::rules::MisplacedBareRaise),
|
||||
(Pylint, "E1132") => (RuleGroup::Preview, rules::pylint::rules::RepeatedKeywordArgument),
|
||||
(Pylint, "E1141") => (RuleGroup::Preview, rules::pylint::rules::DictIterMissingItems),
|
||||
(Pylint, "E1142") => (RuleGroup::Stable, rules::pylint::rules::AwaitOutsideAsync),
|
||||
(Pylint, "E1205") => (RuleGroup::Stable, rules::pylint::rules::LoggingTooManyArgs),
|
||||
(Pylint, "E1206") => (RuleGroup::Stable, rules::pylint::rules::LoggingTooFewArgs),
|
||||
|
||||
@@ -26,7 +26,7 @@ static HASH_NUMBER: Lazy<Regex> = Lazy::new(|| Regex::new(r"#\d").unwrap());
|
||||
static POSITIVE_CASES: Lazy<RegexSet> = Lazy::new(|| {
|
||||
RegexSet::new([
|
||||
// Keywords
|
||||
r"^(?:elif\s+.*\s*:|else\s*:|try\s*:|finally\s*:|except\s+.*\s*:)$",
|
||||
r"^(?:elif\s+.*\s*:.*|else\s*:.*|try\s*:.*|finally\s*:.*|except.*:.*|case\s+.*\s*:.*)$",
|
||||
// Partial dictionary
|
||||
r#"^['"]\w+['"]\s*:.+[,{]\s*(#.*)?$"#,
|
||||
// Multiline assignment
|
||||
@@ -147,6 +147,27 @@ mod tests {
|
||||
assert!(!comment_contains_code("#to print", &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_contains_code_single_line() {
|
||||
assert!(comment_contains_code("# case 1: print()", &[]));
|
||||
assert!(comment_contains_code("# try: get(1, 2, 3)", &[]));
|
||||
assert!(comment_contains_code("# else: print()", &[]));
|
||||
assert!(comment_contains_code("# elif x == 10: print()", &[]));
|
||||
assert!(comment_contains_code(
|
||||
"# except Exception as e: print(e)",
|
||||
&[]
|
||||
));
|
||||
assert!(comment_contains_code("# except: print()", &[]));
|
||||
assert!(comment_contains_code("# finally: close_handle()", &[]));
|
||||
|
||||
assert!(!comment_contains_code("# try: use cache", &[]));
|
||||
assert!(!comment_contains_code("# else: we should return", &[]));
|
||||
assert!(!comment_contains_code(
|
||||
"# call function except: without cache",
|
||||
&[]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_contains_code_with_multiline() {
|
||||
assert!(comment_contains_code("#else:", &[]));
|
||||
@@ -155,11 +176,15 @@ mod tests {
|
||||
assert!(comment_contains_code("#elif True:", &[]));
|
||||
assert!(comment_contains_code("#x = foo(", &[]));
|
||||
assert!(comment_contains_code("#except Exception:", &[]));
|
||||
assert!(comment_contains_code("# case 1:", &[]));
|
||||
assert!(comment_contains_code("#case 1:", &[]));
|
||||
assert!(comment_contains_code("# try:", &[]));
|
||||
|
||||
assert!(!comment_contains_code("# this is = to that :(", &[]));
|
||||
assert!(!comment_contains_code("#else", &[]));
|
||||
assert!(!comment_contains_code("#or else:", &[]));
|
||||
assert!(!comment_contains_code("#else True:", &[]));
|
||||
assert!(!comment_contains_code("# in that case:", &[]));
|
||||
|
||||
// Unpacking assignments
|
||||
assert!(comment_contains_code(
|
||||
|
||||
@@ -47,7 +47,8 @@ fn is_standalone_comment(line: &str) -> bool {
|
||||
for char in line.chars() {
|
||||
if char == '#' {
|
||||
return true;
|
||||
} else if !char.is_whitespace() {
|
||||
}
|
||||
if !char.is_whitespace() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,4 +148,132 @@ ERA001.py:27:5: ERA001 Found commented-out code
|
||||
29 28 |
|
||||
30 29 | #import os # noqa
|
||||
|
||||
ERA001.py:32:1: ERA001 Found commented-out code
|
||||
|
|
||||
30 | #import os # noqa
|
||||
31 |
|
||||
32 | # case 1:
|
||||
| ^^^^^^^^^ ERA001
|
||||
33 | # try:
|
||||
34 | # try: # with comment
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
29 29 |
|
||||
30 30 | #import os # noqa
|
||||
31 31 |
|
||||
32 |-# case 1:
|
||||
33 32 | # try:
|
||||
34 33 | # try: # with comment
|
||||
35 34 | # try: print()
|
||||
|
||||
ERA001.py:33:1: ERA001 Found commented-out code
|
||||
|
|
||||
32 | # case 1:
|
||||
33 | # try:
|
||||
| ^^^^^^ ERA001
|
||||
34 | # try: # with comment
|
||||
35 | # try: print()
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
30 30 | #import os # noqa
|
||||
31 31 |
|
||||
32 32 | # case 1:
|
||||
33 |-# try:
|
||||
34 33 | # try: # with comment
|
||||
35 34 | # try: print()
|
||||
36 35 | # except:
|
||||
|
||||
ERA001.py:34:1: ERA001 Found commented-out code
|
||||
|
|
||||
32 | # case 1:
|
||||
33 | # try:
|
||||
34 | # try: # with comment
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ ERA001
|
||||
35 | # try: print()
|
||||
36 | # except:
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
31 31 |
|
||||
32 32 | # case 1:
|
||||
33 33 | # try:
|
||||
34 |-# try: # with comment
|
||||
35 34 | # try: print()
|
||||
36 35 | # except:
|
||||
37 36 | # except Foo:
|
||||
|
||||
ERA001.py:35:1: ERA001 Found commented-out code
|
||||
|
|
||||
33 | # try:
|
||||
34 | # try: # with comment
|
||||
35 | # try: print()
|
||||
| ^^^^^^^^^^^^^^ ERA001
|
||||
36 | # except:
|
||||
37 | # except Foo:
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
32 32 | # case 1:
|
||||
33 33 | # try:
|
||||
34 34 | # try: # with comment
|
||||
35 |-# try: print()
|
||||
36 35 | # except:
|
||||
37 36 | # except Foo:
|
||||
38 37 | # except Exception as e: print(e)
|
||||
|
||||
ERA001.py:36:1: ERA001 Found commented-out code
|
||||
|
|
||||
34 | # try: # with comment
|
||||
35 | # try: print()
|
||||
36 | # except:
|
||||
| ^^^^^^^^^ ERA001
|
||||
37 | # except Foo:
|
||||
38 | # except Exception as e: print(e)
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
33 33 | # try:
|
||||
34 34 | # try: # with comment
|
||||
35 35 | # try: print()
|
||||
36 |-# except:
|
||||
37 36 | # except Foo:
|
||||
38 37 | # except Exception as e: print(e)
|
||||
|
||||
ERA001.py:37:1: ERA001 Found commented-out code
|
||||
|
|
||||
35 | # try: print()
|
||||
36 | # except:
|
||||
37 | # except Foo:
|
||||
| ^^^^^^^^^^^^^ ERA001
|
||||
38 | # except Exception as e: print(e)
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
34 34 | # try: # with comment
|
||||
35 35 | # try: print()
|
||||
36 36 | # except:
|
||||
37 |-# except Foo:
|
||||
38 37 | # except Exception as e: print(e)
|
||||
|
||||
ERA001.py:38:1: ERA001 Found commented-out code
|
||||
|
|
||||
36 | # except:
|
||||
37 | # except Foo:
|
||||
38 | # except Exception as e: print(e)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ERA001
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Display-only fix
|
||||
35 35 | # try: print()
|
||||
36 36 | # except:
|
||||
37 37 | # except Foo:
|
||||
38 |-# except Exception as e: print(e)
|
||||
|
||||
@@ -45,7 +45,7 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
|
||||
/// Returns `true` if a function definition is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||
matches!(name, "__setitem__")
|
||||
matches!(name, "__setitem__" | "__post_init__")
|
||||
}
|
||||
|
||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||
|
||||
@@ -109,6 +109,7 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
elts: elts.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ enum TokenType {
|
||||
/// Simplified token specialized for the task.
|
||||
#[derive(Copy, Clone)]
|
||||
struct Token {
|
||||
r#type: TokenType,
|
||||
ty: TokenType,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ impl Ranged for Token {
|
||||
}
|
||||
|
||||
impl Token {
|
||||
fn new(r#type: TokenType, range: TextRange) -> Self {
|
||||
Self { r#type, range }
|
||||
fn new(ty: TokenType, range: TextRange) -> Self {
|
||||
Self { ty, range }
|
||||
}
|
||||
|
||||
fn irrelevant() -> Token {
|
||||
Token {
|
||||
r#type: TokenType::Irrelevant,
|
||||
ty: TokenType::Irrelevant,
|
||||
range: TextRange::default(),
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ impl Token {
|
||||
|
||||
impl From<(&Tok, TextRange)> for Token {
|
||||
fn from((tok, range): (&Tok, TextRange)) -> Self {
|
||||
let r#type = match tok {
|
||||
let ty = match tok {
|
||||
Tok::Name { .. } => TokenType::Named,
|
||||
Tok::String { .. } => TokenType::String,
|
||||
Tok::Newline => TokenType::Newline,
|
||||
@@ -75,7 +75,7 @@ impl From<(&Tok, TextRange)> for Token {
|
||||
_ => TokenType::Irrelevant,
|
||||
};
|
||||
#[allow(clippy::inconsistent_struct_constructor)]
|
||||
Self { range, r#type }
|
||||
Self { range, ty }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,16 +102,13 @@ enum ContextType {
|
||||
/// Comma context - described a comma-delimited "situation".
|
||||
#[derive(Copy, Clone)]
|
||||
struct Context {
|
||||
r#type: ContextType,
|
||||
ty: ContextType,
|
||||
num_commas: u32,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
const fn new(r#type: ContextType) -> Self {
|
||||
Self {
|
||||
r#type,
|
||||
num_commas: 0,
|
||||
}
|
||||
const fn new(ty: ContextType) -> Self {
|
||||
Self { ty, num_commas: 0 }
|
||||
}
|
||||
|
||||
fn inc(&mut self) {
|
||||
@@ -277,9 +274,7 @@ pub(crate) fn trailing_commas(
|
||||
let mut stack = vec![Context::new(ContextType::No)];
|
||||
|
||||
for token in tokens {
|
||||
if prev.r#type == TokenType::NonLogicalNewline
|
||||
&& token.r#type == TokenType::NonLogicalNewline
|
||||
{
|
||||
if prev.ty == TokenType::NonLogicalNewline && token.ty == TokenType::NonLogicalNewline {
|
||||
// Collapse consecutive newlines to the first one -- trailing commas are
|
||||
// added before the first newline.
|
||||
continue;
|
||||
@@ -288,87 +283,18 @@ pub(crate) fn trailing_commas(
|
||||
// Update the comma context stack.
|
||||
let context = update_context(token, prev, prev_prev, &mut stack);
|
||||
|
||||
// Is it allowed to have a trailing comma before this token?
|
||||
let comma_allowed = token.r#type == TokenType::ClosingBracket
|
||||
&& match context.r#type {
|
||||
ContextType::No => false,
|
||||
ContextType::FunctionParameters => true,
|
||||
ContextType::CallArguments => true,
|
||||
// `(1)` is not equivalent to `(1,)`.
|
||||
ContextType::Tuple => context.num_commas != 0,
|
||||
// `x[1]` is not equivalent to `x[1,]`.
|
||||
ContextType::Subscript => context.num_commas != 0,
|
||||
ContextType::List => true,
|
||||
ContextType::Dict => true,
|
||||
// Lambdas are required to be a single line, trailing comma never makes sense.
|
||||
ContextType::LambdaParameters => false,
|
||||
};
|
||||
|
||||
// Is prev a prohibited trailing comma?
|
||||
let comma_prohibited = prev.r#type == TokenType::Comma && {
|
||||
// Is `(1,)` or `x[1,]`?
|
||||
let is_singleton_tuplish =
|
||||
matches!(context.r#type, ContextType::Subscript | ContextType::Tuple)
|
||||
&& context.num_commas <= 1;
|
||||
// There was no non-logical newline, so prohibit (except in `(1,)` or `x[1,]`).
|
||||
if comma_allowed && !is_singleton_tuplish {
|
||||
true
|
||||
// Lambdas not handled by comma_allowed so handle it specially.
|
||||
} else {
|
||||
context.r#type == ContextType::LambdaParameters && token.r#type == TokenType::Colon
|
||||
}
|
||||
};
|
||||
if comma_prohibited {
|
||||
let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, prev.range());
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
// Is prev a prohibited trailing comma on a bare tuple?
|
||||
// Approximation: any comma followed by a statement-ending newline.
|
||||
let bare_comma_prohibited =
|
||||
prev.r#type == TokenType::Comma && token.r#type == TokenType::Newline;
|
||||
if bare_comma_prohibited {
|
||||
diagnostics.push(Diagnostic::new(TrailingCommaOnBareTuple, prev.range()));
|
||||
}
|
||||
|
||||
// Comma is required if:
|
||||
// - It is allowed,
|
||||
// - Followed by a newline,
|
||||
// - Not already present,
|
||||
// - Not on an empty (), {}, [].
|
||||
let comma_required = comma_allowed
|
||||
&& prev.r#type == TokenType::NonLogicalNewline
|
||||
&& !matches!(
|
||||
prev_prev.r#type,
|
||||
TokenType::Comma
|
||||
| TokenType::OpeningBracket
|
||||
| TokenType::OpeningSquareBracket
|
||||
| TokenType::OpeningCurlyBracket
|
||||
);
|
||||
if comma_required {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end()));
|
||||
// Create a replacement that includes the final bracket (or other token),
|
||||
// rather than just inserting a comma at the end. This prevents the UP034 fix
|
||||
// removing any brackets in the same linter pass - doing both at the same time could
|
||||
// lead to a syntax error.
|
||||
let contents = locator.slice(prev_prev.range());
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{contents},"),
|
||||
prev_prev.range(),
|
||||
)));
|
||||
if let Some(diagnostic) = check_token(token, prev, prev_prev, context, locator) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
// Pop the current context if the current token ended it.
|
||||
// The top context is never popped (if unbalanced closing brackets).
|
||||
let pop_context = match context.r#type {
|
||||
let pop_context = match context.ty {
|
||||
// Lambda terminated by `:`.
|
||||
ContextType::LambdaParameters => token.r#type == TokenType::Colon,
|
||||
ContextType::LambdaParameters => token.ty == TokenType::Colon,
|
||||
// All others terminated by a closing bracket.
|
||||
// flake8-commas doesn't verify that it matches the opening...
|
||||
_ => token.r#type == TokenType::ClosingBracket,
|
||||
_ => token.ty == TokenType::ClosingBracket,
|
||||
};
|
||||
if pop_context && stack.len() > 1 {
|
||||
stack.pop();
|
||||
@@ -379,21 +305,107 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
}
|
||||
|
||||
fn check_token(
|
||||
token: Token,
|
||||
prev: Token,
|
||||
prev_prev: Token,
|
||||
context: Context,
|
||||
locator: &Locator,
|
||||
) -> Option<Diagnostic> {
|
||||
// Is it allowed to have a trailing comma before this token?
|
||||
let comma_allowed = token.ty == TokenType::ClosingBracket
|
||||
&& match context.ty {
|
||||
ContextType::No => false,
|
||||
ContextType::FunctionParameters => true,
|
||||
ContextType::CallArguments => true,
|
||||
// `(1)` is not equivalent to `(1,)`.
|
||||
ContextType::Tuple => context.num_commas != 0,
|
||||
// `x[1]` is not equivalent to `x[1,]`.
|
||||
ContextType::Subscript => context.num_commas != 0,
|
||||
ContextType::List => true,
|
||||
ContextType::Dict => true,
|
||||
// Lambdas are required to be a single line, trailing comma never makes sense.
|
||||
ContextType::LambdaParameters => false,
|
||||
};
|
||||
|
||||
// Is prev a prohibited trailing comma?
|
||||
let comma_prohibited = prev.ty == TokenType::Comma && {
|
||||
// Is `(1,)` or `x[1,]`?
|
||||
let is_singleton_tuplish =
|
||||
matches!(context.ty, ContextType::Subscript | ContextType::Tuple)
|
||||
&& context.num_commas <= 1;
|
||||
// There was no non-logical newline, so prohibit (except in `(1,)` or `x[1,]`).
|
||||
if comma_allowed && !is_singleton_tuplish {
|
||||
true
|
||||
// Lambdas not handled by comma_allowed so handle it specially.
|
||||
} else {
|
||||
context.ty == ContextType::LambdaParameters && token.ty == TokenType::Colon
|
||||
}
|
||||
};
|
||||
|
||||
if comma_prohibited {
|
||||
let mut diagnostic = Diagnostic::new(ProhibitedTrailingComma, prev.range());
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(diagnostic.range())));
|
||||
return Some(diagnostic);
|
||||
}
|
||||
|
||||
// Is prev a prohibited trailing comma on a bare tuple?
|
||||
// Approximation: any comma followed by a statement-ending newline.
|
||||
let bare_comma_prohibited = prev.ty == TokenType::Comma && token.ty == TokenType::Newline;
|
||||
if bare_comma_prohibited {
|
||||
return Some(Diagnostic::new(TrailingCommaOnBareTuple, prev.range()));
|
||||
}
|
||||
|
||||
if !comma_allowed {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Comma is required if:
|
||||
// - It is allowed,
|
||||
// - Followed by a newline,
|
||||
// - Not already present,
|
||||
// - Not on an empty (), {}, [].
|
||||
let comma_required = prev.ty == TokenType::NonLogicalNewline
|
||||
&& !matches!(
|
||||
prev_prev.ty,
|
||||
TokenType::Comma
|
||||
| TokenType::OpeningBracket
|
||||
| TokenType::OpeningSquareBracket
|
||||
| TokenType::OpeningCurlyBracket
|
||||
);
|
||||
if comma_required {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(MissingTrailingComma, TextRange::empty(prev_prev.end()));
|
||||
// Create a replacement that includes the final bracket (or other token),
|
||||
// rather than just inserting a comma at the end. This prevents the UP034 fix
|
||||
// removing any brackets in the same linter pass - doing both at the same time could
|
||||
// lead to a syntax error.
|
||||
let contents = locator.slice(prev_prev.range());
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{contents},"),
|
||||
prev_prev.range(),
|
||||
)));
|
||||
Some(diagnostic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_context(
|
||||
token: Token,
|
||||
prev: Token,
|
||||
prev_prev: Token,
|
||||
stack: &mut Vec<Context>,
|
||||
) -> Context {
|
||||
let new_context = match token.r#type {
|
||||
TokenType::OpeningBracket => match (prev.r#type, prev_prev.r#type) {
|
||||
let new_context = match token.ty {
|
||||
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
|
||||
(TokenType::Named, TokenType::Def) => Context::new(ContextType::FunctionParameters),
|
||||
(TokenType::Named | TokenType::ClosingBracket, _) => {
|
||||
Context::new(ContextType::CallArguments)
|
||||
}
|
||||
_ => Context::new(ContextType::Tuple),
|
||||
},
|
||||
TokenType::OpeningSquareBracket => match prev.r#type {
|
||||
TokenType::OpeningSquareBracket => match prev.ty {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,20 @@ import os
|
||||
r"
|
||||
# Copyright (C) 2023
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
&settings::LinterSettings::for_rules(vec![Rule::MissingCopyrightNotice]),
|
||||
);
|
||||
assert_messages!(diagnostics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notice_with_unicode_c() {
|
||||
let diagnostics = test_snippet(
|
||||
r"
|
||||
# Copyright © 2023
|
||||
|
||||
import os
|
||||
"
|
||||
.trim(),
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct Settings {
|
||||
}
|
||||
|
||||
pub static COPYRIGHT: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"(?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*").unwrap());
|
||||
Lazy::new(|| Regex::new(r"(?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}(-\d{4})*").unwrap());
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
|
||||
---
|
||||
|
||||
@@ -173,6 +173,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
});
|
||||
let node1 = Expr::Name(ast::ExprName {
|
||||
id: arg_name.into(),
|
||||
|
||||
@@ -72,6 +72,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
|
||||
elts,
|
||||
range: _,
|
||||
ctx: _,
|
||||
parenthesized: _,
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
for expr in elts {
|
||||
@@ -123,6 +124,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
|
||||
elts: literal_exprs.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
@@ -148,6 +150,7 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
|
||||
elts,
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
|
||||
@@ -130,6 +130,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
@@ -151,6 +152,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
|
||||
elts: exprs.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -337,6 +337,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
format!("({})", checker.generator().expr(&node)),
|
||||
@@ -444,6 +445,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
});
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
format!("({})", checker.generator().expr(&node)),
|
||||
|
||||
@@ -428,6 +428,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &mut Checker, expr: &Expr) {
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
};
|
||||
let node1 = ast::ExprName {
|
||||
id: "isinstance".into(),
|
||||
@@ -543,6 +544,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) {
|
||||
elts: comparators.into_iter().map(Clone::clone).collect(),
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
};
|
||||
let node1 = ast::ExprName {
|
||||
id: id.into(),
|
||||
@@ -718,7 +720,7 @@ fn get_short_circuit_edit(
|
||||
generator.expr(expr)
|
||||
};
|
||||
Edit::range_replacement(
|
||||
if matches!(expr, Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _}) if !elts.is_empty())
|
||||
if matches!(expr, Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _, parenthesized: _}) if !elts.is_empty())
|
||||
{
|
||||
format!("({content})")
|
||||
} else {
|
||||
|
||||
@@ -382,6 +382,7 @@ fn return_stmt(id: &str, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
|
||||
range: TextRange::default(),
|
||||
}],
|
||||
range: TextRange::default(),
|
||||
parenthesized: false,
|
||||
};
|
||||
let node1 = ast::ExprName {
|
||||
id: id.into(),
|
||||
|
||||
@@ -30,31 +30,4 @@ runtime_evaluated_decorators_3.py:6:18: TCH003 [*] Move standard library import
|
||||
13 16 |
|
||||
14 17 | @attrs.define(auto_attribs=True)
|
||||
|
||||
runtime_evaluated_decorators_3.py:7:29: TCH003 [*] Move standard library import `collections.abc.Sequence` into a type-checking block
|
||||
|
|
||||
5 | from dataclasses import dataclass
|
||||
6 | from uuid import UUID # TCH003
|
||||
7 | from collections.abc import Sequence
|
||||
| ^^^^^^^^ TCH003
|
||||
8 | from pydantic import validate_call
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
4 4 | from array import array
|
||||
5 5 | from dataclasses import dataclass
|
||||
6 6 | from uuid import UUID # TCH003
|
||||
7 |-from collections.abc import Sequence
|
||||
8 7 | from pydantic import validate_call
|
||||
9 8 |
|
||||
10 9 | import attrs
|
||||
11 10 | from attrs import frozen
|
||||
11 |+from typing import TYPE_CHECKING
|
||||
12 |+
|
||||
13 |+if TYPE_CHECKING:
|
||||
14 |+ from collections.abc import Sequence
|
||||
12 15 |
|
||||
13 16 |
|
||||
14 17 | @attrs.define(auto_attribs=True)
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ mod tests {
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E40.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_0.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_1.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_2.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.ipynb"))]
|
||||
#[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))]
|
||||
#[test_case(Rule::MultipleStatementsOnOneLineColon, Path::new("E70.py"))]
|
||||
@@ -69,6 +70,7 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::IsLiteral, Path::new("constant_literals.py"))]
|
||||
#[test_case(Rule::TypeComparison, Path::new("E721.py"))]
|
||||
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_2.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -213,11 +213,11 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &mut LogicalLin
|
||||
diagnostic.range(),
|
||||
)));
|
||||
context.push_diagnostic(diagnostic);
|
||||
} else if iter
|
||||
.peek()
|
||||
.is_some_and(|token| token.kind() == TokenKind::Rsqb)
|
||||
{
|
||||
} else if iter.peek().is_some_and(|token| {
|
||||
matches!(token.kind(), TokenKind::Rsqb | TokenKind::Comma)
|
||||
}) {
|
||||
// Allow `foo[1 :]`, but not `foo[1 :]`.
|
||||
// Or `foo[index :, 2]`, but not `foo[index :, 2]`.
|
||||
if let (Whitespace::Many | Whitespace::Tab, offset) = whitespace
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
|
||||
@@ -17,6 +17,9 @@ use crate::checkers::ast::Checker;
|
||||
/// `sys.path.insert`, `sys.path.append`, and similar modifications between import
|
||||
/// statements.
|
||||
///
|
||||
/// In [preview], this rule also allows `os.environ` modifications between import
|
||||
/// statements.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// "One string"
|
||||
@@ -37,6 +40,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#imports
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[violation]
|
||||
pub struct ModuleImportNotAtTopOfFile {
|
||||
source_type: PySourceType,
|
||||
|
||||
@@ -86,30 +86,33 @@ pub(crate) fn trailing_whitespace(
|
||||
.sum();
|
||||
if whitespace_len > TextSize::from(0) {
|
||||
let range = TextRange::new(line.end() - whitespace_len, line.end());
|
||||
|
||||
// Removing trailing whitespace is not safe inside multiline strings.
|
||||
let applicability = if indexer.multiline_ranges().contains_range(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
if range == line.range() {
|
||||
if settings.rules.enabled(Rule::BlankLineWithWhitespace) {
|
||||
let mut diagnostic = Diagnostic::new(BlankLineWithWhitespace, range);
|
||||
// Remove any preceding continuations, to avoid introducing a potential
|
||||
// syntax error.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
indexer
|
||||
.preceded_by_continuations(line.start(), locator)
|
||||
.unwrap_or(range.start()),
|
||||
range.end(),
|
||||
))));
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::range_deletion(TextRange::new(
|
||||
indexer
|
||||
.preceded_by_continuations(line.start(), locator)
|
||||
.unwrap_or(range.start()),
|
||||
range.end(),
|
||||
)),
|
||||
applicability,
|
||||
));
|
||||
return Some(diagnostic);
|
||||
}
|
||||
} else if settings.rules.enabled(Rule::TrailingWhitespace) {
|
||||
let mut diagnostic = Diagnostic::new(TrailingWhitespace, range);
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::range_deletion(range),
|
||||
// Removing trailing whitespace is not safe inside multiline strings.
|
||||
if indexer.multiline_ranges().contains_range(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
applicability,
|
||||
));
|
||||
return Some(diagnostic);
|
||||
}
|
||||
|
||||
@@ -231,6 +231,8 @@ E20.py:149:10: E203 [*] Whitespace before ':'
|
||||
148 | #: E203:1:10
|
||||
149 | ham[upper :]
|
||||
| ^^ E203
|
||||
150 |
|
||||
151 | #: Okay
|
||||
|
|
||||
= help: Remove whitespace before ':'
|
||||
|
||||
@@ -240,5 +242,21 @@ E20.py:149:10: E203 [*] Whitespace before ':'
|
||||
148 148 | #: E203:1:10
|
||||
149 |-ham[upper :]
|
||||
149 |+ham[upper:]
|
||||
150 150 |
|
||||
151 151 | #: Okay
|
||||
152 152 | ham[lower +1 :, "columnname"]
|
||||
|
||||
E20.py:155:14: E203 [*] Whitespace before ':'
|
||||
|
|
||||
154 | #: E203:1:13
|
||||
155 | ham[lower + 1 :, "columnname"]
|
||||
| ^^ E203
|
||||
|
|
||||
= help: Remove whitespace before ':'
|
||||
|
||||
ℹ Safe fix
|
||||
152 152 | ham[lower +1 :, "columnname"]
|
||||
153 153 |
|
||||
154 154 | #: E203:1:13
|
||||
155 |-ham[lower + 1 :, "columnname"]
|
||||
155 |+ham[lower + 1:, "columnname"]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E402_2.py:7:1: E402 Module level import not at top of file
|
||||
|
|
||||
5 | del os.environ["WORLD_SIZE"]
|
||||
6 |
|
||||
7 | import torch
|
||||
| ^^^^^^^^^^^^ E402
|
||||
|
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ W293.py:4:1: W293 [*] Blank line contains whitespace
|
||||
|
|
||||
= help: Remove whitespace from blank line
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
1 1 | # See: https://github.com/astral-sh/ruff/issues/9323
|
||||
2 2 | class Chassis(RobotModuleTemplate):
|
||||
3 3 | """底盘信息推送控制
|
||||
@@ -48,6 +48,7 @@ W293.py:16:1: W293 [*] Blank line contains whitespace
|
||||
15 | \
|
||||
16 |
|
||||
| ^^^^ W293
|
||||
17 | '''blank line with whitespace
|
||||
|
|
||||
= help: Remove whitespace from blank line
|
||||
|
||||
@@ -59,5 +60,25 @@ W293.py:16:1: W293 [*] Blank line contains whitespace
|
||||
15 |- \
|
||||
16 |-
|
||||
14 |+ "
|
||||
17 15 | '''blank line with whitespace
|
||||
18 16 |
|
||||
19 17 | inside a multiline string'''
|
||||
|
||||
W293.py:18:1: W293 [*] Blank line contains whitespace
|
||||
|
|
||||
17 | '''blank line with whitespace
|
||||
18 |
|
||||
| ^ W293
|
||||
19 | inside a multiline string'''
|
||||
|
|
||||
= help: Remove whitespace from blank line
|
||||
|
||||
ℹ Unsafe fix
|
||||
15 15 | \
|
||||
16 16 |
|
||||
17 17 | '''blank line with whitespace
|
||||
18 |-
|
||||
18 |+
|
||||
19 19 | inside a multiline string'''
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
|
||||
@@ -171,6 +171,7 @@ mod tests {
|
||||
#[test_case(Rule::PotentialIndexError, Path::new("potential_index_error.py"))]
|
||||
#[test_case(Rule::SuperWithoutBrackets, Path::new("super_without_brackets.py"))]
|
||||
#[test_case(Rule::TooManyNestedBlocks, Path::new("too_many_nested_blocks.py"))]
|
||||
#[test_case(Rule::DictIterMissingItems, Path::new("dict_iter_missing_items.py"))]
|
||||
#[test_case(
|
||||
Rule::UnnecessaryDictIndexLookup,
|
||||
Path::new("unnecessary_dict_index_lookup.py")
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
use ruff_python_ast::{Expr, ExprTuple};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::analyze::typing::is_dict;
|
||||
use ruff_python_semantic::{Binding, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for dictionary unpacking in a for loop without calling `.items()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When iterating over a dictionary in a for loop, if a dictionary is unpacked
|
||||
/// without calling `.items()`, it could lead to a runtime error if the keys are not
|
||||
/// a tuple of two elements.
|
||||
///
|
||||
/// It is likely that you're looking for an iteration over (key, value) pairs which
|
||||
/// can only be achieved when calling `.items()`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// data = {"Paris": 2_165_423, "New York City": 8_804_190, "Tokyo": 13_988_129}
|
||||
///
|
||||
/// for city, population in data:
|
||||
/// print(f"{city} has population {population}.")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// data = {"Paris": 2_165_423, "New York City": 8_804_190, "Tokyo": 13_988_129}
|
||||
///
|
||||
/// for city, population in data.items():
|
||||
/// print(f"{city} has population {population}.")
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct DictIterMissingItems;
|
||||
|
||||
impl AlwaysFixableViolation for DictIterMissingItems {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unpacking a dictionary in iteration without calling `.items()`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Add a call to `.items()`")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dict_iter_missing_items(checker: &mut Checker, target: &Expr, iter: &Expr) {
|
||||
let Expr::Tuple(ExprTuple { elts, .. }) = target else {
|
||||
return;
|
||||
};
|
||||
|
||||
if elts.len() != 2 {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(name) = iter.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.only_binding(name)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !is_dict(binding, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we can reliably determine that a dictionary has keys that are tuples of two we don't warn
|
||||
if is_dict_key_tuple_with_two_elements(checker.semantic(), binding) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(DictIterMissingItems, iter.range());
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{}.items()", name.id),
|
||||
iter.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// Returns true if the binding is a dictionary where each key is a tuple with two elements.
|
||||
fn is_dict_key_tuple_with_two_elements(semantic: &SemanticModel, binding: &Binding) -> bool {
|
||||
let Some(statement) = binding.statement(semantic) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(assign_stmt) = statement.as_assign_stmt() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(dict_expr) = assign_stmt.value.as_dict_expr() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
dict_expr.keys.iter().all(|elt| {
|
||||
elt.as_ref().is_some_and(|x| {
|
||||
if let Some(tuple) = x.as_tuple_expr() {
|
||||
return tuple.elts.len() == 2;
|
||||
}
|
||||
false
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -13,6 +13,7 @@ pub(crate) use compare_to_empty_string::*;
|
||||
pub(crate) use comparison_of_constant::*;
|
||||
pub(crate) use comparison_with_itself::*;
|
||||
pub(crate) use continue_in_finally::*;
|
||||
pub(crate) use dict_iter_missing_items::*;
|
||||
pub(crate) use duplicate_bases::*;
|
||||
pub(crate) use empty_comment::*;
|
||||
pub(crate) use eq_without_hash::*;
|
||||
@@ -98,6 +99,7 @@ mod compare_to_empty_string;
|
||||
mod comparison_of_constant;
|
||||
mod comparison_with_itself;
|
||||
mod continue_in_finally;
|
||||
mod dict_iter_missing_items;
|
||||
mod duplicate_bases;
|
||||
mod empty_comment;
|
||||
mod eq_without_hash;
|
||||
|
||||
@@ -4,9 +4,10 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, DiagnosticKind, Edit,
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_trivia::indentation_at_offset;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of a classmethod being made without the decorator.
|
||||
@@ -86,21 +87,21 @@ enum MethodType {
|
||||
}
|
||||
|
||||
/// PLR0202
|
||||
pub(crate) fn no_classmethod_decorator(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
||||
get_undecorated_methods(checker, class_def, &MethodType::Classmethod);
|
||||
pub(crate) fn no_classmethod_decorator(checker: &mut Checker, stmt: &Stmt) {
|
||||
get_undecorated_methods(checker, stmt, &MethodType::Classmethod);
|
||||
}
|
||||
|
||||
/// PLR0203
|
||||
pub(crate) fn no_staticmethod_decorator(checker: &mut Checker, class_def: &ast::StmtClassDef) {
|
||||
get_undecorated_methods(checker, class_def, &MethodType::Staticmethod);
|
||||
pub(crate) fn no_staticmethod_decorator(checker: &mut Checker, stmt: &Stmt) {
|
||||
get_undecorated_methods(checker, stmt, &MethodType::Staticmethod);
|
||||
}
|
||||
|
||||
fn get_undecorated_methods(
|
||||
checker: &mut Checker,
|
||||
class_def: &ast::StmtClassDef,
|
||||
method_type: &MethodType,
|
||||
) {
|
||||
let mut explicit_decorator_calls: HashMap<String, TextRange> = HashMap::default();
|
||||
fn get_undecorated_methods(checker: &mut Checker, class_stmt: &Stmt, method_type: &MethodType) {
|
||||
let Stmt::ClassDef(class_def) = class_stmt else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut explicit_decorator_calls: HashMap<String, &Stmt> = HashMap::default();
|
||||
|
||||
let (method_name, diagnostic_type): (&str, DiagnosticKind) = match method_type {
|
||||
MethodType::Classmethod => ("classmethod", NoClassmethodDecorator.into()),
|
||||
@@ -131,7 +132,7 @@ fn get_undecorated_methods(
|
||||
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = &arguments.args[0] {
|
||||
if target_name == *id {
|
||||
explicit_decorator_calls.insert(id.clone(), stmt.range());
|
||||
explicit_decorator_calls.insert(id.clone(), stmt);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -151,7 +152,7 @@ fn get_undecorated_methods(
|
||||
..
|
||||
}) = stmt
|
||||
{
|
||||
if !explicit_decorator_calls.contains_key(name.as_str()) {
|
||||
let Some(decorator_call_statement) = explicit_decorator_calls.get(name.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -177,18 +178,16 @@ fn get_undecorated_methods(
|
||||
|
||||
match indentation {
|
||||
Some(indentation) => {
|
||||
let range = &explicit_decorator_calls[name.as_str()];
|
||||
|
||||
// SAFETY: Ruff only supports formatting files <= 4GB
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
diagnostic.set_fix(Fix::safe_edits(
|
||||
Edit::insertion(
|
||||
format!("@{method_name}\n{indentation}"),
|
||||
stmt.range().start(),
|
||||
),
|
||||
[Edit::deletion(
|
||||
range.start() - TextSize::from(indentation.len() as u32),
|
||||
range.end(),
|
||||
[fix::edits::delete_stmt(
|
||||
decorator_call_statement,
|
||||
Some(class_stmt),
|
||||
checker.locator(),
|
||||
checker.indexer(),
|
||||
)],
|
||||
));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -305,6 +305,7 @@ fn assignment_targets_from_expr<'a>(
|
||||
ctx: ExprContext::Store,
|
||||
elts,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => Box::new(
|
||||
elts.iter()
|
||||
.flat_map(|elt| assignment_targets_from_expr(elt, dummy_variable_rgx)),
|
||||
|
||||
@@ -9,7 +9,9 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::hashable::HashableExpr;
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -74,7 +76,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
if bool_op
|
||||
.values
|
||||
.iter()
|
||||
.any(|value| !is_allowed_value(bool_op.op, value))
|
||||
.any(|value| !is_allowed_value(bool_op.op, value, checker.semantic()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -143,6 +145,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
elts: comparators.iter().copied().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})]),
|
||||
range: bool_op.range(),
|
||||
})),
|
||||
@@ -157,7 +160,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
/// Return `true` if the given expression is compatible with a membership test.
|
||||
/// E.g., `==` operators can be joined with `or` and `!=` operators can be
|
||||
/// joined with `and`.
|
||||
fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
|
||||
fn is_allowed_value(bool_op: BoolOp, value: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Compare(ast::ExprCompare {
|
||||
left,
|
||||
ops,
|
||||
@@ -196,6 +199,16 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore `sys.version_info` and `sys.platform` comparisons, which are only
|
||||
// respected by type checkers when enforced via equality.
|
||||
if any_over_expr(value, &|expr| {
|
||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["sys", "version_info" | "platform"])
|
||||
})
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
dict_iter_missing_items.py:13:13: PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
|
|
||||
12 | # Errors
|
||||
13 | for k, v in d:
|
||||
| ^ PLE1141
|
||||
14 | pass
|
||||
|
|
||||
= help: Add a call to `.items()`
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | s2 = {1, 2, 3}
|
||||
11 11 |
|
||||
12 12 | # Errors
|
||||
13 |-for k, v in d:
|
||||
13 |+for k, v in d.items():
|
||||
14 14 | pass
|
||||
15 15 |
|
||||
16 16 | for k, v in d_tuple_incorrect_tuple:
|
||||
|
||||
dict_iter_missing_items.py:16:13: PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
|
|
||||
14 | pass
|
||||
15 |
|
||||
16 | for k, v in d_tuple_incorrect_tuple:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ PLE1141
|
||||
17 | pass
|
||||
|
|
||||
= help: Add a call to `.items()`
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | for k, v in d:
|
||||
14 14 | pass
|
||||
15 15 |
|
||||
16 |-for k, v in d_tuple_incorrect_tuple:
|
||||
16 |+for k, v in d_tuple_incorrect_tuple.items():
|
||||
17 17 | pass
|
||||
18 18 |
|
||||
19 19 |
|
||||
|
||||
|
||||
@@ -23,9 +23,28 @@ no_method_decorator.py:9:5: PLR0202 [*] Class method defined without decorator
|
||||
12 13 |
|
||||
13 |- pick_colors = classmethod(pick_colors)
|
||||
14 14 |
|
||||
15 |+
|
||||
15 16 | def pick_one_color(): # [no-staticmethod-decorator]
|
||||
16 17 | """staticmethod to pick one fruit color"""
|
||||
17 18 | return choice(Fruit.COLORS)
|
||||
15 15 | def pick_one_color(): # [no-staticmethod-decorator]
|
||||
16 16 | """staticmethod to pick one fruit color"""
|
||||
|
||||
no_method_decorator.py:22:5: PLR0202 [*] Class method defined without decorator
|
||||
|
|
||||
21 | class Class:
|
||||
22 | def class_method(cls):
|
||||
| PLR0202
|
||||
23 | pass
|
||||
|
|
||||
= help: Add @classmethod decorator
|
||||
|
||||
ℹ Safe fix
|
||||
19 19 | pick_one_color = staticmethod(pick_one_color)
|
||||
20 20 |
|
||||
21 21 | class Class:
|
||||
22 |+ @classmethod
|
||||
22 23 | def class_method(cls):
|
||||
23 24 | pass
|
||||
24 25 |
|
||||
25 |- class_method = classmethod(class_method);another_statement
|
||||
26 |+ another_statement
|
||||
26 27 |
|
||||
27 28 | def static_method():
|
||||
28 29 | pass
|
||||
|
||||
@@ -22,6 +22,27 @@ no_method_decorator.py:15:5: PLR0203 [*] Static method defined without decorator
|
||||
17 18 | return choice(Fruit.COLORS)
|
||||
18 19 |
|
||||
19 |- pick_one_color = staticmethod(pick_one_color)
|
||||
20 |+
|
||||
20 20 |
|
||||
21 21 | class Class:
|
||||
22 22 | def class_method(cls):
|
||||
|
||||
no_method_decorator.py:27:5: PLR0203 [*] Static method defined without decorator
|
||||
|
|
||||
25 | class_method = classmethod(class_method);another_statement
|
||||
26 |
|
||||
27 | def static_method():
|
||||
| PLR0203
|
||||
28 | pass
|
||||
|
|
||||
= help: Add @staticmethod decorator
|
||||
|
||||
ℹ Safe fix
|
||||
24 24 |
|
||||
25 25 | class_method = classmethod(class_method);another_statement
|
||||
26 26 |
|
||||
27 |+ @staticmethod
|
||||
27 28 | def static_method():
|
||||
28 29 | pass
|
||||
29 30 |
|
||||
30 |- static_method = staticmethod(static_method);
|
||||
31 |+
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::str::FromStr;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef};
|
||||
use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef, UnaryOp};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -198,15 +198,31 @@ pub(crate) fn native_literals(
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
Some(arg) => {
|
||||
let Some(literal_expr) = arg.as_literal_expr() else {
|
||||
let literal_expr = if let Some(literal_expr) = arg.as_literal_expr() {
|
||||
// Skip implicit concatenated strings.
|
||||
if literal_expr.is_implicit_concatenated() {
|
||||
return;
|
||||
}
|
||||
literal_expr
|
||||
} else if let Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
op: UnaryOp::UAdd | UnaryOp::USub,
|
||||
operand,
|
||||
..
|
||||
}) = arg
|
||||
{
|
||||
if let Some(literal_expr) = operand
|
||||
.as_literal_expr()
|
||||
.filter(|expr| matches!(expr, LiteralExpressionRef::NumberLiteral(_)))
|
||||
{
|
||||
literal_expr
|
||||
} else {
|
||||
// Only allow unary operators for numbers.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Skip implicit string concatenations.
|
||||
if literal_expr.is_implicit_concatenated() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(arg_literal_type) = LiteralType::try_from(literal_expr) else {
|
||||
return;
|
||||
};
|
||||
@@ -221,14 +237,8 @@ pub(crate) fn native_literals(
|
||||
// Ex) `(7).denominator` is valid but `7.denominator` is not
|
||||
// Note that floats do not have this problem
|
||||
// Ex) `(1.0).real` is valid and `1.0.real` is too
|
||||
let content = match (parent_expr, arg) {
|
||||
(
|
||||
Some(Expr::Attribute(_)),
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(_),
|
||||
..
|
||||
}),
|
||||
) => format!("({arg_code})"),
|
||||
let content = match (parent_expr, literal_type) {
|
||||
(Some(Expr::Attribute(_)), LiteralType::Int) => format!("({arg_code})"),
|
||||
_ => arg_code.to_string(),
|
||||
};
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E
|
||||
elts: remaining,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
};
|
||||
format!("({})", checker.generator().expr(&node.into()))
|
||||
};
|
||||
|
||||
@@ -141,6 +141,7 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E
|
||||
elts: remaining,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
};
|
||||
format!("({})", checker.generator().expr(&node.into()))
|
||||
};
|
||||
|
||||
@@ -127,6 +127,7 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
|
||||
range: TextRange::default(),
|
||||
elts: constraints.into_iter().cloned().collect(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})))
|
||||
}
|
||||
None => None,
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP018.py:37:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
||||
|
|
||||
36 | # These become string or byte literals
|
||||
36 | # These become literals
|
||||
37 | str()
|
||||
| ^^^^^ UP018
|
||||
38 | str("foo")
|
||||
@@ -14,7 +14,7 @@ UP018.py:37:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
||||
ℹ Safe fix
|
||||
34 34 | int().denominator
|
||||
35 35 |
|
||||
36 36 | # These become string or byte literals
|
||||
36 36 | # These become literals
|
||||
37 |-str()
|
||||
37 |+""
|
||||
38 38 | str("foo")
|
||||
@@ -23,7 +23,7 @@ UP018.py:37:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
||||
|
||||
UP018.py:38:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
||||
|
|
||||
36 | # These become string or byte literals
|
||||
36 | # These become literals
|
||||
37 | str()
|
||||
38 | str("foo")
|
||||
| ^^^^^^^^^^ UP018
|
||||
@@ -34,7 +34,7 @@ UP018.py:38:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
||||
|
||||
ℹ Safe fix
|
||||
35 35 |
|
||||
36 36 | # These become string or byte literals
|
||||
36 36 | # These become literals
|
||||
37 37 | str()
|
||||
38 |-str("foo")
|
||||
38 |+"foo"
|
||||
@@ -55,7 +55,7 @@ UP018.py:39:1: UP018 [*] Unnecessary `str` call (rewrite as a literal)
|
||||
= help: Replace with string literal
|
||||
|
||||
ℹ Safe fix
|
||||
36 36 | # These become string or byte literals
|
||||
36 36 | # These become literals
|
||||
37 37 | str()
|
||||
38 38 | str("foo")
|
||||
39 |-str("""
|
||||
@@ -304,6 +304,8 @@ UP018.py:55:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
||||
54 | # These become a literal but retain parentheses
|
||||
55 | int(1).denominator
|
||||
| ^^^^^^ UP018
|
||||
56 |
|
||||
57 | # These too are literals in spirit
|
||||
|
|
||||
= help: Replace with integer literal
|
||||
|
||||
@@ -313,5 +315,82 @@ UP018.py:55:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
||||
54 54 | # These become a literal but retain parentheses
|
||||
55 |-int(1).denominator
|
||||
55 |+(1).denominator
|
||||
56 56 |
|
||||
57 57 | # These too are literals in spirit
|
||||
58 58 | int(+1)
|
||||
|
||||
UP018.py:58:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
||||
|
|
||||
57 | # These too are literals in spirit
|
||||
58 | int(+1)
|
||||
| ^^^^^^^ UP018
|
||||
59 | int(-1)
|
||||
60 | float(+1.0)
|
||||
|
|
||||
= help: Replace with integer literal
|
||||
|
||||
ℹ Safe fix
|
||||
55 55 | int(1).denominator
|
||||
56 56 |
|
||||
57 57 | # These too are literals in spirit
|
||||
58 |-int(+1)
|
||||
58 |++1
|
||||
59 59 | int(-1)
|
||||
60 60 | float(+1.0)
|
||||
61 61 | float(-1.0)
|
||||
|
||||
UP018.py:59:1: UP018 [*] Unnecessary `int` call (rewrite as a literal)
|
||||
|
|
||||
57 | # These too are literals in spirit
|
||||
58 | int(+1)
|
||||
59 | int(-1)
|
||||
| ^^^^^^^ UP018
|
||||
60 | float(+1.0)
|
||||
61 | float(-1.0)
|
||||
|
|
||||
= help: Replace with integer literal
|
||||
|
||||
ℹ Safe fix
|
||||
56 56 |
|
||||
57 57 | # These too are literals in spirit
|
||||
58 58 | int(+1)
|
||||
59 |-int(-1)
|
||||
59 |+-1
|
||||
60 60 | float(+1.0)
|
||||
61 61 | float(-1.0)
|
||||
|
||||
UP018.py:60:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
||||
|
|
||||
58 | int(+1)
|
||||
59 | int(-1)
|
||||
60 | float(+1.0)
|
||||
| ^^^^^^^^^^^ UP018
|
||||
61 | float(-1.0)
|
||||
|
|
||||
= help: Replace with float literal
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | # These too are literals in spirit
|
||||
58 58 | int(+1)
|
||||
59 59 | int(-1)
|
||||
60 |-float(+1.0)
|
||||
60 |++1.0
|
||||
61 61 | float(-1.0)
|
||||
|
||||
UP018.py:61:1: UP018 [*] Unnecessary `float` call (rewrite as a literal)
|
||||
|
|
||||
59 | int(-1)
|
||||
60 | float(+1.0)
|
||||
61 | float(-1.0)
|
||||
| ^^^^^^^^^^^ UP018
|
||||
|
|
||||
= help: Replace with float literal
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 | int(+1)
|
||||
59 59 | int(-1)
|
||||
60 60 | float(+1.0)
|
||||
61 |-float(-1.0)
|
||||
61 |+-1.0
|
||||
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ fn make_suggestion(group: &AppendGroup, generator: Generator) -> String {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
};
|
||||
// Make `var.extend`.
|
||||
// NOTE: receiver is the same for all appends and that's why we can take the first.
|
||||
|
||||
@@ -162,6 +162,7 @@ fn concatenate_expressions(expr: &Expr) -> Option<(Expr, Type)> {
|
||||
elts: new_elts,
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
}
|
||||
.into(),
|
||||
};
|
||||
|
||||
@@ -70,6 +70,11 @@ pub(crate) fn missing_fstring_syntax(
|
||||
}
|
||||
}
|
||||
|
||||
// We also want to avoid expressions that are intended to be translated.
|
||||
if semantic.current_expressions().any(is_gettext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if should_be_fstring(literal, locator, semantic) {
|
||||
let diagnostic = Diagnostic::new(MissingFStringSyntax, literal.range())
|
||||
.with_fix(fix_fstring_syntax(literal.range()));
|
||||
@@ -77,6 +82,26 @@ pub(crate) fn missing_fstring_syntax(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if an expression appears to be a `gettext` call.
|
||||
///
|
||||
/// We want to avoid statement expressions and assignments related to aliases
|
||||
/// of the gettext API.
|
||||
///
|
||||
/// See <https://docs.python.org/3/library/gettext.html> for details. When one
|
||||
/// uses `_` to mark a string for translation, the tools look for these markers
|
||||
/// and replace the original string with its translated counterpart. If the
|
||||
/// string contains variable placeholders or formatting, it can complicate the
|
||||
/// translation process, lead to errors or incorrect translations.
|
||||
fn is_gettext(expr: &ast::Expr) -> bool {
|
||||
let ast::Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||
return false;
|
||||
};
|
||||
let ast::Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
matches!(id.as_str(), "_" | "gettext" | "ngettext")
|
||||
}
|
||||
|
||||
/// Returns `true` if `literal` is likely an f-string with a missing `f` prefix.
|
||||
/// See [`MissingFStringSyntax`] for the validation criteria.
|
||||
fn should_be_fstring(
|
||||
|
||||
@@ -116,6 +116,7 @@ pub(crate) fn never_union(checker: &mut Checker, expr: &Expr) {
|
||||
elts,
|
||||
ctx: _,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) = slice.as_ref()
|
||||
else {
|
||||
return;
|
||||
@@ -157,6 +158,7 @@ pub(crate) fn never_union(checker: &mut Checker, expr: &Expr) {
|
||||
elts: rest,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
})),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
|
||||
@@ -134,23 +134,23 @@ impl InferredMemberType {
|
||||
/// single-line tuple literals *can* be unparenthesized.
|
||||
/// We keep the original AST node around for the
|
||||
/// Tuple variant so that this can be queried later.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum SequenceKind<'a> {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(super) enum SequenceKind {
|
||||
List,
|
||||
Set,
|
||||
Tuple(&'a ast::ExprTuple),
|
||||
Tuple { parenthesized: bool },
|
||||
}
|
||||
|
||||
impl SequenceKind<'_> {
|
||||
impl SequenceKind {
|
||||
// N.B. We only need the source code for the Tuple variant here,
|
||||
// but if you already have a `Locator` instance handy,
|
||||
// getting the source code is very cheap.
|
||||
fn surrounding_brackets(&self, source: &str) -> (&'static str, &'static str) {
|
||||
fn surrounding_brackets(self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
Self::List => ("[", "]"),
|
||||
Self::Set => ("{", "}"),
|
||||
Self::Tuple(ast_node) => {
|
||||
if ast_node.is_parenthesized(source) {
|
||||
Self::Tuple { parenthesized } => {
|
||||
if parenthesized {
|
||||
("(", ")")
|
||||
} else {
|
||||
("", "")
|
||||
@@ -159,19 +159,19 @@ impl SequenceKind<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
const fn opening_token_for_multiline_definition(&self) -> TokenKind {
|
||||
const fn opening_token_for_multiline_definition(self) -> TokenKind {
|
||||
match self {
|
||||
Self::List => TokenKind::Lsqb,
|
||||
Self::Set => TokenKind::Lbrace,
|
||||
Self::Tuple(_) => TokenKind::Lpar,
|
||||
Self::Tuple { .. } => TokenKind::Lpar,
|
||||
}
|
||||
}
|
||||
|
||||
const fn closing_token_for_multiline_definition(&self) -> TokenKind {
|
||||
const fn closing_token_for_multiline_definition(self) -> TokenKind {
|
||||
match self {
|
||||
Self::List => TokenKind::Rsqb,
|
||||
Self::Set => TokenKind::Rbrace,
|
||||
Self::Tuple(_) => TokenKind::Rpar,
|
||||
Self::Tuple { .. } => TokenKind::Rpar,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ impl<'a> SequenceElements<'a> {
|
||||
/// that can be inserted into the
|
||||
/// source code as a `range_replacement` autofix.
|
||||
pub(super) fn sort_single_line_elements_sequence(
|
||||
kind: &SequenceKind,
|
||||
kind: SequenceKind,
|
||||
elts: &[ast::Expr],
|
||||
elements: &[&str],
|
||||
locator: &Locator,
|
||||
@@ -225,7 +225,7 @@ pub(super) fn sort_single_line_elements_sequence(
|
||||
) -> String {
|
||||
let element_pairs = SequenceElements::new(elements, elts);
|
||||
let last_item_index = element_pairs.last_item_index();
|
||||
let (opening_paren, closing_paren) = kind.surrounding_brackets(locator.contents());
|
||||
let (opening_paren, closing_paren) = kind.surrounding_brackets();
|
||||
let mut result = String::from(opening_paren);
|
||||
// We grab the original source-code ranges using `locator.slice()`
|
||||
// rather than using the expression generator, as this approach allows
|
||||
@@ -334,7 +334,7 @@ impl MultilineStringSequenceValue {
|
||||
/// Return `None` if the analysis fails for whatever reason.
|
||||
pub(super) fn from_source_range(
|
||||
range: TextRange,
|
||||
kind: &SequenceKind,
|
||||
kind: SequenceKind,
|
||||
locator: &Locator,
|
||||
) -> Option<MultilineStringSequenceValue> {
|
||||
// Parse the multiline string sequence using the raw tokens.
|
||||
@@ -486,7 +486,7 @@ impl Ranged for MultilineStringSequenceValue {
|
||||
/// in the original source code.
|
||||
fn collect_string_sequence_lines(
|
||||
range: TextRange,
|
||||
kind: &SequenceKind,
|
||||
kind: SequenceKind,
|
||||
locator: &Locator,
|
||||
) -> Option<(Vec<StringSequenceLine>, bool)> {
|
||||
// These first two variables are used for keeping track of state
|
||||
|
||||
@@ -152,9 +152,13 @@ fn sort_dunder_all(checker: &mut Checker, target: &ast::Expr, node: &ast::Expr)
|
||||
|
||||
let (elts, range, kind) = match node {
|
||||
ast::Expr::List(ast::ExprList { elts, range, .. }) => (elts, *range, SequenceKind::List),
|
||||
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => {
|
||||
(elts, *range, SequenceKind::Tuple(tuple_node))
|
||||
}
|
||||
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => (
|
||||
elts,
|
||||
*range,
|
||||
SequenceKind::Tuple {
|
||||
parenthesized: tuple_node.parenthesized,
|
||||
},
|
||||
),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@@ -166,7 +170,7 @@ fn sort_dunder_all(checker: &mut Checker, target: &ast::Expr, node: &ast::Expr)
|
||||
let mut diagnostic = Diagnostic::new(UnsortedDunderAll, range);
|
||||
|
||||
if let SortClassification::UnsortedAndMaybeFixable { items } = elts_analysis {
|
||||
if let Some(fix) = create_fix(range, elts, &items, &kind, checker) {
|
||||
if let Some(fix) = create_fix(range, elts, &items, kind, checker) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +191,7 @@ fn create_fix(
|
||||
range: TextRange,
|
||||
elts: &[ast::Expr],
|
||||
string_items: &[&str],
|
||||
kind: &SequenceKind,
|
||||
kind: SequenceKind,
|
||||
checker: &Checker,
|
||||
) -> Option<Fix> {
|
||||
let locator = checker.locator();
|
||||
|
||||
@@ -157,7 +157,9 @@ impl<'a> StringLiteralDisplay<'a> {
|
||||
}
|
||||
}
|
||||
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => {
|
||||
let display_kind = DisplayKind::Sequence(SequenceKind::Tuple(tuple_node));
|
||||
let display_kind = DisplayKind::Sequence(SequenceKind::Tuple {
|
||||
parenthesized: tuple_node.parenthesized,
|
||||
});
|
||||
Self {
|
||||
elts: Cow::Borrowed(elts),
|
||||
range: *range,
|
||||
@@ -211,7 +213,7 @@ impl<'a> StringLiteralDisplay<'a> {
|
||||
(DisplayKind::Sequence(sequence_kind), true) => {
|
||||
let analyzed_sequence = MultilineStringSequenceValue::from_source_range(
|
||||
self.range(),
|
||||
sequence_kind,
|
||||
*sequence_kind,
|
||||
locator,
|
||||
)?;
|
||||
assert_eq!(analyzed_sequence.len(), self.elts.len());
|
||||
@@ -220,7 +222,7 @@ impl<'a> StringLiteralDisplay<'a> {
|
||||
// Sorting multiline dicts is unsupported
|
||||
(DisplayKind::Dict { .. }, true) => return None,
|
||||
(DisplayKind::Sequence(sequence_kind), false) => sort_single_line_elements_sequence(
|
||||
sequence_kind,
|
||||
*sequence_kind,
|
||||
&self.elts,
|
||||
items,
|
||||
locator,
|
||||
@@ -242,7 +244,7 @@ impl<'a> StringLiteralDisplay<'a> {
|
||||
/// Python provides for builtin containers.
|
||||
#[derive(Debug)]
|
||||
enum DisplayKind<'a> {
|
||||
Sequence(SequenceKind<'a>),
|
||||
Sequence(SequenceKind),
|
||||
Dict { values: &'a [ast::Expr] },
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,21 @@ use crate::checkers::ast::Checker;
|
||||
use crate::fix::snippet::SourceCodeSnippet;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `list(...)[0]` that can be replaced with
|
||||
/// `next(iter(...))`.
|
||||
/// Checks the following constructs, all of which can be replaced by
|
||||
/// `next(iter(...))`:
|
||||
///
|
||||
/// - `list(...)[0]`
|
||||
/// - `tuple(...)[0]`
|
||||
/// - `list(i for i in ...)[0]`
|
||||
/// - `[i for i in ...][0]`
|
||||
/// - `list(...).pop(0)`
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling `list(...)` will create a new list of the entire collection, which
|
||||
/// can be very expensive for large collections. If you only need the first
|
||||
/// element of the collection, you can use `next(...)` or `next(iter(...)` to
|
||||
/// lazily fetch the first element.
|
||||
/// Calling e.g. `list(...)` will create a new list of the entire collection,
|
||||
/// which can be very expensive for large collections. If you only need the
|
||||
/// first element of the collection, you can use `next(...)` or
|
||||
/// `next(iter(...)` to lazily fetch the first element. The same is true for
|
||||
/// the other constructs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -33,14 +40,16 @@ use crate::fix::snippet::SourceCodeSnippet;
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as migrating from `list(...)[0]` to
|
||||
/// `next(iter(...))` can change the behavior of your program in two ways:
|
||||
/// This rule's fix is marked as unsafe, as migrating from e.g. `list(...)[0]`
|
||||
/// to `next(iter(...))` can change the behavior of your program in two ways:
|
||||
///
|
||||
/// 1. First, `list(...)` will eagerly evaluate the entire collection, while
|
||||
/// `next(iter(...))` will only evaluate the first element. As such, any
|
||||
/// side effects that occur during iteration will be delayed.
|
||||
/// 2. Second, `list(...)[0]` will raise `IndexError` if the collection is
|
||||
/// empty, while `next(iter(...))` will raise `StopIteration`.
|
||||
/// 1. First, all above mentioned constructs will eagerly evaluate the entire
|
||||
/// collection, while `next(iter(...))` will only evaluate the first
|
||||
/// element. As such, any side effects that occur during iteration will be
|
||||
/// delayed.
|
||||
/// 2. Second, accessing members of a collection via square bracket notation
|
||||
/// `[0]` of the `pop()` function will raise `IndexError` if the collection
|
||||
/// is empty, while `next(iter(...))` will raise `StopIteration`.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Iterators and Iterables in Python: Run Efficient Iterations](https://realpython.com/python-iterators-iterables/#when-to-use-an-iterator-in-python)
|
||||
@@ -67,18 +76,37 @@ impl AlwaysFixableViolation for UnnecessaryIterableAllocationForFirstElement {
|
||||
/// RUF015
|
||||
pub(crate) fn unnecessary_iterable_allocation_for_first_element(
|
||||
checker: &mut Checker,
|
||||
subscript: &ast::ExprSubscript,
|
||||
expr: &ast::Expr,
|
||||
) {
|
||||
let ast::ExprSubscript {
|
||||
value,
|
||||
slice,
|
||||
range,
|
||||
..
|
||||
} = subscript;
|
||||
|
||||
if !is_head_slice(slice) {
|
||||
return;
|
||||
}
|
||||
let (value, range) = match expr {
|
||||
ast::Expr::Subscript(ast::ExprSubscript {
|
||||
value,
|
||||
slice,
|
||||
range,
|
||||
..
|
||||
}) => {
|
||||
if !is_head_slice(slice) {
|
||||
return;
|
||||
}
|
||||
(value, range)
|
||||
}
|
||||
ast::Expr::Call(ast::ExprCall {
|
||||
func, arguments, ..
|
||||
}) => {
|
||||
let Some(arg) = arguments.args.first() else {
|
||||
return;
|
||||
};
|
||||
if !is_head_slice(arg) {
|
||||
return;
|
||||
}
|
||||
let ast::Expr::Attribute(ast::ExprAttribute { range, value, .. }) = func.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
(value, range)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let Some(target) = match_iteration_target(value, checker.semantic()) else {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
assertion_line: 58
|
||||
---
|
||||
RUF015.py:4:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
|
|
||||
@@ -383,7 +384,7 @@ RUF015.py:57:1: RUF015 [*] Prefer `next(zip(x, y))` over single element slice
|
||||
57 |+next(zip(x, y))
|
||||
58 58 | [*zip(x, y)][0]
|
||||
59 59 |
|
||||
60 60 |
|
||||
60 60 | # RUF015 (pop)
|
||||
|
||||
RUF015.py:58:1: RUF015 [*] Prefer `next(zip(x, y))` over single element slice
|
||||
|
|
||||
@@ -391,6 +392,8 @@ RUF015.py:58:1: RUF015 [*] Prefer `next(zip(x, y))` over single element slice
|
||||
57 | list(zip(x, y))[0]
|
||||
58 | [*zip(x, y)][0]
|
||||
| ^^^^^^^^^^^^^^^ RUF015
|
||||
59 |
|
||||
60 | # RUF015 (pop)
|
||||
|
|
||||
= help: Replace with `next(zip(x, y))`
|
||||
|
||||
@@ -401,23 +404,41 @@ RUF015.py:58:1: RUF015 [*] Prefer `next(zip(x, y))` over single element slice
|
||||
58 |-[*zip(x, y)][0]
|
||||
58 |+next(zip(x, y))
|
||||
59 59 |
|
||||
60 60 |
|
||||
61 61 | def test():
|
||||
60 60 | # RUF015 (pop)
|
||||
61 61 | list(x).pop(0)
|
||||
|
||||
RUF015.py:63:5: RUF015 [*] Prefer `next(iter(zip(x, y)))` over single element slice
|
||||
RUF015.py:61:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
|
|
||||
61 | def test():
|
||||
62 | zip = list # Overwrite the builtin zip
|
||||
63 | list(zip(x, y))[0]
|
||||
60 | # RUF015 (pop)
|
||||
61 | list(x).pop(0)
|
||||
| ^^^^^^^^^^^ RUF015
|
||||
62 |
|
||||
63 | # OK
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
|
||||
ℹ Unsafe fix
|
||||
58 58 | [*zip(x, y)][0]
|
||||
59 59 |
|
||||
60 60 | # RUF015 (pop)
|
||||
61 |-list(x).pop(0)
|
||||
61 |+next(iter(x))(0)
|
||||
62 62 |
|
||||
63 63 | # OK
|
||||
64 64 | list(x).pop(1)
|
||||
|
||||
RUF015.py:68:5: RUF015 [*] Prefer `next(iter(zip(x, y)))` over single element slice
|
||||
|
|
||||
66 | def test():
|
||||
67 | zip = list # Overwrite the builtin zip
|
||||
68 | list(zip(x, y))[0]
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF015
|
||||
|
|
||||
= help: Replace with `next(iter(zip(x, y)))`
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 |
|
||||
61 61 | def test():
|
||||
62 62 | zip = list # Overwrite the builtin zip
|
||||
63 |- list(zip(x, y))[0]
|
||||
63 |+ next(iter(zip(x, y)))
|
||||
|
||||
|
||||
65 65 |
|
||||
66 66 | def test():
|
||||
67 67 | zip = list # Overwrite the builtin zip
|
||||
68 |- list(zip(x, y))[0]
|
||||
68 |+ next(iter(zip(x, y)))
|
||||
|
||||
@@ -977,6 +977,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => Self::GeneratorExp(ExprGeneratorExp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(Into::into).collect(),
|
||||
@@ -1072,6 +1073,7 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
|
||||
elts,
|
||||
ctx: _,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => Self::Tuple(ExprTuple {
|
||||
elts: elts.iter().map(Into::into).collect(),
|
||||
}),
|
||||
|
||||
@@ -183,6 +183,7 @@ pub fn any_over_expr(expr: &Expr, func: &dyn Fn(&Expr) -> bool) -> bool {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
any_over_expr(elt, func)
|
||||
|| generators.iter().any(|generator| {
|
||||
@@ -1423,6 +1424,7 @@ pub fn pep_604_union(elts: &[Expr]) -> Expr {
|
||||
elts: vec![],
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
}),
|
||||
[Expr::Tuple(ast::ExprTuple { elts, .. })] => pep_604_union(elts),
|
||||
[elt] => elt.clone(),
|
||||
@@ -1457,6 +1459,7 @@ pub fn typing_union(elts: &[Expr], binding: String) -> Expr {
|
||||
elts: vec![],
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
parenthesized: true,
|
||||
}),
|
||||
[Expr::Tuple(ast::ExprTuple { elts, .. })] => typing_union(elts, binding),
|
||||
[elt] => elt.clone(),
|
||||
|
||||
@@ -2430,6 +2430,7 @@ impl AstNode for ast::ExprGeneratorExp {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
} = self;
|
||||
visitor.visit_expr(elt);
|
||||
for comprehension in generators {
|
||||
@@ -3256,6 +3257,7 @@ impl AstNode for ast::ExprTuple {
|
||||
elts,
|
||||
ctx: _,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
} = self;
|
||||
|
||||
for expr in elts {
|
||||
|
||||
@@ -8,7 +8,6 @@ use std::slice::{Iter, IterMut};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::{int, LiteralExpressionRef};
|
||||
@@ -842,6 +841,7 @@ pub struct ExprGeneratorExp {
|
||||
pub range: TextRange,
|
||||
pub elt: Box<Expr>,
|
||||
pub generators: Vec<Comprehension>,
|
||||
pub parenthesized: bool,
|
||||
}
|
||||
|
||||
impl From<ExprGeneratorExp> for Expr {
|
||||
@@ -1796,6 +1796,9 @@ pub struct ExprTuple {
|
||||
pub range: TextRange,
|
||||
pub elts: Vec<Expr>,
|
||||
pub ctx: ExprContext,
|
||||
|
||||
/// Whether the tuple is parenthesized in the source code.
|
||||
pub parenthesized: bool,
|
||||
}
|
||||
|
||||
impl From<ExprTuple> for Expr {
|
||||
@@ -1804,37 +1807,6 @@ impl From<ExprTuple> for Expr {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprTuple {
|
||||
/// Return `true` if a tuple is parenthesized in the source code.
|
||||
pub fn is_parenthesized(&self, source: &str) -> bool {
|
||||
let Some(elt) = self.elts.first() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Count the number of open parentheses between the start of the tuple and the first element.
|
||||
let open_parentheses_count =
|
||||
SimpleTokenizer::new(source, TextRange::new(self.start(), elt.start()))
|
||||
.skip_trivia()
|
||||
.filter(|token| token.kind() == SimpleTokenKind::LParen)
|
||||
.count();
|
||||
if open_parentheses_count == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count the number of parentheses between the end of the first element and its trailing comma.
|
||||
let close_parentheses_count =
|
||||
SimpleTokenizer::new(source, TextRange::new(elt.end(), self.end()))
|
||||
.skip_trivia()
|
||||
.take_while(|token| token.kind() != SimpleTokenKind::Comma)
|
||||
.filter(|token| token.kind() == SimpleTokenKind::RParen)
|
||||
.count();
|
||||
|
||||
// If the number of open parentheses is greater than the number of close parentheses, the tuple
|
||||
// is parenthesized.
|
||||
open_parentheses_count > close_parentheses_count
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [Slice](https://docs.python.org/3/library/ast.html#ast.Slice)
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExprSlice {
|
||||
@@ -3911,7 +3883,7 @@ mod tests {
|
||||
assert_eq!(std::mem::size_of::<ExprDictComp>(), 48);
|
||||
assert_eq!(std::mem::size_of::<ExprEllipsisLiteral>(), 8);
|
||||
assert_eq!(std::mem::size_of::<ExprFString>(), 48);
|
||||
assert_eq!(std::mem::size_of::<ExprGeneratorExp>(), 40);
|
||||
assert_eq!(std::mem::size_of::<ExprGeneratorExp>(), 48);
|
||||
assert_eq!(std::mem::size_of::<ExprIfExp>(), 32);
|
||||
assert_eq!(std::mem::size_of::<ExprIpyEscapeCommand>(), 32);
|
||||
assert_eq!(std::mem::size_of::<ExprLambda>(), 24);
|
||||
|
||||
@@ -441,6 +441,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension);
|
||||
@@ -539,6 +540,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
|
||||
@@ -428,6 +428,7 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
for comprehension in generators {
|
||||
visitor.visit_comprehension(comprehension);
|
||||
@@ -528,6 +529,7 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
|
||||
elts,
|
||||
ctx,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
}) => {
|
||||
for expr in elts {
|
||||
visitor.visit_expr(expr);
|
||||
|
||||
@@ -970,6 +970,7 @@ impl<'a> Generator<'a> {
|
||||
Expr::GeneratorExp(ast::ExprGeneratorExp {
|
||||
elt,
|
||||
generators,
|
||||
parenthesized: _,
|
||||
range: _,
|
||||
}) => {
|
||||
self.p("(");
|
||||
@@ -1037,6 +1038,7 @@ impl<'a> Generator<'a> {
|
||||
elt,
|
||||
generators,
|
||||
range: _,
|
||||
parenthesized: _,
|
||||
})],
|
||||
[],
|
||||
) = (arguments.args.as_ref(), arguments.keywords.as_ref())
|
||||
|
||||
@@ -12,7 +12,6 @@ use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::comments::visitor::{CommentPlacement, DecoratedComment};
|
||||
use crate::expression::expr_generator_exp::is_generator_parenthesized;
|
||||
use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection};
|
||||
use crate::other::parameters::{
|
||||
assign_argument_separator_comment_placement, find_parameter_separators,
|
||||
@@ -315,12 +314,11 @@ fn handle_enclosed_comment<'a>(
|
||||
| AnyNodeRef::ExprSet(_)
|
||||
| AnyNodeRef::ExprListComp(_)
|
||||
| AnyNodeRef::ExprSetComp(_) => handle_bracketed_end_of_line_comment(comment, locator),
|
||||
AnyNodeRef::ExprTuple(tuple) if tuple.is_parenthesized(locator.contents()) => {
|
||||
handle_bracketed_end_of_line_comment(comment, locator)
|
||||
}
|
||||
AnyNodeRef::ExprGeneratorExp(generator)
|
||||
if is_generator_parenthesized(generator, locator.contents()) =>
|
||||
{
|
||||
AnyNodeRef::ExprTuple(ast::ExprTuple {
|
||||
parenthesized: true,
|
||||
..
|
||||
}) => handle_bracketed_end_of_line_comment(comment, locator),
|
||||
AnyNodeRef::ExprGeneratorExp(generator) if generator.parenthesized => {
|
||||
handle_bracketed_end_of_line_comment(comment, locator)
|
||||
}
|
||||
_ => CommentPlacement::Default(comment),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use ruff_formatter::{format_args, write, FormatRuleWithOptions};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprGeneratorExp;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
|
||||
@@ -42,6 +40,7 @@ impl FormatNodeRule<ExprGeneratorExp> for FormatExprGeneratorExp {
|
||||
range: _,
|
||||
elt,
|
||||
generators,
|
||||
parenthesized: is_parenthesized,
|
||||
} = item;
|
||||
|
||||
let joined = format_with(|f| {
|
||||
@@ -55,7 +54,7 @@ impl FormatNodeRule<ExprGeneratorExp> for FormatExprGeneratorExp {
|
||||
|
||||
if self.parentheses == GeneratorExpParentheses::Preserve
|
||||
&& dangling.is_empty()
|
||||
&& !is_generator_parenthesized(item, f.context().source())
|
||||
&& !is_parenthesized
|
||||
{
|
||||
write!(
|
||||
f,
|
||||
@@ -101,37 +100,3 @@ impl NeedsParentheses for ExprGeneratorExp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a generator is parenthesized in the source code.
|
||||
pub(crate) fn is_generator_parenthesized(generator: &ExprGeneratorExp, source: &str) -> bool {
|
||||
// Count the number of open parentheses between the start of the generator and the first element.
|
||||
let open_parentheses_count = SimpleTokenizer::new(
|
||||
source,
|
||||
TextRange::new(generator.start(), generator.elt.start()),
|
||||
)
|
||||
.skip_trivia()
|
||||
.filter(|token| token.kind() == SimpleTokenKind::LParen)
|
||||
.count();
|
||||
if open_parentheses_count == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count the number of parentheses between the end of the generator and its trailing comma.
|
||||
let close_parentheses_count = SimpleTokenizer::new(
|
||||
source,
|
||||
TextRange::new(
|
||||
generator.elt.end(),
|
||||
generator
|
||||
.generators
|
||||
.first()
|
||||
.map_or(generator.end(), Ranged::start),
|
||||
),
|
||||
)
|
||||
.skip_trivia()
|
||||
.filter(|token| token.kind() == SimpleTokenKind::RParen)
|
||||
.count();
|
||||
|
||||
// If the number of open parentheses is greater than the number of close parentheses, the
|
||||
// generator is parenthesized.
|
||||
open_parentheses_count > close_parentheses_count
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
||||
elts,
|
||||
ctx: _,
|
||||
range: _,
|
||||
parenthesized: is_parenthesized,
|
||||
} = item;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
@@ -136,7 +137,7 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
||||
return empty_parenthesized("(", dangling, ")").fmt(f);
|
||||
}
|
||||
[single] => match self.parentheses {
|
||||
TupleParentheses::Preserve if !item.is_parenthesized(f.context().source()) => {
|
||||
TupleParentheses::Preserve if !is_parenthesized => {
|
||||
write!(f, [single.format(), token(",")])
|
||||
}
|
||||
_ =>
|
||||
@@ -152,7 +153,7 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
||||
//
|
||||
// Unlike other expression parentheses, tuple parentheses are part of the range of the
|
||||
// tuple itself.
|
||||
_ if item.is_parenthesized(f.context().source())
|
||||
_ if *is_parenthesized
|
||||
&& !(self.parentheses == TupleParentheses::NeverPreserve
|
||||
&& dangling.is_empty()) =>
|
||||
{
|
||||
|
||||
@@ -14,7 +14,6 @@ use ruff_text_size::Ranged;
|
||||
use crate::builders::parenthesize_if_expands;
|
||||
use crate::comments::{leading_comments, trailing_comments, LeadingDanglingTrailingComments};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::expr_generator_exp::is_generator_parenthesized;
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, optional_parentheses, parenthesized, NeedsParentheses,
|
||||
OptionalParentheses, Parentheses, Parenthesize,
|
||||
@@ -661,15 +660,16 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::Tuple(tuple) if tuple.is_parenthesized(self.context.source()) => {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
parenthesized: true,
|
||||
..
|
||||
}) => {
|
||||
self.any_parenthesized_expressions = true;
|
||||
// The values are always parenthesized, don't visit.
|
||||
return;
|
||||
}
|
||||
|
||||
Expr::GeneratorExp(generator)
|
||||
if is_generator_parenthesized(generator, self.context.source()) =>
|
||||
{
|
||||
Expr::GeneratorExp(generator) if generator.parenthesized => {
|
||||
self.any_parenthesized_expressions = true;
|
||||
// The values are always parenthesized, don't visit.
|
||||
return;
|
||||
@@ -1035,11 +1035,7 @@ pub(crate) fn has_own_parentheses(
|
||||
Some(OwnParentheses::NonEmpty)
|
||||
}
|
||||
|
||||
Expr::GeneratorExp(generator)
|
||||
if is_generator_parenthesized(generator, context.source()) =>
|
||||
{
|
||||
Some(OwnParentheses::NonEmpty)
|
||||
}
|
||||
Expr::GeneratorExp(generator) if generator.parenthesized => Some(OwnParentheses::NonEmpty),
|
||||
|
||||
// These expressions must contain _some_ child or trivia token in order to be non-empty.
|
||||
Expr::List(ast::ExprList { elts, .. }) | Expr::Set(ast::ExprSet { elts, .. }) => {
|
||||
@@ -1050,7 +1046,12 @@ pub(crate) fn has_own_parentheses(
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Tuple(tuple) if tuple.is_parenthesized(context.source()) => {
|
||||
Expr::Tuple(
|
||||
tuple @ ast::ExprTuple {
|
||||
parenthesized: true,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
if !tuple.elts.is_empty() || context.comments().has_dangling(AnyNodeRef::from(expr)) {
|
||||
Some(OwnParentheses::NonEmpty)
|
||||
} else {
|
||||
|
||||
@@ -59,16 +59,16 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
|
||||
return result;
|
||||
}
|
||||
|
||||
let quotes = normalizer.choose_quotes(&string, &locator);
|
||||
let quote_selection = normalizer.choose_quotes(&string, &locator);
|
||||
|
||||
let context = FStringContext::new(
|
||||
string.prefix(),
|
||||
quotes,
|
||||
quote_selection.quotes(),
|
||||
FStringLayout::from_f_string(self.value, &locator),
|
||||
);
|
||||
|
||||
// Starting prefix and quote
|
||||
write!(f, [string.prefix(), quotes])?;
|
||||
write!(f, [string.prefix(), quote_selection.quotes()])?;
|
||||
|
||||
f.join()
|
||||
.entries(
|
||||
@@ -80,7 +80,7 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
|
||||
.finish()?;
|
||||
|
||||
// Ending quote
|
||||
quotes.fmt(f)
|
||||
quote_selection.quotes().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,11 @@ impl Format<PyFormatContext<'_>> for FormatFStringLiteralElement<'_> {
|
||||
let literal_content = f.context().locator().slice(self.element.range());
|
||||
let normalized = normalize_string(
|
||||
literal_content,
|
||||
0,
|
||||
self.context.quotes(),
|
||||
self.context.prefix(),
|
||||
is_hex_codes_in_unicode_sequences_enabled(f.context()),
|
||||
true,
|
||||
);
|
||||
match &normalized {
|
||||
Cow::Borrowed(_) => source_text_slice(self.element.range()).fmt(f),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_formatter::FormatContext;
|
||||
use ruff_source_file::Locator;
|
||||
@@ -7,7 +8,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use crate::context::FStringState;
|
||||
use crate::options::PythonVersion;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hex_codes_in_unicode_sequences_enabled;
|
||||
use crate::preview::{is_f_string_formatting_enabled, is_hex_codes_in_unicode_sequences_enabled};
|
||||
use crate::string::{QuoteChar, Quoting, StringPart, StringPrefix, StringQuotes};
|
||||
use crate::QuoteStyle;
|
||||
|
||||
@@ -18,6 +19,7 @@ pub(crate) struct StringNormalizer {
|
||||
f_string_state: FStringState,
|
||||
target_version: PythonVersion,
|
||||
normalize_hex: bool,
|
||||
format_fstring: bool,
|
||||
}
|
||||
|
||||
impl StringNormalizer {
|
||||
@@ -29,6 +31,7 @@ impl StringNormalizer {
|
||||
f_string_state: context.f_string_state(),
|
||||
target_version: context.options().target_version(),
|
||||
normalize_hex: is_hex_codes_in_unicode_sequences_enabled(context),
|
||||
format_fstring: is_f_string_formatting_enabled(context),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,68 +45,8 @@ impl StringNormalizer {
|
||||
self
|
||||
}
|
||||
|
||||
/// Computes the strings preferred quotes.
|
||||
pub(crate) fn choose_quotes(&self, string: &StringPart, locator: &Locator) -> StringQuotes {
|
||||
// Per PEP 8, always prefer double quotes for triple-quoted strings.
|
||||
// Except when using quote-style-preserve.
|
||||
let preferred_style = if string.quotes().triple {
|
||||
// ... unless we're formatting a code snippet inside a docstring,
|
||||
// then we specifically want to invert our quote style to avoid
|
||||
// writing out invalid Python.
|
||||
//
|
||||
// It's worth pointing out that we can actually wind up being
|
||||
// somewhat out of sync with PEP8 in this case. Consider this
|
||||
// example:
|
||||
//
|
||||
// def foo():
|
||||
// '''
|
||||
// Something.
|
||||
//
|
||||
// >>> """tricksy"""
|
||||
// '''
|
||||
// pass
|
||||
//
|
||||
// Ideally, this would be reformatted as:
|
||||
//
|
||||
// def foo():
|
||||
// """
|
||||
// Something.
|
||||
//
|
||||
// >>> '''tricksy'''
|
||||
// """
|
||||
// pass
|
||||
//
|
||||
// But the logic here results in the original quoting being
|
||||
// preserved. This is because the quoting style of the outer
|
||||
// docstring is determined, in part, by looking at its contents. In
|
||||
// this case, it notices that it contains a `"""` and thus infers
|
||||
// that using `'''` would overall read better because it avoids
|
||||
// the need to escape the interior `"""`. Except... in this case,
|
||||
// the `"""` is actually part of a code snippet that could get
|
||||
// reformatted to using a different quoting style itself.
|
||||
//
|
||||
// Fixing this would, I believe, require some fairly seismic
|
||||
// changes to how formatting strings works. Namely, we would need
|
||||
// to look for code snippets before normalizing the docstring, and
|
||||
// then figure out the quoting style more holistically by looking
|
||||
// at the various kinds of quotes used in the code snippets and
|
||||
// what reformatting them might look like.
|
||||
//
|
||||
// Overall this is a bit of a corner case and just inverting the
|
||||
// style from what the parent ultimately decided upon works, even
|
||||
// if it doesn't have perfect alignment with PEP8.
|
||||
if let Some(quote) = self.parent_docstring_quote_char {
|
||||
QuoteStyle::from(quote.invert())
|
||||
} else if self.preferred_quote_style.is_preserve() {
|
||||
QuoteStyle::Preserve
|
||||
} else {
|
||||
QuoteStyle::Double
|
||||
}
|
||||
} else {
|
||||
self.preferred_quote_style
|
||||
};
|
||||
|
||||
let quoting = if let FStringState::InsideExpressionElement(context) = self.f_string_state {
|
||||
fn quoting(&self, string: &StringPart) -> Quoting {
|
||||
if let FStringState::InsideExpressionElement(context) = self.f_string_state {
|
||||
// If we're inside an f-string, we need to make sure to preserve the
|
||||
// existing quotes unless we're inside a triple-quoted f-string and
|
||||
// the inner string itself isn't triple-quoted. For example:
|
||||
@@ -127,22 +70,110 @@ impl StringNormalizer {
|
||||
}
|
||||
} else {
|
||||
self.quoting
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
match quoting {
|
||||
/// Computes the strings preferred quotes.
|
||||
pub(crate) fn choose_quotes(&self, string: &StringPart, locator: &Locator) -> QuoteSelection {
|
||||
let raw_content = locator.slice(string.content_range());
|
||||
let first_quote_or_normalized_char_offset = raw_content
|
||||
.bytes()
|
||||
.position(|b| matches!(b, b'\\' | b'"' | b'\'' | b'\r' | b'{'));
|
||||
|
||||
let quotes = match self.quoting(string) {
|
||||
Quoting::Preserve => string.quotes(),
|
||||
Quoting::CanChange => {
|
||||
if let Some(preferred_quote) = QuoteChar::from_style(preferred_style) {
|
||||
let raw_content = locator.slice(string.content_range());
|
||||
if string.prefix().is_raw_string() {
|
||||
choose_quotes_for_raw_string(raw_content, string.quotes(), preferred_quote)
|
||||
// Per PEP 8, always prefer double quotes for triple-quoted strings.
|
||||
// Except when using quote-style-preserve.
|
||||
let preferred_style = if string.quotes().triple {
|
||||
// ... unless we're formatting a code snippet inside a docstring,
|
||||
// then we specifically want to invert our quote style to avoid
|
||||
// writing out invalid Python.
|
||||
//
|
||||
// It's worth pointing out that we can actually wind up being
|
||||
// somewhat out of sync with PEP8 in this case. Consider this
|
||||
// example:
|
||||
//
|
||||
// def foo():
|
||||
// '''
|
||||
// Something.
|
||||
//
|
||||
// >>> """tricksy"""
|
||||
// '''
|
||||
// pass
|
||||
//
|
||||
// Ideally, this would be reformatted as:
|
||||
//
|
||||
// def foo():
|
||||
// """
|
||||
// Something.
|
||||
//
|
||||
// >>> '''tricksy'''
|
||||
// """
|
||||
// pass
|
||||
//
|
||||
// But the logic here results in the original quoting being
|
||||
// preserved. This is because the quoting style of the outer
|
||||
// docstring is determined, in part, by looking at its contents. In
|
||||
// this case, it notices that it contains a `"""` and thus infers
|
||||
// that using `'''` would overall read better because it avoids
|
||||
// the need to escape the interior `"""`. Except... in this case,
|
||||
// the `"""` is actually part of a code snippet that could get
|
||||
// reformatted to using a different quoting style itself.
|
||||
//
|
||||
// Fixing this would, I believe, require some fairly seismic
|
||||
// changes to how formatting strings works. Namely, we would need
|
||||
// to look for code snippets before normalizing the docstring, and
|
||||
// then figure out the quoting style more holistically by looking
|
||||
// at the various kinds of quotes used in the code snippets and
|
||||
// what reformatting them might look like.
|
||||
//
|
||||
// Overall this is a bit of a corner case and just inverting the
|
||||
// style from what the parent ultimately decided upon works, even
|
||||
// if it doesn't have perfect alignment with PEP8.
|
||||
if let Some(quote) = self.parent_docstring_quote_char {
|
||||
QuoteStyle::from(quote.invert())
|
||||
} else if self.preferred_quote_style.is_preserve() {
|
||||
QuoteStyle::Preserve
|
||||
} else {
|
||||
choose_quotes_impl(raw_content, string.quotes(), preferred_quote)
|
||||
QuoteStyle::Double
|
||||
}
|
||||
} else {
|
||||
self.preferred_quote_style
|
||||
};
|
||||
|
||||
if let Some(preferred_quote) = QuoteChar::from_style(preferred_style) {
|
||||
if let Some(first_quote_or_normalized_char_offset) =
|
||||
first_quote_or_normalized_char_offset
|
||||
{
|
||||
if string.prefix().is_raw_string() {
|
||||
choose_quotes_for_raw_string(
|
||||
&raw_content[first_quote_or_normalized_char_offset..],
|
||||
string.quotes(),
|
||||
preferred_quote,
|
||||
)
|
||||
} else {
|
||||
choose_quotes_impl(
|
||||
&raw_content[first_quote_or_normalized_char_offset..],
|
||||
string.quotes(),
|
||||
preferred_quote,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
StringQuotes {
|
||||
quote_char: preferred_quote,
|
||||
triple: string.quotes().is_triple(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
string.quotes()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QuoteSelection {
|
||||
quotes,
|
||||
first_quote_or_normalized_char_offset,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,19 +185,48 @@ impl StringNormalizer {
|
||||
) -> NormalizedString<'a> {
|
||||
let raw_content = locator.slice(string.content_range());
|
||||
|
||||
let quotes = self.choose_quotes(string, locator);
|
||||
let quote_selection = self.choose_quotes(string, locator);
|
||||
|
||||
let normalized = normalize_string(raw_content, quotes, string.prefix(), self.normalize_hex);
|
||||
let normalized = if let Some(first_quote_or_escape_offset) =
|
||||
quote_selection.first_quote_or_normalized_char_offset
|
||||
{
|
||||
normalize_string(
|
||||
raw_content,
|
||||
first_quote_or_escape_offset,
|
||||
quote_selection.quotes,
|
||||
string.prefix(),
|
||||
self.normalize_hex,
|
||||
// TODO: Remove the `b'{'` in `choose_quotes` when promoting the
|
||||
// `format_fstring` preview style
|
||||
self.format_fstring,
|
||||
)
|
||||
} else {
|
||||
Cow::Borrowed(raw_content)
|
||||
};
|
||||
|
||||
NormalizedString {
|
||||
prefix: string.prefix(),
|
||||
content_range: string.content_range(),
|
||||
text: normalized,
|
||||
quotes,
|
||||
quotes: quote_selection.quotes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct QuoteSelection {
|
||||
quotes: StringQuotes,
|
||||
|
||||
/// Offset to the first quote character or character that needs special handling in [`normalize_string`].
|
||||
first_quote_or_normalized_char_offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl QuoteSelection {
|
||||
pub(crate) fn quotes(&self) -> StringQuotes {
|
||||
self.quotes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NormalizedString<'a> {
|
||||
prefix: crate::string::StringPrefix,
|
||||
@@ -391,9 +451,11 @@ fn choose_quotes_impl(
|
||||
/// Returns the normalized string and whether it contains new lines.
|
||||
pub(crate) fn normalize_string(
|
||||
input: &str,
|
||||
start_offset: usize,
|
||||
quotes: StringQuotes,
|
||||
prefix: StringPrefix,
|
||||
normalize_hex: bool,
|
||||
format_fstring: bool,
|
||||
) -> Cow<str> {
|
||||
// The normalized string if `input` is not yet normalized.
|
||||
// `output` must remain empty if `input` is already normalized.
|
||||
@@ -406,10 +468,10 @@ pub(crate) fn normalize_string(
|
||||
let preferred_quote = quote.as_char();
|
||||
let opposite_quote = quote.invert().as_char();
|
||||
|
||||
let mut chars = input.char_indices().peekable();
|
||||
let mut chars = CharIndicesWithOffset::new(input, start_offset).peekable();
|
||||
|
||||
let is_raw = prefix.is_raw_string();
|
||||
let is_fstring = prefix.is_fstring();
|
||||
let is_fstring = !format_fstring && prefix.is_fstring();
|
||||
let mut formatted_value_nesting = 0u32;
|
||||
|
||||
while let Some((index, c)) = chars.next() {
|
||||
@@ -445,13 +507,11 @@ pub(crate) fn normalize_string(
|
||||
// Skip over escaped backslashes
|
||||
chars.next();
|
||||
} else if normalize_hex {
|
||||
// Length of the `\` plus the length of the escape sequence character (`u` | `U` | `x`)
|
||||
let escape_start_len = '\\'.len_utf8() + next.len_utf8();
|
||||
if let Some(normalised) = UnicodeEscape::new(next, !prefix.is_byte())
|
||||
.and_then(|escape| {
|
||||
escape.normalize(&input[index + c.len_utf8() + next.len_utf8()..])
|
||||
})
|
||||
.and_then(|escape| escape.normalize(&input[index + escape_start_len..]))
|
||||
{
|
||||
// Length of the `\` plus the length of the escape sequence character (`u` | `U` | `x`)
|
||||
let escape_start_len = '\\'.len_utf8() + next.len_utf8();
|
||||
let escape_start_offset = index + escape_start_len;
|
||||
if let Cow::Owned(normalised) = &normalised {
|
||||
output.push_str(&input[last_index..escape_start_offset]);
|
||||
@@ -501,6 +561,35 @@ pub(crate) fn normalize_string(
|
||||
normalized
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CharIndicesWithOffset<'str> {
|
||||
chars: std::str::Chars<'str>,
|
||||
next_offset: usize,
|
||||
}
|
||||
|
||||
impl<'str> CharIndicesWithOffset<'str> {
|
||||
fn new(input: &'str str, start_offset: usize) -> Self {
|
||||
Self {
|
||||
chars: input[start_offset..].chars(),
|
||||
next_offset: start_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'str> Iterator for CharIndicesWithOffset<'str> {
|
||||
type Item = (usize, char);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.chars.next().map(|c| {
|
||||
let index = self.next_offset;
|
||||
self.next_offset += c.len_utf8();
|
||||
(index, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for CharIndicesWithOffset<'_> {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum UnicodeEscape {
|
||||
/// A hex escape sequence of either 2 (`\x`), 4 (`\u`) or 8 (`\U`) hex characters.
|
||||
@@ -642,12 +731,14 @@ mod tests {
|
||||
|
||||
let normalized = normalize_string(
|
||||
input,
|
||||
0,
|
||||
StringQuotes {
|
||||
triple: false,
|
||||
quote_char: QuoteChar::Double,
|
||||
},
|
||||
StringPrefix::BYTE,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(r"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", &normalized);
|
||||
|
||||
@@ -3,10 +3,16 @@ use ruff_python_ast::{self as ast, Expr, ExprContext};
|
||||
pub(crate) fn set_context(expr: Expr, ctx: ExprContext) -> Expr {
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, range, .. }) => ast::ExprName { range, id, ctx }.into(),
|
||||
Expr::Tuple(ast::ExprTuple { elts, range, .. }) => ast::ExprTuple {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts,
|
||||
range,
|
||||
parenthesized: is_parenthesized,
|
||||
ctx: _,
|
||||
}) => ast::ExprTuple {
|
||||
elts: elts.into_iter().map(|elt| set_context(elt, ctx)).collect(),
|
||||
range,
|
||||
ctx,
|
||||
parenthesized: is_parenthesized,
|
||||
}
|
||||
.into(),
|
||||
|
||||
|
||||
@@ -1505,4 +1505,20 @@ u"foo" f"bar {baz} really" u"bar" "no"
|
||||
let parse_ast = parse_suite(r#"x = "\N{BACKSPACE}another cool trick""#).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple() {
|
||||
let parse_ast = parse_suite(
|
||||
r#"
|
||||
a,b
|
||||
(a,b)
|
||||
()
|
||||
(a,)
|
||||
((a,b))
|
||||
"#
|
||||
.trim(),
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +483,8 @@ MatchStatement: ast::Stmt = {
|
||||
ast::ExprTuple {
|
||||
elts: vec![subject.into()],
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (tuple_location..tuple_end_location).into()
|
||||
range: (tuple_location..tuple_end_location).into(),
|
||||
parenthesized: false
|
||||
},
|
||||
)),
|
||||
cases,
|
||||
@@ -506,7 +507,8 @@ MatchStatement: ast::Stmt = {
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (tuple_location..tuple_end_location).into()
|
||||
range: (tuple_location..tuple_end_location).into(),
|
||||
parenthesized: false
|
||||
},
|
||||
)),
|
||||
cases,
|
||||
@@ -1573,6 +1575,7 @@ SubscriptList: crate::parser::ParenthesizedExpr = {
|
||||
elts: vec![s1.into()],
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
},
|
||||
<location:@L> <elts:TwoOrMoreSep<Subscript, ",">> ","? <end_location:@R> => {
|
||||
@@ -1581,6 +1584,7 @@ SubscriptList: crate::parser::ParenthesizedExpr = {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
}
|
||||
};
|
||||
@@ -1726,7 +1730,12 @@ Atom<Goal>: crate::parser::ParenthesizedExpr = {
|
||||
}
|
||||
} else {
|
||||
let elts = elts.into_iter().map(ast::Expr::from).collect();
|
||||
ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into()
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into()
|
||||
}
|
||||
},
|
||||
<location:@L> "(" <left:(<OneOrMore<Test<"all">>> ",")?> <mid:NamedOrStarExpr> <right:("," <TestOrStarNamedExpr>)*> <trailing_comma:","?> ")" <end_location:@R> =>? {
|
||||
@@ -1743,13 +1752,19 @@ Atom<Goal>: crate::parser::ParenthesizedExpr = {
|
||||
})
|
||||
} else {
|
||||
let elts = left.into_iter().flatten().chain([mid]).chain(right).map(ast::Expr::from).collect();
|
||||
Ok(ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into())
|
||||
Ok(ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into())
|
||||
}
|
||||
},
|
||||
<location:@L> "(" ")" <end_location:@R> => ast::ExprTuple {
|
||||
elts: Vec::new(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into(),
|
||||
<location:@L> "(" <e:YieldExpr> ")" <end_location:@R> => crate::parser::ParenthesizedExpr {
|
||||
expr: e.into(),
|
||||
@@ -1759,6 +1774,7 @@ Atom<Goal>: crate::parser::ParenthesizedExpr = {
|
||||
elt: Box::new(elt.into()),
|
||||
generators,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into(),
|
||||
"(" <location:@L> "**" <e:Expression<"all">> ")" <end_location:@R> =>? {
|
||||
Err(LexicalError::new(
|
||||
@@ -1852,7 +1868,12 @@ GenericList<Element>: crate::parser::ParenthesizedExpr = {
|
||||
}
|
||||
} else {
|
||||
let elts = elts.into_iter().map(ast::Expr::from).collect();
|
||||
ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into()
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1904,7 +1925,8 @@ FunctionArgument: (Option<(TextSize, TextSize, Option<ast::Identifier>)>, ast::E
|
||||
ast::ExprGeneratorExp {
|
||||
elt: Box::new(elt.into()),
|
||||
generators,
|
||||
range: (location..end_location).into()
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}
|
||||
),
|
||||
None => elt.into(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// auto-generated: "lalrpop 0.20.0"
|
||||
// sha3: 8c85e4bbac54760ed8be03b56a428d76e14d18e6dbde62b424d0b2b5e8e65dbe
|
||||
// sha3: d64ca7ff27121baee9d7a1b4d0f341932391a365fe75f115987b05bf2aaf538e
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ruff_python_ast::{self as ast, Int, IpyEscapeKind};
|
||||
use crate::{
|
||||
@@ -33938,7 +33938,8 @@ fn __action86<
|
||||
ast::ExprTuple {
|
||||
elts: vec![subject.into()],
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (tuple_location..tuple_end_location).into()
|
||||
range: (tuple_location..tuple_end_location).into(),
|
||||
parenthesized: false
|
||||
},
|
||||
)),
|
||||
cases,
|
||||
@@ -33982,7 +33983,8 @@ fn __action87<
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (tuple_location..tuple_end_location).into()
|
||||
range: (tuple_location..tuple_end_location).into(),
|
||||
parenthesized: false
|
||||
},
|
||||
)),
|
||||
cases,
|
||||
@@ -36227,6 +36229,7 @@ fn __action208<
|
||||
elts: vec![s1.into()],
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
@@ -36249,6 +36252,7 @@ fn __action209<
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
@@ -36796,7 +36800,8 @@ fn __action242<
|
||||
ast::ExprGeneratorExp {
|
||||
elt: Box::new(elt.into()),
|
||||
generators,
|
||||
range: (location..end_location).into()
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}
|
||||
),
|
||||
None => elt.into(),
|
||||
@@ -37049,7 +37054,12 @@ fn __action259<
|
||||
}
|
||||
} else {
|
||||
let elts = elts.into_iter().map(ast::Expr::from).collect();
|
||||
ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into()
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37103,7 +37113,12 @@ fn __action262<
|
||||
}
|
||||
} else {
|
||||
let elts = elts.into_iter().map(ast::Expr::from).collect();
|
||||
ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into()
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: false
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41292,7 +41307,12 @@ fn __action553<
|
||||
}
|
||||
} else {
|
||||
let elts = elts.into_iter().map(ast::Expr::from).collect();
|
||||
ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into()
|
||||
ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41327,7 +41347,12 @@ fn __action554<
|
||||
})
|
||||
} else {
|
||||
let elts = left.into_iter().flatten().chain([mid]).chain(right).map(ast::Expr::from).collect();
|
||||
Ok(ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into())
|
||||
Ok(ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41348,6 +41373,7 @@ fn __action555<
|
||||
elts: Vec::new(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into()
|
||||
}
|
||||
|
||||
@@ -41388,6 +41414,7 @@ fn __action557<
|
||||
elt: Box::new(elt.into()),
|
||||
generators,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into()
|
||||
}
|
||||
|
||||
@@ -42025,7 +42052,12 @@ fn __action596<
|
||||
})
|
||||
} else {
|
||||
let elts = left.into_iter().flatten().chain([mid]).chain(right).map(ast::Expr::from).collect();
|
||||
Ok(ast::ExprTuple { elts, ctx: ast::ExprContext::Load, range: (location..end_location).into() }.into())
|
||||
Ok(ast::ExprTuple {
|
||||
elts,
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42046,6 +42078,7 @@ fn __action597<
|
||||
elts: Vec::new(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into()
|
||||
}
|
||||
|
||||
@@ -42086,6 +42119,7 @@ fn __action599<
|
||||
elt: Box::new(elt.into()),
|
||||
generators,
|
||||
range: (location..end_location).into(),
|
||||
parenthesized: true
|
||||
}.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
body: [
|
||||
|
||||
@@ -60,6 +60,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
@@ -65,6 +65,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
ifs: [],
|
||||
|
||||
@@ -45,6 +45,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
@@ -65,6 +65,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
ifs: [],
|
||||
|
||||
@@ -33,6 +33,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Store,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -66,6 +67,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
@@ -58,6 +58,7 @@ expression: parse_ast
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user