Compare commits

..

2 Commits

Author SHA1 Message Date
Micha Reiser
d3e160dcb7 Indent expanded binary expressions 2024-02-14 18:53:11 +01:00
Micha Reiser
003851b54c Beautify 2024-02-14 18:10:38 +01:00
228 changed files with 4480 additions and 5639 deletions

6
.github/CODEOWNERS vendored
View File

@@ -7,9 +7,3 @@
# 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

View File

@@ -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@v4
- uses: actions/upload-artifact@v3
with:
name: ruff
path: target/debug/ruff
@@ -238,7 +238,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
name: Download comparison Ruff binary
id: ruff-target
with:
@@ -250,7 +250,6 @@ jobs:
with:
name: ruff
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- name: Install ruff-ecosystem
@@ -325,13 +324,13 @@ jobs:
run: |
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
name: Upload Results
with:
name: ecosystem-result
@@ -485,7 +484,7 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
name: Download development ruff binary
id: ruff-target
with:

View File

@@ -52,9 +52,9 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload sdist"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: wheels-sdist
name: wheels
path: dist
macos-x86_64:
@@ -80,9 +80,9 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: wheels-macos-x86_64
name: wheels
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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-macos-x86_64
name: binaries
path: |
*.tar.gz
*.sha256
@@ -119,9 +119,9 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: wheels-aarch64-apple-darwin
name: wheels
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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-aarch64-apple-darwin
name: binaries
path: |
*.tar.gz
*.sha256
@@ -170,9 +170,9 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: wheels-${{ matrix.platform.target }}
name: wheels
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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-${{ matrix.platform.target }}
name: binaries
path: |
*.zip
*.sha256
@@ -218,9 +218,9 @@ jobs:
ruff --help
python -m ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: wheels-${{ matrix.target }}
name: wheels
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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-${{ matrix.target }}
name: binaries
path: |
*.tar.gz
*.sha256
@@ -251,12 +251,8 @@ 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
@@ -289,9 +285,9 @@ jobs:
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
ruff --help
- name: "Upload wheels"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: wheels-${{ matrix.platform.target }}
name: wheels
path: dist
- name: "Archive binary"
run: |
@@ -299,9 +295,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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-${{ matrix.platform.target }}
name: binaries
path: |
*.tar.gz
*.sha256
@@ -341,9 +337,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@v4
uses: actions/upload-artifact@v3
with:
name: wheels-${{ matrix.target }}
name: wheels
path: dist
- name: "Archive binary"
run: |
@@ -351,9 +347,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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-${{ matrix.target }}
name: binaries
path: |
*.tar.gz
*.sha256
@@ -398,9 +394,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@v4
uses: actions/upload-artifact@v3
with:
name: wheels-${{ matrix.platform.target }}
name: wheels
path: dist
- name: "Archive binary"
run: |
@@ -408,9 +404,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@v4
uses: actions/upload-artifact@v3
with:
name: binaries-${{ matrix.platform.target }}
name: binaries
path: |
*.tar.gz
*.sha256
@@ -467,11 +463,10 @@ jobs:
# For pypi trusted publishing
id-token: write
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
pattern: wheels-*
name: wheels
path: wheels
merge-multiple: true
- name: Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
@@ -511,11 +506,10 @@ jobs:
# For GitHub release publishing
contents: write
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
pattern: binaries-*
name: binaries
path: binaries
merge-multiple: true
- name: "Publish to GitHub"
uses: softprops/action-gh-release@v1
with:

View File

@@ -1,66 +1,5 @@
# Changelog
## 0.2.2
Highlights include:
- Initial support formatting f-strings (in `--preview`).
- Support for overriding arbitrary configuration options via the CLI through an expanded `--config`
argument (e.g., `--config "lint.isort.combine-as-imports=false"`).
- Significant performance improvements in Ruff's lexer, parser, and lint rules.
### Preview features
- Implement minimal f-string formatting ([#9642](https://github.com/astral-sh/ruff/pull/9642))
- \[`pycodestyle`\] Add blank line(s) rules (`E301`, `E302`, `E303`, `E304`, `E305`, `E306`) ([#9266](https://github.com/astral-sh/ruff/pull/9266))
- \[`refurb`\] Implement `readlines_in_for` (`FURB129`) ([#9880](https://github.com/astral-sh/ruff/pull/9880))
### Rule changes
- \[`ruff`\] Ensure closing parentheses for multiline sequences are always on their own line (`RUF022`, `RUF023`) ([#9793](https://github.com/astral-sh/ruff/pull/9793))
- \[`numpy`\] Add missing deprecation violations (`NPY002`) ([#9862](https://github.com/astral-sh/ruff/pull/9862))
- \[`flake8-bandit`\] Detect `mark_safe` usages in decorators ([#9887](https://github.com/astral-sh/ruff/pull/9887))
- \[`ruff`\] Expand `asyncio-dangling-task` (`RUF006`) to include `new_event_loop` ([#9976](https://github.com/astral-sh/ruff/pull/9976))
- \[`flake8-pyi`\] Ignore 'unused' private type dicts in class scopes ([#9952](https://github.com/astral-sh/ruff/pull/9952))
### Formatter
- Docstring formatting: Preserve tab indentation when using `indent-style=tabs` ([#9915](https://github.com/astral-sh/ruff/pull/9915))
- Disable top-level docstring formatting for notebooks ([#9957](https://github.com/astral-sh/ruff/pull/9957))
- Stabilize quote-style's `preserve` mode ([#9922](https://github.com/astral-sh/ruff/pull/9922))
### CLI
- Allow arbitrary configuration options to be overridden via the CLI ([#9599](https://github.com/astral-sh/ruff/pull/9599))
### Bug fixes
- Make `show-settings` filters directory-agnostic ([#9866](https://github.com/astral-sh/ruff/pull/9866))
- Respect duplicates when rewriting type aliases ([#9905](https://github.com/astral-sh/ruff/pull/9905))
- Respect tuple assignments in typing analyzer ([#9969](https://github.com/astral-sh/ruff/pull/9969))
- Use atomic write when persisting cache ([#9981](https://github.com/astral-sh/ruff/pull/9981))
- Use non-parenthesized range for `DebugText` ([#9953](https://github.com/astral-sh/ruff/pull/9953))
- \[`flake8-simplify`\] Avoid false positive with `async` for loops (`SIM113`) ([#9996](https://github.com/astral-sh/ruff/pull/9996))
- \[`flake8-trio`\] Respect `async with` in `timeout-without-await` ([#9859](https://github.com/astral-sh/ruff/pull/9859))
- \[`perflint`\] Catch a wider range of mutations in `PERF101` ([#9955](https://github.com/astral-sh/ruff/pull/9955))
- \[`pycodestyle`\] Fix `E30X` panics on blank lines with trailing white spaces ([#9907](https://github.com/astral-sh/ruff/pull/9907))
- \[`pydocstyle`\] Allow using `parameters` as a subsection header (`D405`) ([#9894](https://github.com/astral-sh/ruff/pull/9894))
- \[`pydocstyle`\] Fix blank-line docstring rules for module-level docstrings ([#9878](https://github.com/astral-sh/ruff/pull/9878))
- \[`pylint`\] Accept 0.0 and 1.0 as common magic values (`PLR2004`) ([#9964](https://github.com/astral-sh/ruff/pull/9964))
- \[`pylint`\] Avoid suggesting set rewrites for non-hashable types ([#9956](https://github.com/astral-sh/ruff/pull/9956))
- \[`ruff`\] Avoid false negatives with string literals inside of method calls (`RUF027`) ([#9865](https://github.com/astral-sh/ruff/pull/9865))
- \[`ruff`\] Fix panic on with f-string detection (`RUF027`) ([#9990](https://github.com/astral-sh/ruff/pull/9990))
- \[`ruff`\] Ignore builtins when detecting missing f-strings ([#9849](https://github.com/astral-sh/ruff/pull/9849))
### Performance
- Use `memchr` for string lexing ([#9888](https://github.com/astral-sh/ruff/pull/9888))
- Use `memchr` for tab-indentation detection ([#9853](https://github.com/astral-sh/ruff/pull/9853))
- Reduce `Result<Tok, LexicalError>` size by using `Box<str>` instead of `String` ([#9885](https://github.com/astral-sh/ruff/pull/9885))
- Reduce size of `Expr` from 80 to 64 bytes ([#9900](https://github.com/astral-sh/ruff/pull/9900))
- Improve trailing comma rule performance ([#9867](https://github.com/astral-sh/ruff/pull/9867))
- Remove unnecessary string cloning from the parser ([#9884](https://github.com/astral-sh/ruff/pull/9884))
## 0.2.1
This release includes support for range formatting (i.e., the ability to format specific lines

View File

@@ -39,7 +39,7 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
You can also join us on [**Discord**](https://discord.com/invite/astral-sh) to discuss your idea with the
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)

126
Cargo.lock generated
View File

@@ -123,9 +123,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.80"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "argfile"
@@ -217,9 +217,9 @@ checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bstr"
version = "1.9.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
dependencies = [
"memchr",
"regex-automata 0.4.5",
@@ -312,9 +312,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.1"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -322,9 +322,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.1"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
dependencies = [
"anstream",
"anstyle",
@@ -383,7 +383,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -407,9 +407,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b85b056aa0541d1975ebc524149dde72803a5d7352b6aebf9eabc44f9017246"
checksum = "0eb4ab4dcb6554eb4f590fb16f99d3b102ab76f5f56554c9a5340518b32c499b"
dependencies = [
"colored",
"libc",
@@ -418,9 +418,9 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02ae9de916d6315a5129bca2fc7957285f0b9f77a2f6a8734a0a146caee2b0b6"
checksum = "cc07a3d3f7e0c8961d0ffdee149d39b231bafdcdc3d978dc5ad790c615f55f3f"
dependencies = [
"codspeed",
"colored",
@@ -592,7 +592,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -603,7 +603,7 @@ checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77"
dependencies = [
"darling_core",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -914,6 +914,35 @@ 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"
@@ -1048,9 +1077,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.35.1"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2"
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
dependencies = [
"console",
"globset",
@@ -1101,7 +1130,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -1680,7 +1709,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -1931,7 +1960,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -1950,7 +1979,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.2.2"
version = "0.2.1"
dependencies = [
"anyhow",
"argfile",
@@ -2111,7 +2140,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.2.2"
version = "0.2.1"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2183,7 +2212,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2365,7 +2394,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.2.2"
version = "0.2.1"
dependencies = [
"anyhow",
"clap",
@@ -2583,24 +2612,24 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "semver"
version = "1.0.22"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.4"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c1432112bce8b966497ac46519535189a3250a3812cd27a999678a69756f79f"
checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132"
dependencies = [
"js-sys",
"serde",
@@ -2609,13 +2638,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2678,7 +2707,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2788,7 +2817,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2810,9 +2839,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.51"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@@ -2898,7 +2927,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -2909,7 +2938,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
"test-case-core",
]
@@ -2930,7 +2959,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3067,7 +3096,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3233,12 +3262,13 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.9.6"
version = "2.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35"
checksum = "0b52731d03d6bb2fd18289d4028aee361d6c28d44977846793b994b13cdcc64d"
dependencies = [
"base64",
"flate2",
"hootbin",
"log",
"once_cell",
"rustls",
@@ -3286,7 +3316,7 @@ checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3380,7 +3410,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@@ -3414,7 +3444,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3447,7 +3477,7 @@ checksum = "a5211b7550606857312bba1d978a8ec75692eae187becc5e680444fffc5e6f89"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]
@@ -3712,7 +3742,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.48",
]
[[package]]

View File

@@ -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.80" }
anyhow = { version = "1.0.79" }
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.1" }
bstr = { version = "1.9.0" }
cachedir = { version = "0.3.1" }
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
clap = { version = "4.5.1", features = ["derive"] }
clap = { version = "4.4.18", features = ["derive"] }
clap_complete_command = { version = "0.5.1" }
clearscreen = { version = "2.0.0" }
codspeed-criterion-compat = { version = "2.4.0", default-features = false }
codspeed-criterion-compat = { version = "2.3.3", default-features = false }
colored = { version = "2.1.0" }
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.35.1", feature = ["filters", "glob"] }
indicatif ={ version = "0.17.8"}
indoc ={ version = "2.0.4"}
insta = { version = "1.34.0", 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.22" }
serde = { version = "1.0.197", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.4" }
seahash = { version ="4.1.0"}
semver = { version = "1.0.21" }
serde = { version = "1.0.196", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.3" }
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.51" }
tempfile = { version = "3.9.0" }
syn = { version = "2.0.40" }
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.6" }
ureq = { version = "2.9.1" }
url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
walkdir = { version = "2.3.2" }

View File

@@ -8,7 +8,7 @@
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions)
[**Discord**](https://discord.com/invite/astral-sh) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/)
An extremely fast Python linter and code formatter, written in Rust.
@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.2.2
rev: v0.2.1
hooks:
# Run the linter.
- id: ruff
@@ -172,7 +172,7 @@ jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
```
@@ -341,14 +341,14 @@ For a complete enumeration of the supported rules, see [_Rules_](https://docs.as
Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](https://docs.astral.sh/ruff/contributing/).
You can also join us on [**Discord**](https://discord.com/invite/astral-sh).
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Support
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/astral-sh/ruff/issues),
or feel free to [**open a new one**](https://github.com/astral-sh/ruff/issues/new).
You can also ask for help on [**Discord**](https://discord.com/invite/astral-sh).
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Acknowledgements
@@ -378,7 +378,6 @@ 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)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.2.2"
version = "0.2.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -745,34 +745,38 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
}
}
/// Enumeration of various ways in which a --config CLI flag
/// could be invalid
#[derive(Debug)]
enum InvalidConfigFlagReason {
InvalidToml(toml::de::Error),
/// It was valid TOML, but not a valid ruff config file.
/// E.g. the user tried to select a rule that doesn't exist,
/// or tried to enable a setting that doesn't exist
ValidTomlButInvalidRuffSchema(toml::de::Error),
/// It was a valid ruff config file, but the user tried to pass a
/// value for `extend` as part of the config override.
// `extend` is special, because it affects which config files we look at
/// in the first place. We currently only parse --config overrides *after*
/// we've combined them with all the arguments from the various config files
/// that we found, so trying to override `extend` as part of a --config
/// override is forbidden.
ExtendPassedViaConfigFlag,
enum TomlParseFailureKind {
SyntaxError,
UnknownOption,
}
impl InvalidConfigFlagReason {
const fn description(&self) -> &'static str {
match self {
Self::InvalidToml(_) => "The supplied argument is not valid TOML",
Self::ValidTomlButInvalidRuffSchema(_) => {
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 => {
"Could not parse the supplied argument as a `ruff.toml` configuration option"
}
Self::ExtendPassedViaConfigFlag => "Cannot include `extend` in a --config flag value",
}
};
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())
}
}
@@ -823,19 +827,18 @@ impl TypedValueParser for ConfigArgumentParser {
.to_str()
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
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)
}
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,
},
Err(underlying_error) => InvalidConfigFlagReason::InvalidToml(underlying_error),
};
let mut new_error = clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
@@ -850,21 +853,6 @@ 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());
@@ -893,16 +881,12 @@ The path `{value}` does not exist"
));
}
} else if value.contains('=') {
tip.push_str(&format!(
"\n\n{}:\n\n{underlying_error}",
config_parse_error.description()
));
tip.push_str(&format!("\n\n{toml_parse_error}"));
}
let tip = tip.trim_end().to_owned().into();
new_error.insert(
clap::error::ContextKind::Suggested,
clap::error::ContextValue::StyledStrs(vec![tip]),
clap::error::ContextValue::StyledStrs(vec![tip.into()]),
);
Err(new_error)

View File

@@ -7,15 +7,10 @@ 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))
@@ -152,29 +147,28 @@ 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)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
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))
.arg("format")
.arg("--config")
.arg(&ruff_dot_toml)
.arg("--config")
.arg(&ruff2_dot_toml)
.arg("."), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: You cannot specify more than one configuration file on the command line.
tip: remove either `--config=[TMP]/ruff.toml` or `--config=[TMP]/ruff2.toml`.
For more information, try `--help`.
"###);
});
.arg(".")
.output()?;
let stderr = std::str::from_utf8(&cmd.stderr)?;
assert_eq!(stderr, expected_stderr);
Ok(())
}
@@ -183,29 +177,27 @@ fn config_file_and_isolated() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_dot_toml = tempdir.path().join("ruff.toml");
fs::File::create(&ruff_dot_toml)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
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))
.arg("format")
.arg("--config")
.arg(&ruff_dot_toml)
.arg("--isolated")
.arg("."), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: The argument `--config=[TMP]/ruff.toml` cannot be used with `--isolated`
tip: You cannot specify a configuration file and also specify `--isolated`,
as `--isolated` causes ruff to ignore all configuration files.
For more information, try `--help`.
"###);
});
.arg(".")
.output()?;
let stderr = std::str::from_utf8(&cmd.stderr)?;
assert_eq!(stderr, expected_stderr);
Ok(())
}

View File

@@ -595,24 +595,6 @@ 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()?;

View File

@@ -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

View File

@@ -1,8 +1,9 @@
use crate::format_element::PrintMode;
use crate::{GroupId, TextSize};
use std::cell::Cell;
use std::num::NonZeroU8;
use crate::format_element::PrintMode;
use crate::{GroupId, TextSize};
/// A Tag marking the start and end of some content to which some special formatting should be applied.
///
/// Tags always come in pairs of a start and an end tag and the styling defined by this tag
@@ -99,6 +100,10 @@ pub enum Tag {
}
impl Tag {
pub const fn align(count: NonZeroU8) -> Tag {
Tag::StartAlign(Align(count))
}
/// Returns `true` if `self` is any start tag.
pub const fn is_start(&self) -> bool {
matches!(

View File

@@ -78,28 +78,27 @@ 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(
#[cold]
|| {
self.element_slices.pop();
let elements = self.element_slices.last_mut()?;
elements.next()
},
)
elements.next().or_else(|| {
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()?;
slice.as_slice().first().or_else(
#[cold]
|| {
slices
.next()
.and_then(|next_elements| next_elements.as_slice().first())
},
)
match slice.as_slice().first() {
Some(element) => Some(element),
None => {
if let Some(next_elements) = slices.next() {
next_elements.as_slice().first()
} else {
None
}
}
}
}
fn extend_back(&mut self, elements: &'a [FormatElement]) {
@@ -147,30 +146,24 @@ impl<'a, 'print> FitsQueue<'a, 'print> {
impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
fn pop(&mut self) -> Option<&'a FormatElement> {
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
}
},
)
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
}
})
}
fn top_with_interned(&self) -> Option<&'a FormatElement> {
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
}
},
)
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
}
})
}
fn extend_back(&mut self, elements: &'a [FormatElement]) {

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.2.2"
version = "0.2.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -28,11 +28,3 @@ dictionary = {
}
#import os # noqa
# case 1:
# try:
# try: # with comment
# try: print()
# except:
# except Foo:
# except Exception as e: print(e)

View File

@@ -119,16 +119,3 @@ 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)

View File

@@ -193,11 +193,3 @@ def func():
for y in range(5):
g(x, idx)
idx += 1
async def func():
# OK (for loop is async)
idx = 0
async for x in async_gen():
g(x, idx)
idx += 1

View File

@@ -147,9 +147,3 @@ ham[upper : ]
#: E203:1:10
ham[upper :]
#: Okay
ham[lower +1 :, "columnname"]
#: E203:1:13
ham[lower + 1 :, "columnname"]

View File

@@ -1,7 +0,0 @@
import os
os.environ["WORLD_SIZE"] = "1"
os.putenv("CUDA_VISIBLE_DEVICES", "4")
del os.environ["WORLD_SIZE"]
import torch

View File

@@ -14,6 +14,3 @@ class Chassis(RobotModuleTemplate):
" \
\
'''blank line with whitespace
inside a multiline string'''

View File

@@ -1,32 +0,0 @@
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

View File

@@ -17,14 +17,3 @@ 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);

View File

@@ -51,7 +51,3 @@ 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

View File

@@ -33,7 +33,7 @@ bool(b"")
bool(1.0)
int().denominator
# These become literals
# These become string or byte literals
str()
str("foo")
str("""
@@ -53,9 +53,3 @@ 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)

View File

@@ -57,11 +57,6 @@ 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

View File

@@ -68,7 +68,3 @@ def method_calls():
first = "Wendy"
last = "Appleseed"
value.method("{first} {last}") # RUF027
def format_specifiers():
a = 4
b = "{a:b} {a:^5}"

View File

@@ -25,10 +25,6 @@ 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))

View File

@@ -1,2 +0,0 @@
# 测试eval函数,eval()函数用来执行一个字符串表达式并返t表达式的值。另外可以讲字符串转换成列表或元组或字典
a = "{1: 'a', 2: 'b'}"

View File

@@ -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, expr);
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
}
if checker.enabled(Rule::InvalidIndexType) {
ruff::rules::invalid_index_type(checker, subscript);
@@ -134,7 +134,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
elts,
ctx,
range: _,
parenthesized: _,
})
| Expr::List(ast::ExprList {
elts,
@@ -965,9 +964,6 @@ 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(&[
@@ -1455,7 +1451,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
generators,
elt: _,
range: _,
parenthesized: _,
},
) => {
if checker.enabled(Rule::UnnecessaryListIndexLookup) {

View File

@@ -386,10 +386,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
},
) => {
if checker.enabled(Rule::NoClassmethodDecorator) {
pylint::rules::no_classmethod_decorator(checker, stmt);
pylint::rules::no_classmethod_decorator(checker, class_def);
}
if checker.enabled(Rule::NoStaticmethodDecorator) {
pylint::rules::no_staticmethod_decorator(checker, stmt);
pylint::rules::no_staticmethod_decorator(checker, class_def);
}
if checker.enabled(Rule::DjangoNullableModelStringField) {
flake8_django::rules::nullable_model_string_field(checker, body);
@@ -1299,9 +1299,6 @@ 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);
}

View File

@@ -2,16 +2,10 @@ use ruff_python_ast::StringLike;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_bandit, flake8_pyi, ruff};
use crate::rules::{flake8_bandit, flake8_pyi};
/// Run lint rules over a [`StringLike`] syntax nodes.
pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
if checker.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
]) {
ruff::rules::ambiguous_unicode_character_string(checker, string_like);
}
if checker.enabled(Rule::HardcodedBindAllInterfaces) {
flake8_bandit::rules::hardcoded_bind_all_interfaces(checker, string_like);
}

View File

@@ -1,4 +1,3 @@
use ruff_python_ast::StmtFunctionDef;
use ruff_python_semantic::{ScopeKind, SemanticModel};
use crate::rules::flake8_type_checking;
@@ -27,8 +26,6 @@ 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
@@ -74,23 +71,4 @@ 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
}
}
}

View File

@@ -374,9 +374,7 @@ 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())
|| (self.settings.preview.is_enabled()
&& imports::is_os_environ_modification(stmt, self.semantic())))
|| imports::is_sys_path_modification(stmt, self.semantic()))
{
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
}
@@ -584,8 +582,7 @@ where
// Function annotations are always evaluated at runtime, unless future annotations
// are enabled.
let annotation =
AnnotationContext::from_function(function_def, &self.semantic, self.settings);
let runtime_annotation = !self.semantic.future_annotations();
// The first parameter may be a single dispatch.
let mut singledispatch =
@@ -609,18 +606,10 @@ where
if let Some(expr) = &parameter_with_default.parameter.annotation {
if singledispatch {
self.visit_runtime_required_annotation(expr);
} else if runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
match annotation {
AnnotationContext::RuntimeRequired => {
self.visit_runtime_required_annotation(expr);
}
AnnotationContext::RuntimeEvaluated => {
self.visit_runtime_evaluated_annotation(expr);
}
AnnotationContext::TypingOnly => {
self.visit_annotation(expr);
}
}
self.visit_annotation(expr);
};
}
if let Some(expr) = &parameter_with_default.default {
@@ -630,46 +619,28 @@ where
}
if let Some(arg) = &parameters.vararg {
if let Some(expr) = &arg.annotation {
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 runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
}
if let Some(arg) = &parameters.kwarg {
if let Some(expr) = &arg.annotation {
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 runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
}
for expr in returns {
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 runtime_annotation {
self.visit_runtime_evaluated_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
let definition = docstrings::extraction::extract_definition(
@@ -1007,7 +978,6 @@ where
elt,
generators,
range: _,
parenthesized: _,
}) => {
self.visit_generators(generators);
self.visit_expr(elt);
@@ -1328,7 +1298,6 @@ where
elts,
ctx,
range: _,
parenthesized: _,
}) = slice.as_ref()
{
let mut iter = elts.iter();

View File

@@ -6,14 +6,17 @@ use ruff_notebook::CellOffsets;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::Tok;
use ruff_diagnostics::Diagnostic;
use ruff_python_index::Indexer;
use ruff_source_file::Locator;
use crate::directives::TodoComment;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::BlankLinesChecker;
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
flake8_pyi, flake8_quotes, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
@@ -63,15 +66,31 @@ pub(crate) fn check_tokens(
pylint::rules::empty_comments(&mut diagnostics, indexer, locator);
}
if settings
.rules
.enabled(Rule::AmbiguousUnicodeCharacterComment)
{
for range in indexer.comment_ranges() {
ruff::rules::ambiguous_unicode_character_comment(
if settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]) {
let mut state_machine = StateMachine::default();
for &(ref tok, range) in tokens.iter().flatten() {
let is_docstring = state_machine.consume(tok);
let context = match tok {
Tok::String { .. } => {
if is_docstring {
Context::Docstring
} else {
Context::String
}
}
Tok::FStringMiddle { .. } => Context::String,
Tok::Comment(_) => Context::Comment,
_ => continue,
};
ruff::rules::ambiguous_unicode_character(
&mut diagnostics,
locator,
*range,
range,
context,
settings,
);
}

View File

@@ -244,7 +244,6 @@ 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),

View File

@@ -256,6 +256,8 @@ impl Rule {
| Rule::MixedSpacesAndTabs
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
| Rule::AvoidableEscapedQuote
| Rule::BadQuotesDocstring
| Rule::BadQuotesInlineString

View File

@@ -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.*:.*|case\s+.*\s*:.*)$",
r"^(?:elif\s+.*\s*:|else\s*:|try\s*:|finally\s*:|except\s+.*\s*:)$",
// Partial dictionary
r#"^['"]\w+['"]\s*:.+[,{]\s*(#.*)?$"#,
// Multiline assignment
@@ -147,27 +147,6 @@ 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:", &[]));
@@ -176,15 +155,11 @@ 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(

View File

@@ -47,8 +47,7 @@ fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
if char == '#' {
return true;
}
if !char.is_whitespace() {
} else if !char.is_whitespace() {
return false;
}
}

View File

@@ -148,132 +148,4 @@ 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)

View File

@@ -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__" | "__post_init__")
matches!(name, "__setitem__")
}
/// Returns `true` if an argument is allowed to use a boolean trap. To return

View File

@@ -109,7 +109,6 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
elts: elts.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
parenthesized: true,
}
.into()
}

View File

@@ -29,7 +29,7 @@ enum TokenType {
/// Simplified token specialized for the task.
#[derive(Copy, Clone)]
struct Token {
ty: TokenType,
r#type: TokenType,
range: TextRange,
}
@@ -40,13 +40,13 @@ impl Ranged for Token {
}
impl Token {
fn new(ty: TokenType, range: TextRange) -> Self {
Self { ty, range }
fn new(r#type: TokenType, range: TextRange) -> Self {
Self { r#type, range }
}
fn irrelevant() -> Token {
Token {
ty: TokenType::Irrelevant,
r#type: 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 ty = match tok {
let r#type = 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, ty }
Self { range, r#type }
}
}
@@ -102,13 +102,16 @@ enum ContextType {
/// Comma context - described a comma-delimited "situation".
#[derive(Copy, Clone)]
struct Context {
ty: ContextType,
r#type: ContextType,
num_commas: u32,
}
impl Context {
const fn new(ty: ContextType) -> Self {
Self { ty, num_commas: 0 }
const fn new(r#type: ContextType) -> Self {
Self {
r#type,
num_commas: 0,
}
}
fn inc(&mut self) {
@@ -274,7 +277,9 @@ pub(crate) fn trailing_commas(
let mut stack = vec![Context::new(ContextType::No)];
for token in tokens {
if prev.ty == TokenType::NonLogicalNewline && token.ty == TokenType::NonLogicalNewline {
if prev.r#type == TokenType::NonLogicalNewline
&& token.r#type == TokenType::NonLogicalNewline
{
// Collapse consecutive newlines to the first one -- trailing commas are
// added before the first newline.
continue;
@@ -283,18 +288,87 @@ pub(crate) fn trailing_commas(
// Update the comma context stack.
let context = update_context(token, prev, prev_prev, &mut stack);
if let Some(diagnostic) = check_token(token, prev, prev_prev, context, locator) {
// 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(),
)));
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.ty {
let pop_context = match context.r#type {
// Lambda terminated by `:`.
ContextType::LambdaParameters => token.ty == TokenType::Colon,
ContextType::LambdaParameters => token.r#type == TokenType::Colon,
// All others terminated by a closing bracket.
// flake8-commas doesn't verify that it matches the opening...
_ => token.ty == TokenType::ClosingBracket,
_ => token.r#type == TokenType::ClosingBracket,
};
if pop_context && stack.len() > 1 {
stack.pop();
@@ -305,107 +379,21 @@ 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.ty {
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
let new_context = match token.r#type {
TokenType::OpeningBracket => match (prev.r#type, prev_prev.r#type) {
(TokenType::Named, TokenType::Def) => Context::new(ContextType::FunctionParameters),
(TokenType::Named | TokenType::ClosingBracket, _) => {
Context::new(ContextType::CallArguments)
}
_ => Context::new(ContextType::Tuple),
},
TokenType::OpeningSquareBracket => match prev.ty {
TokenType::OpeningSquareBracket => match prev.r#type {
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
Context::new(ContextType::Subscript)
}

View File

@@ -29,20 +29,6 @@ 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(),

View File

@@ -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 {

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_copyright/mod.rs
---

View File

@@ -173,7 +173,6 @@ 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(),

View File

@@ -72,7 +72,6 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Exp
elts,
range: _,
ctx: _,
parenthesized: _,
}) = slice.as_ref()
{
for expr in elts {
@@ -124,7 +123,6 @@ 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,
@@ -150,7 +148,6 @@ 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,

View File

@@ -130,7 +130,6 @@ 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(),
@@ -152,7 +151,6 @@ 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(),

View File

@@ -337,7 +337,6 @@ 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)),
@@ -445,7 +444,6 @@ 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)),

View File

@@ -428,7 +428,6 @@ 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(),
@@ -544,7 +543,6 @@ 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(),
@@ -720,7 +718,7 @@ fn get_short_circuit_edit(
generator.expr(expr)
};
Edit::range_replacement(
if matches!(expr, Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _, parenthesized: _}) if !elts.is_empty())
if matches!(expr, Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _}) if !elts.is_empty())
{
format!("({content})")
} else {

View File

@@ -49,11 +49,6 @@ impl Violation for EnumerateForLoop {
/// SIM113
pub(crate) fn enumerate_for_loop(checker: &mut Checker, for_stmt: &ast::StmtFor) {
// If the loop is async, abort.
if for_stmt.is_async {
return;
}
// If the loop contains a `continue`, abort.
let mut visitor = LoopControlFlowVisitor::default();
visitor.visit_body(&for_stmt.body);

View File

@@ -382,7 +382,6 @@ 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(),

View File

@@ -30,4 +30,31 @@ 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)

View File

@@ -38,7 +38,6 @@ 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"))]
@@ -70,7 +69,6 @@ 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__{}_{}",

View File

@@ -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| {
matches!(token.kind(), TokenKind::Rsqb | TokenKind::Comma)
}) {
} else if iter
.peek()
.is_some_and(|token| token.kind() == TokenKind::Rsqb)
{
// 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(

View File

@@ -17,9 +17,6 @@ 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"
@@ -40,7 +37,6 @@ 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,

View File

@@ -86,33 +86,30 @@ 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::applicable_edit(
Edit::range_deletion(TextRange::new(
indexer
.preceded_by_continuations(line.start(), locator)
.unwrap_or(range.start()),
range.end(),
)),
applicability,
));
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
indexer
.preceded_by_continuations(line.start(), locator)
.unwrap_or(range.start()),
range.end(),
))));
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),
applicability,
// Removing trailing whitespace is not safe inside multiline strings.
if indexer.multiline_ranges().contains_range(range) {
Applicability::Unsafe
} else {
Applicability::Safe
},
));
return Some(diagnostic);
}

View File

@@ -231,8 +231,6 @@ E20.py:149:10: E203 [*] Whitespace before ':'
148 | #: E203:1:10
149 | ham[upper :]
| ^^ E203
150 |
151 | #: Okay
|
= help: Remove whitespace before ':'
@@ -242,21 +240,5 @@ 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"]

View File

@@ -1,12 +0,0 @@
---
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
|

View File

@@ -11,7 +11,7 @@ W293.py:4:1: W293 [*] Blank line contains whitespace
|
= help: Remove whitespace from blank line
Unsafe fix
Safe fix
1 1 | # See: https://github.com/astral-sh/ruff/issues/9323
2 2 | class Chassis(RobotModuleTemplate):
3 3 | """底盘信息推送控制
@@ -48,7 +48,6 @@ W293.py:16:1: W293 [*] Blank line contains whitespace
15 | \
16 |
| ^^^^ W293
17 | '''blank line with whitespace
|
= help: Remove whitespace from blank line
@@ -60,25 +59,5 @@ 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'''

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
---

View File

@@ -87,17 +87,12 @@ impl Violation for IndentWithSpaces {
/// """
/// ```
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent indentation, making the rule redundant.
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
/// [formatter]: https://docs.astral.sh/ruff/formatter/
#[violation]
pub struct UnderIndentation;

View File

@@ -27,16 +27,10 @@ use crate::docstrings::Docstring;
/// """Return the pathname of the KOS root directory."""
/// ```
///
/// ## Formatter compatibility
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent quotes, making the rule redundant.
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [formatter]: https://docs.astral.sh/ruff/formatter/
#[violation]
pub struct TripleSingleQuotes {
expected_quote: Quote,

View File

@@ -171,7 +171,6 @@ 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")

View File

@@ -1,110 +0,0 @@
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
})
})
}

View File

@@ -13,7 +13,6 @@ 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::*;
@@ -99,7 +98,6 @@ 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;

View File

@@ -4,10 +4,9 @@ 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};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;
use crate::fix;
/// ## What it does
/// Checks for the use of a classmethod being made without the decorator.
@@ -87,21 +86,21 @@ enum MethodType {
}
/// PLR0202
pub(crate) fn no_classmethod_decorator(checker: &mut Checker, stmt: &Stmt) {
get_undecorated_methods(checker, stmt, &MethodType::Classmethod);
pub(crate) fn no_classmethod_decorator(checker: &mut Checker, class_def: &ast::StmtClassDef) {
get_undecorated_methods(checker, class_def, &MethodType::Classmethod);
}
/// PLR0203
pub(crate) fn no_staticmethod_decorator(checker: &mut Checker, stmt: &Stmt) {
get_undecorated_methods(checker, stmt, &MethodType::Staticmethod);
pub(crate) fn no_staticmethod_decorator(checker: &mut Checker, class_def: &ast::StmtClassDef) {
get_undecorated_methods(checker, class_def, &MethodType::Staticmethod);
}
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();
fn get_undecorated_methods(
checker: &mut Checker,
class_def: &ast::StmtClassDef,
method_type: &MethodType,
) {
let mut explicit_decorator_calls: HashMap<String, TextRange> = HashMap::default();
let (method_name, diagnostic_type): (&str, DiagnosticKind) = match method_type {
MethodType::Classmethod => ("classmethod", NoClassmethodDecorator.into()),
@@ -132,7 +131,7 @@ fn get_undecorated_methods(checker: &mut Checker, class_stmt: &Stmt, method_type
if let Expr::Name(ast::ExprName { id, .. }) = &arguments.args[0] {
if target_name == *id {
explicit_decorator_calls.insert(id.clone(), stmt);
explicit_decorator_calls.insert(id.clone(), stmt.range());
}
};
}
@@ -152,7 +151,7 @@ fn get_undecorated_methods(checker: &mut Checker, class_stmt: &Stmt, method_type
..
}) = stmt
{
let Some(decorator_call_statement) = explicit_decorator_calls.get(name.as_str()) else {
if !explicit_decorator_calls.contains_key(name.as_str()) {
continue;
};
@@ -178,16 +177,18 @@ fn get_undecorated_methods(checker: &mut Checker, class_stmt: &Stmt, method_type
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(),
),
[fix::edits::delete_stmt(
decorator_call_statement,
Some(class_stmt),
checker.locator(),
checker.indexer(),
[Edit::deletion(
range.start() - TextSize::from(indentation.len() as u32),
range.end(),
)],
));
checker.diagnostics.push(diagnostic);

View File

@@ -305,7 +305,6 @@ 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)),

View File

@@ -9,9 +9,7 @@ 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};
@@ -76,7 +74,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, checker.semantic()))
.any(|value| !is_allowed_value(bool_op.op, value))
{
return;
}
@@ -145,7 +143,6 @@ 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(),
})),
@@ -160,7 +157,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, semantic: &SemanticModel) -> bool {
fn is_allowed_value(bool_op: BoolOp, value: &Expr) -> bool {
let Expr::Compare(ast::ExprCompare {
left,
ops,
@@ -199,16 +196,6 @@ fn is_allowed_value(bool_op: BoolOp, value: &Expr, semantic: &SemanticModel) ->
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
}

View File

@@ -1,43 +0,0 @@
---
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 |

View File

@@ -23,28 +23,9 @@ no_method_decorator.py:9:5: PLR0202 [*] Class method defined without decorator
12 13 |
13 |- pick_colors = classmethod(pick_colors)
14 14 |
15 15 | def pick_one_color(): # [no-staticmethod-decorator]
16 16 | """staticmethod to pick one fruit color"""
15 |+
15 16 | def pick_one_color(): # [no-staticmethod-decorator]
16 17 | """staticmethod to pick one fruit color"""
17 18 | return choice(Fruit.COLORS)
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

View File

@@ -22,27 +22,6 @@ 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 |
21 21 | class Class:
22 22 | def class_method(cls):
20 |+
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 |+

View File

@@ -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, UnaryOp};
use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef};
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
@@ -198,31 +198,15 @@ pub(crate) fn native_literals(
checker.diagnostics.push(diagnostic);
}
Some(arg) => {
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 {
let Some(literal_expr) = arg.as_literal_expr() 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;
};
@@ -237,8 +221,14 @@ 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, literal_type) {
(Some(Expr::Attribute(_)), LiteralType::Int) => format!("({arg_code})"),
let content = match (parent_expr, arg) {
(
Some(Expr::Attribute(_)),
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_),
..
}),
) => format!("({arg_code})"),
_ => arg_code.to_string(),
};

View File

@@ -127,7 +127,6 @@ 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()))
};

View File

@@ -141,7 +141,6 @@ 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()))
};

View File

@@ -127,7 +127,6 @@ 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,

View File

@@ -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 literals
36 | # These become string or byte 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 literals
36 36 | # These become string or byte 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 literals
36 | # These become string or byte 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 literals
36 36 | # These become string or byte 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 literals
36 36 | # These become string or byte literals
37 37 | str()
38 38 | str("foo")
39 |-str("""
@@ -304,8 +304,6 @@ 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
@@ -315,82 +313,5 @@ 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

View File

@@ -347,7 +347,6 @@ 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.

View File

@@ -48,7 +48,6 @@ mod tests {
#[test_case(Rule::DefaultFactoryKwarg, Path::new("RUF026.py"))]
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_0.py"))]
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_1.py"))]
#[test_case(Rule::MissingFStringSyntax, Path::new("RUF027_2.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -4,11 +4,9 @@ use bitflags::bitflags;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::StringLike;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::ruff::rules::confusables::confusable;
use crate::rules::ruff::rules::Context;
@@ -173,59 +171,16 @@ impl Violation for AmbiguousUnicodeCharacterComment {
}
}
/// RUF003
pub(crate) fn ambiguous_unicode_character_comment(
/// RUF001, RUF002, RUF003
pub(crate) fn ambiguous_unicode_character(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
range: TextRange,
settings: &LinterSettings,
) {
let text = locator.slice(range);
ambiguous_unicode_character(diagnostics, text, range, Context::Comment, settings);
}
/// RUF001, RUF002
pub(crate) fn ambiguous_unicode_character_string(checker: &mut Checker, string_like: StringLike) {
let context = if checker.semantic().in_docstring() {
Context::Docstring
} else {
Context::String
};
match string_like {
StringLike::StringLiteral(string_literal) => {
for string in &string_literal.value {
let text = checker.locator().slice(string);
ambiguous_unicode_character(
&mut checker.diagnostics,
text,
string.range(),
context,
checker.settings,
);
}
}
StringLike::FStringLiteral(f_string_literal) => {
let text = checker.locator().slice(f_string_literal);
ambiguous_unicode_character(
&mut checker.diagnostics,
text,
f_string_literal.range(),
context,
checker.settings,
);
}
StringLike::BytesLiteral(_) => (),
}
}
fn ambiguous_unicode_character(
diagnostics: &mut Vec<Diagnostic>,
text: &str,
range: TextRange,
context: Context,
settings: &LinterSettings,
) {
let text = locator.slice(range);
// Most of the time, we don't need to check for ambiguous unicode characters at all.
if text.is_ascii() {
return;

View File

@@ -162,7 +162,6 @@ fn concatenate_expressions(expr: &Expr) -> Option<(Expr, Type)> {
elts: new_elts,
ctx: ExprContext::Load,
range: TextRange::default(),
parenthesized: true,
}
.into(),
};

View File

@@ -70,11 +70,6 @@ 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()));
@@ -82,26 +77,6 @@ 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(
@@ -113,10 +88,8 @@ fn should_be_fstring(
return false;
}
let fstring_expr = format!("f{}", locator.slice(literal));
// Note: Range offsets for `value` are based on `fstring_expr`
let Ok(ast::Expr::FString(ast::ExprFString { value, .. })) = parse_expression(&fstring_expr)
let Ok(ast::Expr::FString(ast::ExprFString { value, .. })) =
parse_expression(&format!("f{}", locator.slice(literal.range())))
else {
return false;
};
@@ -186,7 +159,7 @@ fn should_be_fstring(
has_name = true;
}
if let Some(spec) = &element.format_spec {
let spec = &fstring_expr[spec.range()];
let spec = locator.slice(spec.range());
if FormatSpec::parse(spec).is_err() {
return false;
}

View File

@@ -116,7 +116,6 @@ pub(crate) fn never_union(checker: &mut Checker, expr: &Expr) {
elts,
ctx: _,
range: _,
parenthesized: _,
}) = slice.as_ref()
else {
return;
@@ -158,7 +157,6 @@ 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(),

View File

@@ -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(Copy, Clone, Debug)]
pub(super) enum SequenceKind {
#[derive(Debug)]
pub(super) enum SequenceKind<'a> {
List,
Set,
Tuple { parenthesized: bool },
Tuple(&'a ast::ExprTuple),
}
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) -> (&'static str, &'static str) {
fn surrounding_brackets(&self, source: &str) -> (&'static str, &'static str) {
match self {
Self::List => ("[", "]"),
Self::Set => ("{", "}"),
Self::Tuple { parenthesized } => {
if parenthesized {
Self::Tuple(ast_node) => {
if ast_node.is_parenthesized(source) {
("(", ")")
} 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();
let (opening_paren, closing_paren) = kind.surrounding_brackets(locator.contents());
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

View File

@@ -152,13 +152,9 @@ 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 {
parenthesized: tuple_node.parenthesized,
},
),
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => {
(elts, *range, SequenceKind::Tuple(tuple_node))
}
_ => return,
};
@@ -170,7 +166,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);
}
}
@@ -191,7 +187,7 @@ fn create_fix(
range: TextRange,
elts: &[ast::Expr],
string_items: &[&str],
kind: SequenceKind,
kind: &SequenceKind,
checker: &Checker,
) -> Option<Fix> {
let locator = checker.locator();

View File

@@ -157,9 +157,7 @@ impl<'a> StringLiteralDisplay<'a> {
}
}
ast::Expr::Tuple(tuple_node @ ast::ExprTuple { elts, range, .. }) => {
let display_kind = DisplayKind::Sequence(SequenceKind::Tuple {
parenthesized: tuple_node.parenthesized,
});
let display_kind = DisplayKind::Sequence(SequenceKind::Tuple(tuple_node));
Self {
elts: Cow::Borrowed(elts),
range: *range,
@@ -213,7 +211,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());
@@ -222,7 +220,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,
@@ -244,7 +242,7 @@ impl<'a> StringLiteralDisplay<'a> {
/// Python provides for builtin containers.
#[derive(Debug)]
enum DisplayKind<'a> {
Sequence(SequenceKind),
Sequence(SequenceKind<'a>),
Dict { values: &'a [ast::Expr] },
}

View File

@@ -11,21 +11,14 @@ use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet;
/// ## What it does
/// 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)`
/// Checks for uses of `list(...)[0]` that can be replaced with
/// `next(iter(...))`.
///
/// ## Why is this bad?
/// 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.
/// 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.
///
/// ## Example
/// ```python
@@ -40,16 +33,14 @@ use crate::fix::snippet::SourceCodeSnippet;
/// ```
///
/// ## Fix safety
/// 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:
/// 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:
///
/// 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`.
/// 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`.
///
/// ## References
/// - [Iterators and Iterables in Python: Run Efficient Iterations](https://realpython.com/python-iterators-iterables/#when-to-use-an-iterator-in-python)
@@ -76,37 +67,18 @@ impl AlwaysFixableViolation for UnnecessaryIterableAllocationForFirstElement {
/// RUF015
pub(crate) fn unnecessary_iterable_allocation_for_first_element(
checker: &mut Checker,
expr: &ast::Expr,
subscript: &ast::ExprSubscript,
) {
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 ast::ExprSubscript {
value,
slice,
range,
..
} = subscript;
if !is_head_slice(slice) {
return;
}
let Some(target) = match_iteration_target(value, checker.semantic()) else {
return;

View File

@@ -1,6 +1,5 @@
---
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
|
@@ -384,7 +383,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 | # RUF015 (pop)
60 60 |
RUF015.py:58:1: RUF015 [*] Prefer `next(zip(x, y))` over single element slice
|
@@ -392,8 +391,6 @@ 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))`
@@ -404,41 +401,23 @@ 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 | # RUF015 (pop)
61 61 | list(x).pop(0)
60 60 |
61 61 | def test():
RUF015.py:61:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
RUF015.py:63:5: RUF015 [*] Prefer `next(iter(zip(x, y)))` over single element slice
|
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]
61 | def test():
62 | zip = list # Overwrite the builtin zip
63 | list(zip(x, y))[0]
| ^^^^^^^^^^^^^^^^^^ RUF015
|
= help: Replace with `next(iter(zip(x, y)))`
Unsafe fix
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)))
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)))

View File

@@ -285,8 +285,6 @@ RUF027_0.py:70:18: RUF027 [*] Possible f-string without an `f` prefix
69 | last = "Appleseed"
70 | value.method("{first} {last}") # RUF027
| ^^^^^^^^^^^^^^^^ RUF027
71 |
72 | def format_specifiers():
|
= help: Add `f` prefix
@@ -296,24 +294,5 @@ RUF027_0.py:70:18: RUF027 [*] Possible f-string without an `f` prefix
69 69 | last = "Appleseed"
70 |- value.method("{first} {last}") # RUF027
70 |+ value.method(f"{first} {last}") # RUF027
71 71 |
72 72 | def format_specifiers():
73 73 | a = 4
RUF027_0.py:74:9: RUF027 [*] Possible f-string without an `f` prefix
|
72 | def format_specifiers():
73 | a = 4
74 | b = "{a:b} {a:^5}"
| ^^^^^^^^^^^^^^ RUF027
|
= help: Add `f` prefix
Unsafe fix
71 71 |
72 72 | def format_specifiers():
73 73 | a = 4
74 |- b = "{a:b} {a:^5}"
74 |+ b = f"{a:b} {a:^5}"

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---

View File

@@ -977,7 +977,6 @@ 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(),
@@ -1073,7 +1072,6 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
elts,
ctx: _,
range: _,
parenthesized: _,
}) => Self::Tuple(ExprTuple {
elts: elts.iter().map(Into::into).collect(),
}),

View File

@@ -183,7 +183,6 @@ 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| {
@@ -1424,7 +1423,6 @@ 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(),
@@ -1459,7 +1457,6 @@ 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(),

View File

@@ -2430,7 +2430,6 @@ impl AstNode for ast::ExprGeneratorExp {
elt,
generators,
range: _,
parenthesized: _,
} = self;
visitor.visit_expr(elt);
for comprehension in generators {
@@ -3257,7 +3256,6 @@ impl AstNode for ast::ExprTuple {
elts,
ctx: _,
range: _,
parenthesized: _,
} = self;
for expr in elts {

View File

@@ -8,6 +8,7 @@ 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};
@@ -841,7 +842,6 @@ pub struct ExprGeneratorExp {
pub range: TextRange,
pub elt: Box<Expr>,
pub generators: Vec<Comprehension>,
pub parenthesized: bool,
}
impl From<ExprGeneratorExp> for Expr {
@@ -1796,9 +1796,6 @@ 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 {
@@ -1807,6 +1804,37 @@ 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 {
@@ -3883,7 +3911,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>(), 48);
assert_eq!(std::mem::size_of::<ExprGeneratorExp>(), 40);
assert_eq!(std::mem::size_of::<ExprIfExp>(), 32);
assert_eq!(std::mem::size_of::<ExprIpyEscapeCommand>(), 32);
assert_eq!(std::mem::size_of::<ExprLambda>(), 24);

View File

@@ -441,7 +441,6 @@ 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);
@@ -540,7 +539,6 @@ 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);

View File

@@ -428,7 +428,6 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
elt,
generators,
range: _,
parenthesized: _,
}) => {
for comprehension in generators {
visitor.visit_comprehension(comprehension);
@@ -529,7 +528,6 @@ pub fn walk_expr<V: Transformer + ?Sized>(visitor: &V, expr: &mut Expr) {
elts,
ctx,
range: _,
parenthesized: _,
}) => {
for expr in elts {
visitor.visit_expr(expr);

View File

@@ -970,7 +970,6 @@ impl<'a> Generator<'a> {
Expr::GeneratorExp(ast::ExprGeneratorExp {
elt,
generators,
parenthesized: _,
range: _,
}) => {
self.p("(");
@@ -1038,7 +1037,6 @@ impl<'a> Generator<'a> {
elt,
generators,
range: _,
parenthesized: _,
})],
[],
) = (arguments.args.as_ref(), arguments.keywords.as_ref())

View File

@@ -1,8 +0,0 @@
[
{
"preview": "enabled"
},
{
"preview": "disabled"
}
]

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