Compare commits
65 Commits
v0.4.4
...
dhruv/b018
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb191c496 | ||
|
|
ff2bb6867c | ||
|
|
574843049e | ||
|
|
9e7821f4a6 | ||
|
|
403f0dccd8 | ||
|
|
46fcd19ca6 | ||
|
|
d9ec3d56b0 | ||
|
|
cd87b787d9 | ||
|
|
dd6d411026 | ||
|
|
cfceb437a8 | ||
|
|
48b0660228 | ||
|
|
24899efe50 | ||
|
|
83152fff92 | ||
|
|
43e8147eaf | ||
|
|
42b655b24f | ||
|
|
f67c02c837 | ||
|
|
4436dec1d9 | ||
|
|
27da223e9f | ||
|
|
b3e4d39f64 | ||
|
|
d05347cfcb | ||
|
|
7ac9cabbff | ||
|
|
6963f75a14 | ||
|
|
effe3ad4ef | ||
|
|
bdc15a7cb9 | ||
|
|
da882b6657 | ||
|
|
96f6288622 | ||
|
|
bb1c107afd | ||
|
|
c17193b5f8 | ||
|
|
a33763170e | ||
|
|
025768d303 | ||
|
|
50f14d017e | ||
|
|
aceb182db6 | ||
|
|
6ed2482e27 | ||
|
|
dc5c44ccc4 | ||
|
|
c3c87e86ef | ||
|
|
ca99e9e2f0 | ||
|
|
4b41e4de7f | ||
|
|
0dc130e841 | ||
|
|
10b85a0f07 | ||
|
|
af60d539ab | ||
|
|
b371713591 | ||
|
|
3b0584449d | ||
|
|
6ecb4776de | ||
|
|
be0ccabbaa | ||
|
|
6cec82fff8 | ||
|
|
5ab4cc86c2 | ||
|
|
bc7856e899 | ||
|
|
6a28f3448e | ||
|
|
7c824faa88 | ||
|
|
12da5968a0 | ||
|
|
a747b3f2a1 | ||
|
|
01a0e6cc7e | ||
|
|
a8b06537c7 | ||
|
|
7b8fe25d32 | ||
|
|
a50416a6d7 | ||
|
|
41e53d59ab | ||
|
|
0fc6cf9bee | ||
|
|
d835b3e218 | ||
|
|
d7f093ef9e | ||
|
|
4b330b11c6 | ||
|
|
890cc325d5 | ||
|
|
0726e82342 | ||
|
|
f79c980e17 | ||
|
|
35ba3c91ce | ||
|
|
1f794077ec |
19
.github/workflows/ci.yaml
vendored
19
.github/workflows/ci.yaml
vendored
@@ -167,6 +167,9 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
|
||||
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||
run: |
|
||||
cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
@@ -395,22 +398,16 @@ jobs:
|
||||
name: ecosystem-result
|
||||
path: ecosystem-result
|
||||
|
||||
cargo-udeps:
|
||||
name: "cargo udeps"
|
||||
cargo-shear:
|
||||
name: "cargo shear"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install nightly Rust toolchain"
|
||||
# Only pinned to make caching work, update freely
|
||||
run: rustup toolchain install nightly-2023-10-15
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Install cargo-udeps"
|
||||
uses: taiki-e/install-action@cargo-udeps
|
||||
- name: "Run cargo-udeps"
|
||||
run: cargo +nightly-2023-10-15 udeps
|
||||
- uses: cargo-bins/cargo-binstall@main
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
python-package:
|
||||
name: "python package"
|
||||
|
||||
80
.github/workflows/sync_typeshed.yaml
vendored
Normal file
80
.github/workflows/sync_typeshed.yaml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Sync typeshed
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run on the 1st and the 15th of every month:
|
||||
- cron: "0 0 1,15 * *"
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync typeshed
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
# Don't run the cron job on forks:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout Ruff
|
||||
with:
|
||||
path: ruff
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout typeshed
|
||||
with:
|
||||
repository: python/typeshed
|
||||
path: typeshed
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync typeshed
|
||||
id: sync
|
||||
run: |
|
||||
rm -rf ruff/crates/red_knot/vendor/typeshed
|
||||
mkdir ruff/crates/red_knot/vendor/typeshed
|
||||
cp typeshed/README.md ruff/crates/red_knot/vendor/typeshed
|
||||
cp typeshed/LICENSE ruff/crates/red_knot/vendor/typeshed
|
||||
cp -r typeshed/stdlib ruff/crates/red_knot/vendor/typeshed/stdlib
|
||||
rm -rf ruff/crates/red_knot/vendor/typeshed/stdlib/@tests
|
||||
git -C typeshed rev-parse HEAD > ruff/crates/red_knot/vendor/typeshed/source_commit.txt
|
||||
- name: Commit the changes
|
||||
id: commit
|
||||
if: ${{ steps.sync.outcome == 'success' }}
|
||||
run: |
|
||||
cd ruff
|
||||
git checkout -b typeshedbot/sync-typeshed
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)"
|
||||
- name: Create a PR
|
||||
if: ${{ steps.sync.outcome == 'success' && steps.commit.outcome == 'success' }}
|
||||
run: |
|
||||
cd ruff
|
||||
git push --force origin typeshedbot/sync-typeshed
|
||||
gh pr list --repo $GITHUB_REPOSITORY --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr create --title "Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "internal"
|
||||
|
||||
create-issue-on-failure:
|
||||
name: Create an issue if the typeshed sync failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [sync]
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.sync.result == 'failure' }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
await github.rest.issues.create({
|
||||
owner: "astral-sh",
|
||||
repo: "ruff",
|
||||
title: `Automated typeshed sync failed on ${new Date().toDateString()}`,
|
||||
body: "Runs are listed here: https://github.com/astral-sh/ruff/actions/workflows/sync_typeshed.yaml",
|
||||
})
|
||||
@@ -14,7 +14,7 @@ exclude: |
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.16
|
||||
rev: v0.17
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
@@ -56,7 +56,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.3
|
||||
rev: v0.4.4
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
|
||||
@@ -637,11 +637,11 @@ Otherwise, follow the instructions from the linux section.
|
||||
`cargo dev` is a shortcut for `cargo run --package ruff_dev --bin ruff_dev`. You can run some useful
|
||||
utils with it:
|
||||
|
||||
- `cargo dev print-ast <file>`: Print the AST of a python file using the
|
||||
[RustPython parser](https://github.com/astral-sh/ruff/tree/main/crates/ruff_python_parser) that is
|
||||
mainly used in Ruff. For `if True: pass # comment`, you can see the syntax tree, the byte offsets
|
||||
for start and stop of each node and also how the `:` token, the comment and whitespace are not
|
||||
represented anymore:
|
||||
- `cargo dev print-ast <file>`: Print the AST of a python file using Ruff's
|
||||
[Python parser](https://github.com/astral-sh/ruff/tree/main/crates/ruff_python_parser).
|
||||
For `if True: pass # comment`, you can see the syntax tree, the byte offsets for start and
|
||||
stop of each node and also how the `:` token, the comment and whitespace are not represented
|
||||
anymore:
|
||||
|
||||
```text
|
||||
[
|
||||
|
||||
45
Cargo.lock
generated
45
Cargo.lock
generated
@@ -129,9 +129,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
|
||||
|
||||
[[package]]
|
||||
name = "argfile"
|
||||
@@ -1707,9 +1707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1821,7 +1821,6 @@ dependencies = [
|
||||
"dashmap",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap",
|
||||
"log",
|
||||
"notify",
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
@@ -1829,10 +1828,8 @@ dependencies = [
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"smallvec",
|
||||
"smol_str",
|
||||
"tempfile",
|
||||
"textwrap",
|
||||
@@ -2200,7 +2197,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.5.0",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.12.1",
|
||||
"once_cell",
|
||||
@@ -2348,7 +2344,6 @@ dependencies = [
|
||||
name = "ruff_python_trivia"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"itertools 0.12.1",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
@@ -2560,9 +2555,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.17"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309"
|
||||
checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -2572,9 +2567,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.17"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108"
|
||||
checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2602,9 +2597,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.200"
|
||||
version = "1.0.201"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2622,9 +2617,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.200"
|
||||
version = "1.0.201"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2644,9 +2639,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.116"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2819,9 +2814,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
version = "2.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2909,18 +2904,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.59"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -12,6 +12,28 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
ruff = { path = "crates/ruff" }
|
||||
ruff_cache = { path = "crates/ruff_cache" }
|
||||
ruff_diagnostics = { path = "crates/ruff_diagnostics" }
|
||||
ruff_formatter = { path = "crates/ruff_formatter" }
|
||||
ruff_index = { path = "crates/ruff_index" }
|
||||
ruff_linter = { path = "crates/ruff_linter" }
|
||||
ruff_macros = { path = "crates/ruff_macros" }
|
||||
ruff_notebook = { path = "crates/ruff_notebook" }
|
||||
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
|
||||
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
|
||||
ruff_python_index = { path = "crates/ruff_python_index" }
|
||||
ruff_python_literal = { path = "crates/ruff_python_literal" }
|
||||
ruff_python_parser = { path = "crates/ruff_python_parser" }
|
||||
ruff_python_semantic = { path = "crates/ruff_python_semantic" }
|
||||
ruff_python_stdlib = { path = "crates/ruff_python_stdlib" }
|
||||
ruff_python_trivia = { path = "crates/ruff_python_trivia" }
|
||||
ruff_server = { path = "crates/ruff_server" }
|
||||
ruff_source_file = { path = "crates/ruff_source_file" }
|
||||
ruff_text_size = { path = "crates/ruff_text_size" }
|
||||
ruff_workspace = { path = "crates/ruff_workspace" }
|
||||
|
||||
aho-corasick = { version = "1.1.3" }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { version = "1.0.80" }
|
||||
@@ -37,7 +59,6 @@ drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version = "0.11.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.23" }
|
||||
fs-err = { version = "2.11.0" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
hashbrown = "0.14.3"
|
||||
@@ -130,6 +151,7 @@ char_lit_as_u8 = "allow"
|
||||
collapsible_else_if = "allow"
|
||||
collapsible_if = "allow"
|
||||
implicit_hasher = "allow"
|
||||
map_unwrap_or = "allow"
|
||||
match_same_arms = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
|
||||
@@ -266,6 +266,11 @@ The remaining configuration options can be provided through a catch-all `--confi
|
||||
ruff check --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
|
||||
```
|
||||
|
||||
To opt in to the latest lint rules, formatter style changes, interface updates, and more, enable
|
||||
[preview mode](https://docs.astral.sh/ruff/rules/) by setting `preview = true` in your configuration
|
||||
file or passing `--preview` on the command line. Preview mode enables a collection of unstable
|
||||
features that may change prior to stabilization.
|
||||
|
||||
See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format`
|
||||
for more on the linting and formatting commands, respectively.
|
||||
|
||||
|
||||
@@ -12,33 +12,30 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_index = { path = "../ruff_index" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_index = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
ctrlc = "3.4.4"
|
||||
crossbeam = { workspace = true }
|
||||
ctrlc = { version = "3.4.4" }
|
||||
dashmap = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
log = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
smol_str = "0.2.1"
|
||||
smol_str = { version = "0.2.1" }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tracing-tree = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
textwrap = "0.16.1"
|
||||
textwrap = { version = "0.16.1" }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -6,13 +6,4 @@ The Red Knot crate contains code working towards multifile analysis, type infere
|
||||
|
||||
Red Knot vendors [typeshed](https://github.com/python/typeshed)'s stubs for the standard library. The vendored stubs can be found in `crates/red_knot/vendor/typeshed`. The file `crates/red_knot/vendor/typeshed/source_commit.txt` tells you the typeshed commit that our vendored stdlib stubs currently correspond to.
|
||||
|
||||
Updating the vendored stubs is currently done manually. On a Unix machine, follow the following steps (if you have a typeshed clone in a `typeshed` directory, and a Ruff clone in a `ruff` directory):
|
||||
|
||||
```shell
|
||||
rm -rf ruff/crates/red_knot/vendor/typeshed
|
||||
mkdir ruff/crates/red_knot/vendor/typeshed
|
||||
cp typeshed/README.md ruff/crates/red_knot/vendor/typeshed
|
||||
cp typeshed/LICENSE ruff/crates/red_knot/vendor/typeshed
|
||||
cp -r typeshed/stdlib ruff/crates/red_knot/vendor/typeshed/stdlib
|
||||
git -C typeshed rev-parse HEAD > ruff/crates/red_knot/vendor/typeshed/source_commit.txt
|
||||
```
|
||||
The typeshed stubs are updated every two weeks via an automated PR using the `sync_typeshed.yaml` workflow in the `.github/workflows` directory. This workflow can also be triggered at any time via [workflow dispatch](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow#running-a-workflow).
|
||||
|
||||
@@ -1 +1 @@
|
||||
2d33fe212221a05661c0db5215a91cf3d7b7f072
|
||||
a9d7e861f7a46ae7acd56569326adef302e10f29
|
||||
|
||||
18
crates/red_knot/vendor/typeshed/stdlib/_typeshed/importlib.pyi
vendored
Normal file
18
crates/red_knot/vendor/typeshed/stdlib/_typeshed/importlib.pyi
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Implicit protocols used in importlib.
|
||||
# We intentionally omit deprecated and optional methods.
|
||||
|
||||
from collections.abc import Sequence
|
||||
from importlib.machinery import ModuleSpec
|
||||
from types import ModuleType
|
||||
from typing import Protocol
|
||||
|
||||
__all__ = ["LoaderProtocol", "MetaPathFinderProtocol", "PathEntryFinderProtocol"]
|
||||
|
||||
class LoaderProtocol(Protocol):
|
||||
def load_module(self, fullname: str, /) -> ModuleType: ...
|
||||
|
||||
class MetaPathFinderProtocol(Protocol):
|
||||
def find_spec(self, fullname: str, path: Sequence[str] | None, target: ModuleType | None = ..., /) -> ModuleSpec | None: ...
|
||||
|
||||
class PathEntryFinderProtocol(Protocol):
|
||||
def find_spec(self, fullname: str, target: ModuleType | None = ..., /) -> ModuleSpec | None: ...
|
||||
@@ -31,7 +31,7 @@ from _typeshed import (
|
||||
)
|
||||
from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized
|
||||
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
|
||||
from types import CodeType, TracebackType, _Cell
|
||||
from types import CellType, CodeType, TracebackType
|
||||
|
||||
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} are imported from collections.abc in builtins.pyi
|
||||
from typing import ( # noqa: Y022
|
||||
@@ -951,7 +951,7 @@ class tuple(Sequence[_T_co]):
|
||||
class function:
|
||||
# Make sure this class definition stays roughly in line with `types.FunctionType`
|
||||
@property
|
||||
def __closure__(self) -> tuple[_Cell, ...] | None: ...
|
||||
def __closure__(self) -> tuple[CellType, ...] | None: ...
|
||||
__code__: CodeType
|
||||
__defaults__: tuple[Any, ...] | None
|
||||
__dict__: dict[str, Any]
|
||||
@@ -1333,7 +1333,7 @@ if sys.version_info >= (3, 11):
|
||||
locals: Mapping[str, object] | None = None,
|
||||
/,
|
||||
*,
|
||||
closure: tuple[_Cell, ...] | None = None,
|
||||
closure: tuple[CellType, ...] | None = None,
|
||||
) -> None: ...
|
||||
|
||||
else:
|
||||
@@ -1794,7 +1794,7 @@ def __import__(
|
||||
fromlist: Sequence[str] = (),
|
||||
level: int = 0,
|
||||
) -> types.ModuleType: ...
|
||||
def __build_class__(func: Callable[[], _Cell | Any], name: str, /, *bases: Any, metaclass: Any = ..., **kwds: Any) -> Any: ...
|
||||
def __build_class__(func: Callable[[], CellType | Any], name: str, /, *bases: Any, metaclass: Any = ..., **kwds: Any) -> Any: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import EllipsisType
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import sys
|
||||
from _typeshed import StrOrBytesPath
|
||||
from collections.abc import Iterator, MutableMapping
|
||||
from types import TracebackType
|
||||
from typing import Literal
|
||||
@@ -91,5 +93,10 @@ class _error(Exception): ...
|
||||
|
||||
error: tuple[type[_error], type[OSError]]
|
||||
|
||||
def whichdb(filename: str) -> str | None: ...
|
||||
def open(file: str, flag: _TFlags = "r", mode: int = 0o666) -> _Database: ...
|
||||
if sys.version_info >= (3, 11):
|
||||
def whichdb(filename: StrOrBytesPath) -> str | None: ...
|
||||
def open(file: StrOrBytesPath, flag: _TFlags = "r", mode: int = 0o666) -> _Database: ...
|
||||
|
||||
else:
|
||||
def whichdb(filename: str) -> str | None: ...
|
||||
def open(file: str, flag: _TFlags = "r", mode: int = 0o666) -> _Database: ...
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import sys
|
||||
from _typeshed import StrOrBytesPath
|
||||
from collections.abc import Iterator, MutableMapping
|
||||
from types import TracebackType
|
||||
from typing_extensions import Self, TypeAlias
|
||||
@@ -28,4 +30,8 @@ class _Database(MutableMapping[_KeyType, bytes]):
|
||||
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
||||
) -> None: ...
|
||||
|
||||
def open(file: str, flag: str = "c", mode: int = 0o666) -> _Database: ...
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(file: StrOrBytesPath, flag: str = "c", mode: int = 0o666) -> _Database: ...
|
||||
|
||||
else:
|
||||
def open(file: str, flag: str = "c", mode: int = 0o666) -> _Database: ...
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sys
|
||||
from _typeshed import ReadOnlyBuffer
|
||||
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
|
||||
from types import TracebackType
|
||||
from typing import TypeVar, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
@@ -38,4 +38,7 @@ if sys.platform != "win32":
|
||||
__new__: None # type: ignore[assignment]
|
||||
__init__: None # type: ignore[assignment]
|
||||
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
else:
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sys
|
||||
from _typeshed import ReadOnlyBuffer
|
||||
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
|
||||
from types import TracebackType
|
||||
from typing import TypeVar, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
@@ -34,4 +34,7 @@ if sys.platform != "win32":
|
||||
__new__: None # type: ignore[assignment]
|
||||
__init__: None # type: ignore[assignment]
|
||||
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
else:
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
|
||||
@@ -64,7 +64,7 @@ class SourceLoader(ResourceLoader, ExecutionLoader, metaclass=ABCMeta):
|
||||
|
||||
# The base classes differ starting in 3.10:
|
||||
if sys.version_info >= (3, 10):
|
||||
# Please keep in sync with sys._MetaPathFinder
|
||||
# Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol
|
||||
class MetaPathFinder(metaclass=ABCMeta):
|
||||
if sys.version_info < (3, 12):
|
||||
def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ...
|
||||
@@ -85,7 +85,7 @@ if sys.version_info >= (3, 10):
|
||||
def find_spec(self, fullname: str, target: types.ModuleType | None = ...) -> ModuleSpec | None: ...
|
||||
|
||||
else:
|
||||
# Please keep in sync with sys._MetaPathFinder
|
||||
# Please keep in sync with _typeshed.importlib.MetaPathFinderProtocol
|
||||
class MetaPathFinder(Finder):
|
||||
def find_module(self, fullname: str, path: Sequence[str] | None) -> Loader | None: ...
|
||||
def invalidate_caches(self) -> None: ...
|
||||
|
||||
@@ -3,6 +3,7 @@ import importlib.machinery
|
||||
import sys
|
||||
import types
|
||||
from _typeshed import ReadableBuffer, StrOrBytesPath
|
||||
from _typeshed.importlib import LoaderProtocol
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing_extensions import ParamSpec
|
||||
@@ -23,13 +24,13 @@ def source_from_cache(path: str) -> str: ...
|
||||
def decode_source(source_bytes: ReadableBuffer) -> str: ...
|
||||
def find_spec(name: str, package: str | None = None) -> importlib.machinery.ModuleSpec | None: ...
|
||||
def spec_from_loader(
|
||||
name: str, loader: importlib.abc.Loader | None, *, origin: str | None = None, is_package: bool | None = None
|
||||
name: str, loader: LoaderProtocol | None, *, origin: str | None = None, is_package: bool | None = None
|
||||
) -> importlib.machinery.ModuleSpec | None: ...
|
||||
def spec_from_file_location(
|
||||
name: str,
|
||||
location: StrOrBytesPath | None = None,
|
||||
*,
|
||||
loader: importlib.abc.Loader | None = None,
|
||||
loader: LoaderProtocol | None = None,
|
||||
submodule_search_locations: list[str] | None = ...,
|
||||
) -> importlib.machinery.ModuleSpec | None: ...
|
||||
def module_from_spec(spec: importlib.machinery.ModuleSpec) -> types.ModuleType: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sys
|
||||
from _typeshed import SupportsRead
|
||||
from _typeshed.importlib import LoaderProtocol, MetaPathFinderProtocol, PathEntryFinderProtocol
|
||||
from collections.abc import Callable, Iterable, Iterator
|
||||
from importlib.abc import Loader, MetaPathFinder, PathEntryFinder
|
||||
from typing import IO, Any, NamedTuple, TypeVar
|
||||
from typing_extensions import deprecated
|
||||
|
||||
@@ -23,7 +23,7 @@ if sys.version_info < (3, 12):
|
||||
_PathT = TypeVar("_PathT", bound=Iterable[str])
|
||||
|
||||
class ModuleInfo(NamedTuple):
|
||||
module_finder: MetaPathFinder | PathEntryFinder
|
||||
module_finder: MetaPathFinderProtocol | PathEntryFinderProtocol
|
||||
name: str
|
||||
ispkg: bool
|
||||
|
||||
@@ -37,11 +37,11 @@ if sys.version_info < (3, 12):
|
||||
def __init__(self, fullname: str, file: IO[str], filename: str, etc: tuple[str, str, int]) -> None: ...
|
||||
|
||||
@deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.")
|
||||
def find_loader(fullname: str) -> Loader | None: ...
|
||||
def get_importer(path_item: str) -> PathEntryFinder | None: ...
|
||||
def find_loader(fullname: str) -> LoaderProtocol | None: ...
|
||||
def get_importer(path_item: str) -> PathEntryFinderProtocol | None: ...
|
||||
@deprecated("Use importlib.util.find_spec() instead. Will be removed in Python 3.14.")
|
||||
def get_loader(module_or_name: str) -> Loader | None: ...
|
||||
def iter_importers(fullname: str = "") -> Iterator[MetaPathFinder | PathEntryFinder]: ...
|
||||
def get_loader(module_or_name: str) -> LoaderProtocol | None: ...
|
||||
def iter_importers(fullname: str = "") -> Iterator[MetaPathFinderProtocol | PathEntryFinderProtocol]: ...
|
||||
def iter_modules(path: Iterable[str] | None = None, prefix: str = "") -> Iterator[ModuleInfo]: ...
|
||||
def read_code(stream: SupportsRead[bytes]) -> Any: ... # undocumented
|
||||
def walk_packages(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import sys
|
||||
from _typeshed import StrOrBytesPath
|
||||
from collections.abc import Iterator, MutableMapping
|
||||
from dbm import _TFlags
|
||||
from types import TracebackType
|
||||
@@ -41,6 +43,17 @@ class BsdDbShelf(Shelf[_VT]):
|
||||
def last(self) -> tuple[str, _VT]: ...
|
||||
|
||||
class DbfilenameShelf(Shelf[_VT]):
|
||||
def __init__(self, filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> None: ...
|
||||
if sys.version_info >= (3, 11):
|
||||
def __init__(
|
||||
self, filename: StrOrBytesPath, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False
|
||||
) -> None: ...
|
||||
else:
|
||||
def __init__(self, filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> None: ...
|
||||
|
||||
def open(filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> Shelf[Any]: ...
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(
|
||||
filename: StrOrBytesPath, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False
|
||||
) -> Shelf[Any]: ...
|
||||
|
||||
else:
|
||||
def open(filename: str, flag: _TFlags = "c", protocol: int | None = None, writeback: bool = False) -> Shelf[Any]: ...
|
||||
|
||||
@@ -474,6 +474,13 @@ if sys.version_info >= (3, 12):
|
||||
ETHERTYPE_VLAN as ETHERTYPE_VLAN,
|
||||
)
|
||||
|
||||
if sys.platform == "linux":
|
||||
from _socket import ETH_P_ALL as ETH_P_ALL
|
||||
|
||||
if sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin":
|
||||
# FreeBSD >= 14.0
|
||||
from _socket import PF_DIVERT as PF_DIVERT
|
||||
|
||||
# Re-exported from errno
|
||||
EBADF: int
|
||||
EAGAIN: int
|
||||
@@ -525,6 +532,9 @@ class AddressFamily(IntEnum):
|
||||
AF_BLUETOOTH = 32
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 12):
|
||||
AF_HYPERV = 34
|
||||
if sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin" and sys.version_info >= (3, 12):
|
||||
# FreeBSD >= 14.0
|
||||
AF_DIVERT = 44
|
||||
|
||||
AF_INET = AddressFamily.AF_INET
|
||||
AF_INET6 = AddressFamily.AF_INET6
|
||||
@@ -577,6 +587,9 @@ if sys.platform != "win32" or sys.version_info >= (3, 9):
|
||||
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 12):
|
||||
AF_HYPERV = AddressFamily.AF_HYPERV
|
||||
if sys.platform != "linux" and sys.platform != "win32" and sys.platform != "darwin" and sys.version_info >= (3, 12):
|
||||
# FreeBSD >= 14.0
|
||||
AF_DIVERT = AddressFamily.AF_DIVERT
|
||||
|
||||
class SocketKind(IntEnum):
|
||||
SOCK_STREAM = 1
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import sys
|
||||
from _typeshed import OptExcInfo, ProfileFunction, TraceFunction, structseq
|
||||
from _typeshed.importlib import MetaPathFinderProtocol, PathEntryFinderProtocol
|
||||
from builtins import object as _object
|
||||
from collections.abc import AsyncGenerator, Callable, Sequence
|
||||
from importlib.abc import PathEntryFinder
|
||||
from importlib.machinery import ModuleSpec
|
||||
from io import TextIOWrapper
|
||||
from types import FrameType, ModuleType, TracebackType
|
||||
from typing import Any, Final, Literal, NoReturn, Protocol, TextIO, TypeVar, final
|
||||
@@ -15,10 +14,6 @@ _T = TypeVar("_T")
|
||||
_ExitCode: TypeAlias = str | int | None
|
||||
_OptExcInfo: TypeAlias = OptExcInfo # noqa: Y047 # TODO: obsolete, remove fall 2022 or later
|
||||
|
||||
# Intentionally omits one deprecated and one optional method of `importlib.abc.MetaPathFinder`
|
||||
class _MetaPathFinder(Protocol):
|
||||
def find_spec(self, fullname: str, path: Sequence[str] | None, target: ModuleType | None = ..., /) -> ModuleSpec | None: ...
|
||||
|
||||
# ----- sys variables -----
|
||||
if sys.platform != "win32":
|
||||
abiflags: str
|
||||
@@ -44,13 +39,13 @@ if sys.version_info >= (3, 12):
|
||||
last_exc: BaseException # or undefined.
|
||||
maxsize: int
|
||||
maxunicode: int
|
||||
meta_path: list[_MetaPathFinder]
|
||||
meta_path: list[MetaPathFinderProtocol]
|
||||
modules: dict[str, ModuleType]
|
||||
if sys.version_info >= (3, 10):
|
||||
orig_argv: list[str]
|
||||
path: list[str]
|
||||
path_hooks: list[Callable[[str], PathEntryFinder]]
|
||||
path_importer_cache: dict[str, PathEntryFinder | None]
|
||||
path_hooks: list[Callable[[str], PathEntryFinderProtocol]]
|
||||
path_importer_cache: dict[str, PathEntryFinderProtocol | None]
|
||||
platform: str
|
||||
if sys.version_info >= (3, 9):
|
||||
platlibdir: str
|
||||
|
||||
@@ -374,7 +374,11 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase):
|
||||
def readlines(self, hint: int = ..., /) -> list[AnyStr]: ... # type: ignore[override]
|
||||
def seek(self, offset: int, whence: int = ...) -> int: ...
|
||||
def tell(self) -> int: ...
|
||||
def truncate(self, size: int | None = None) -> None: ... # type: ignore[override]
|
||||
if sys.version_info >= (3, 11):
|
||||
def truncate(self, size: int | None = None) -> int: ...
|
||||
else:
|
||||
def truncate(self, size: int | None = None) -> None: ... # type: ignore[override]
|
||||
|
||||
@overload
|
||||
def write(self: SpooledTemporaryFile[str], s: str) -> int: ...
|
||||
@overload
|
||||
|
||||
29
crates/red_knot/vendor/typeshed/stdlib/types.pyi
vendored
29
crates/red_knot/vendor/typeshed/stdlib/types.pyi
vendored
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
from _typeshed import SupportsKeysAndGetItem
|
||||
from _typeshed.importlib import LoaderProtocol
|
||||
from collections.abc import (
|
||||
AsyncGenerator,
|
||||
Awaitable,
|
||||
@@ -16,7 +17,7 @@ from collections.abc import (
|
||||
from importlib.machinery import ModuleSpec
|
||||
|
||||
# pytype crashes if types.MappingProxyType inherits from collections.abc.Mapping instead of typing.Mapping
|
||||
from typing import Any, ClassVar, Literal, Mapping, Protocol, TypeVar, final, overload # noqa: Y022
|
||||
from typing import Any, ClassVar, Literal, Mapping, TypeVar, final, overload # noqa: Y022
|
||||
from typing_extensions import ParamSpec, Self, TypeVarTuple, deprecated
|
||||
|
||||
__all__ = [
|
||||
@@ -64,18 +65,11 @@ _T2 = TypeVar("_T2")
|
||||
_KT = TypeVar("_KT")
|
||||
_VT_co = TypeVar("_VT_co", covariant=True)
|
||||
|
||||
@final
|
||||
class _Cell:
|
||||
def __new__(cls, contents: object = ..., /) -> Self: ...
|
||||
def __eq__(self, value: object, /) -> bool: ...
|
||||
__hash__: ClassVar[None] # type: ignore[assignment]
|
||||
cell_contents: Any
|
||||
|
||||
# Make sure this class definition stays roughly in line with `builtins.function`
|
||||
@final
|
||||
class FunctionType:
|
||||
@property
|
||||
def __closure__(self) -> tuple[_Cell, ...] | None: ...
|
||||
def __closure__(self) -> tuple[CellType, ...] | None: ...
|
||||
__code__: CodeType
|
||||
__defaults__: tuple[Any, ...] | None
|
||||
__dict__: dict[str, Any]
|
||||
@@ -98,7 +92,7 @@ class FunctionType:
|
||||
globals: dict[str, Any],
|
||||
name: str | None = ...,
|
||||
argdefs: tuple[object, ...] | None = ...,
|
||||
closure: tuple[_Cell, ...] | None = ...,
|
||||
closure: tuple[CellType, ...] | None = ...,
|
||||
) -> Self: ...
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
@overload
|
||||
@@ -318,15 +312,12 @@ class SimpleNamespace:
|
||||
def __setattr__(self, name: str, value: Any, /) -> None: ...
|
||||
def __delattr__(self, name: str, /) -> None: ...
|
||||
|
||||
class _LoaderProtocol(Protocol):
|
||||
def load_module(self, fullname: str, /) -> ModuleType: ...
|
||||
|
||||
class ModuleType:
|
||||
__name__: str
|
||||
__file__: str | None
|
||||
@property
|
||||
def __dict__(self) -> dict[str, Any]: ... # type: ignore[override]
|
||||
__loader__: _LoaderProtocol | None
|
||||
__loader__: LoaderProtocol | None
|
||||
__package__: str | None
|
||||
__path__: MutableSequence[str]
|
||||
__spec__: ModuleSpec | None
|
||||
@@ -336,6 +327,12 @@ class ModuleType:
|
||||
# using `builtins.__import__` or `importlib.import_module` less painful
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
@final
|
||||
class CellType:
|
||||
def __new__(cls, contents: object = ..., /) -> Self: ...
|
||||
__hash__: ClassVar[None] # type: ignore[assignment]
|
||||
cell_contents: Any
|
||||
|
||||
_YieldT_co = TypeVar("_YieldT_co", covariant=True)
|
||||
_SendT_contra = TypeVar("_SendT_contra", contravariant=True)
|
||||
_ReturnT_co = TypeVar("_ReturnT_co", covariant=True)
|
||||
@@ -405,7 +402,7 @@ class CoroutineType(Coroutine[_YieldT_co, _SendT_contra, _ReturnT_co]):
|
||||
@final
|
||||
class MethodType:
|
||||
@property
|
||||
def __closure__(self) -> tuple[_Cell, ...] | None: ... # inherited from the added function
|
||||
def __closure__(self) -> tuple[CellType, ...] | None: ... # inherited from the added function
|
||||
@property
|
||||
def __defaults__(self) -> tuple[Any, ...] | None: ... # inherited from the added function
|
||||
@property
|
||||
@@ -570,8 +567,6 @@ def coroutine(func: Callable[_P, Generator[Any, Any, _R]]) -> Callable[_P, Await
|
||||
@overload
|
||||
def coroutine(func: _Fn) -> _Fn: ...
|
||||
|
||||
CellType = _Cell
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
class GenericAlias:
|
||||
@property
|
||||
|
||||
@@ -8,7 +8,6 @@ import typing_extensions
|
||||
from _collections_abc import dict_items, dict_keys, dict_values
|
||||
from _typeshed import IdentityFunction, ReadableBuffer, SupportsKeysAndGetItem
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
||||
from re import Match as Match, Pattern as Pattern
|
||||
from types import (
|
||||
BuiltinFunctionType,
|
||||
@@ -24,10 +23,10 @@ from types import (
|
||||
)
|
||||
from typing_extensions import Never as _Never, ParamSpec as _ParamSpec
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import UnionType
|
||||
if sys.version_info >= (3, 9):
|
||||
from types import GenericAlias
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import UnionType
|
||||
|
||||
__all__ = [
|
||||
"AbstractSet",
|
||||
@@ -402,8 +401,8 @@ class Reversible(Iterable[_T_co], Protocol[_T_co]):
|
||||
def __reversed__(self) -> Iterator[_T_co]: ...
|
||||
|
||||
_YieldT_co = TypeVar("_YieldT_co", covariant=True)
|
||||
_SendT_contra = TypeVar("_SendT_contra", contravariant=True)
|
||||
_ReturnT_co = TypeVar("_ReturnT_co", covariant=True)
|
||||
_SendT_contra = TypeVar("_SendT_contra", contravariant=True, default=None)
|
||||
_ReturnT_co = TypeVar("_ReturnT_co", covariant=True, default=None)
|
||||
|
||||
class Generator(Iterator[_YieldT_co], Generic[_YieldT_co, _SendT_contra, _ReturnT_co]):
|
||||
def __next__(self) -> _YieldT_co: ...
|
||||
@@ -428,24 +427,28 @@ class Generator(Iterator[_YieldT_co], Generic[_YieldT_co, _SendT_contra, _Return
|
||||
@property
|
||||
def gi_yieldfrom(self) -> Generator[Any, Any, Any] | None: ...
|
||||
|
||||
# NOTE: Technically we would like this to be able to accept a second parameter as well, just
|
||||
# like it's counterpart in contextlib, however `typing._SpecialGenericAlias` enforces the
|
||||
# correct number of arguments at runtime, so we would be hiding runtime errors.
|
||||
@runtime_checkable
|
||||
class ContextManager(AbstractContextManager[_T_co, bool | None], Protocol[_T_co]): ...
|
||||
# NOTE: Prior to Python 3.13 these aliases are lacking the second _ExitT_co parameter
|
||||
if sys.version_info >= (3, 13):
|
||||
from contextlib import AbstractAsyncContextManager as AsyncContextManager, AbstractContextManager as ContextManager
|
||||
else:
|
||||
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
||||
|
||||
# NOTE: Technically we would like this to be able to accept a second parameter as well, just
|
||||
# like it's counterpart in contextlib, however `typing._SpecialGenericAlias` enforces the
|
||||
# correct number of arguments at runtime, so we would be hiding runtime errors.
|
||||
@runtime_checkable
|
||||
class AsyncContextManager(AbstractAsyncContextManager[_T_co, bool | None], Protocol[_T_co]): ...
|
||||
@runtime_checkable
|
||||
class ContextManager(AbstractContextManager[_T_co, bool | None], Protocol[_T_co]): ...
|
||||
|
||||
@runtime_checkable
|
||||
class AsyncContextManager(AbstractAsyncContextManager[_T_co, bool | None], Protocol[_T_co]): ...
|
||||
|
||||
@runtime_checkable
|
||||
class Awaitable(Protocol[_T_co]):
|
||||
@abstractmethod
|
||||
def __await__(self) -> Generator[Any, Any, _T_co]: ...
|
||||
|
||||
class Coroutine(Awaitable[_ReturnT_co], Generic[_YieldT_co, _SendT_contra, _ReturnT_co]):
|
||||
# Non-default variations to accommodate couroutines, and `AwaitableGenerator` having a 4th type parameter.
|
||||
_SendT_contra_nd = TypeVar("_SendT_contra_nd", contravariant=True)
|
||||
_ReturnT_co_nd = TypeVar("_ReturnT_co_nd", covariant=True)
|
||||
|
||||
class Coroutine(Awaitable[_ReturnT_co_nd], Generic[_YieldT_co, _SendT_contra_nd, _ReturnT_co_nd]):
|
||||
__name__: str
|
||||
__qualname__: str
|
||||
@property
|
||||
@@ -457,7 +460,7 @@ class Coroutine(Awaitable[_ReturnT_co], Generic[_YieldT_co, _SendT_contra, _Retu
|
||||
@property
|
||||
def cr_running(self) -> bool: ...
|
||||
@abstractmethod
|
||||
def send(self, value: _SendT_contra, /) -> _YieldT_co: ...
|
||||
def send(self, value: _SendT_contra_nd, /) -> _YieldT_co: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def throw(
|
||||
@@ -473,9 +476,9 @@ class Coroutine(Awaitable[_ReturnT_co], Generic[_YieldT_co, _SendT_contra, _Retu
|
||||
# The parameters correspond to Generator, but the 4th is the original type.
|
||||
@type_check_only
|
||||
class AwaitableGenerator(
|
||||
Awaitable[_ReturnT_co],
|
||||
Generator[_YieldT_co, _SendT_contra, _ReturnT_co],
|
||||
Generic[_YieldT_co, _SendT_contra, _ReturnT_co, _S],
|
||||
Awaitable[_ReturnT_co_nd],
|
||||
Generator[_YieldT_co, _SendT_contra_nd, _ReturnT_co_nd],
|
||||
Generic[_YieldT_co, _SendT_contra_nd, _ReturnT_co_nd, _S],
|
||||
metaclass=ABCMeta,
|
||||
): ...
|
||||
|
||||
|
||||
@@ -13,17 +13,17 @@ readme = "../../README.md"
|
||||
default-run = "ruff"
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics" }
|
||||
ruff_linter = { path = "../ruff_linter", features = ["clap"] }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_server = { path = "../ruff_server" }
|
||||
ruff_source_file = { path = "../ruff_source_file" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_workspace = { path = "../ruff_workspace" }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["clap"] }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
ruff_server = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_workspace = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
argfile = { workspace = true }
|
||||
@@ -60,7 +60,7 @@ wild = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# Enable test rules during development
|
||||
ruff_linter = { path = "../ruff_linter", features = ["clap", "test-rules"] }
|
||||
ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
|
||||
# Avoid writing colored snapshots when running tests from the terminal
|
||||
colored = { workspace = true, features = ["no-color"] }
|
||||
insta = { workspace = true, features = ["filters", "json"] }
|
||||
@@ -68,6 +68,10 @@ insta-cmd = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via macro expansion.
|
||||
ignored = ["chrono"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
|
||||
@@ -111,7 +111,13 @@ pub enum Command {
|
||||
output_format: HelpFormat,
|
||||
},
|
||||
/// List or describe the available configuration options.
|
||||
Config { option: Option<String> },
|
||||
Config {
|
||||
/// Config key to show
|
||||
option: Option<String>,
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
output_format: HelpFormat,
|
||||
},
|
||||
/// List all supported upstream linters.
|
||||
Linter {
|
||||
/// Output format
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use crate::args::HelpFormat;
|
||||
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub(crate) fn config(key: Option<&str>) -> Result<()> {
|
||||
pub(crate) fn config(key: Option<&str>, format: HelpFormat) -> Result<()> {
|
||||
match key {
|
||||
None => print!("{}", Options::metadata()),
|
||||
None => {
|
||||
let metadata = Options::metadata();
|
||||
match format {
|
||||
HelpFormat::Text => {
|
||||
println!("{metadata}");
|
||||
}
|
||||
|
||||
HelpFormat::Json => {
|
||||
println!("{}", &serde_json::to_string_pretty(&metadata)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(key) => match Options::metadata().find(key) {
|
||||
None => {
|
||||
return Err(anyhow!("Unknown option: {key}"));
|
||||
}
|
||||
Some(entry) => {
|
||||
print!("{entry}");
|
||||
}
|
||||
Some(entry) => match format {
|
||||
HelpFormat::Text => {
|
||||
print!("{entry}");
|
||||
}
|
||||
|
||||
HelpFormat::Json => {
|
||||
println!("{}", &serde_json::to_string_pretty(&entry)?);
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -180,8 +180,11 @@ pub fn run(
|
||||
}
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Config { option } => {
|
||||
commands::config::config(option.as_deref())?;
|
||||
Command::Config {
|
||||
option,
|
||||
output_format,
|
||||
} => {
|
||||
commands::config::config(option.as_deref(), output_format)?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Command::Linter { output_format } => {
|
||||
|
||||
55
crates/ruff/tests/config.rs
Normal file
55
crates/ruff/tests/config.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
//! Tests for the `ruff config` subcommand.
|
||||
use std::process::Command;
|
||||
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[test]
|
||||
fn lint_select() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME)).arg("config").arg("lint.select"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
A list of rule codes or prefixes to enable. Prefixes can specify exact
|
||||
rules (like `F841`), entire categories (like `F`), or anything in
|
||||
between.
|
||||
|
||||
When breaking ties between enabled and disabled rules (via `select` and
|
||||
`ignore`, respectively), more specific prefixes override less
|
||||
specific prefixes.
|
||||
|
||||
Default value: ["E4", "E7", "E9", "F"]
|
||||
Type: list[RuleSelector]
|
||||
Example usage:
|
||||
```toml
|
||||
# On top of the defaults (`E4`, E7`, `E9`, and `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
|
||||
select = ["E4", "E7", "E9", "F", "B", "Q"]
|
||||
```
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lint_select_json() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME)).arg("config").arg("lint.select").arg("--output-format").arg("json"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"doc": "A list of rule codes or prefixes to enable. Prefixes can specify exact\nrules (like `F841`), entire categories (like `F`), or anything in\nbetween.\n\nWhen breaking ties between enabled and disabled rules (via `select` and\n`ignore`, respectively), more specific prefixes override less\nspecific prefixes.",
|
||||
"default": "[\"E4\", \"E7\", \"E9\", \"F\"]",
|
||||
"value_type": "list[RuleSelector]",
|
||||
"scope": null,
|
||||
"example": "# On top of the defaults (`E4`, E7`, `E9`, and `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).\nselect = [\"E4\", \"E7\", \"E9\", \"F\", \"B\", \"Q\"]",
|
||||
"deprecated": null
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
@@ -1553,3 +1553,68 @@ def unused(x): # noqa: ANN001, ARG001, D103
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_multiline_comment() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["UP031"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_path = tempdir.path().join("noqa.py");
|
||||
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
print(
|
||||
"""First line
|
||||
second line
|
||||
third line
|
||||
%s"""
|
||||
% name
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.arg(&test_path)
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Added 1 noqa directive.
|
||||
"###);
|
||||
});
|
||||
|
||||
let test_code = std::fs::read_to_string(&test_path).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r###"
|
||||
print(
|
||||
"""First line
|
||||
second line
|
||||
third line
|
||||
%s""" # noqa: UP031
|
||||
% name
|
||||
)
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ serde_json = { workspace = true }
|
||||
url = { workspace = true }
|
||||
ureq = { workspace = true }
|
||||
criterion = { workspace = true, default-features = false }
|
||||
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true}
|
||||
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_linter = { path = "../ruff_linter" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_python_index = { path = "../ruff_python_index" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_linter = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
ruff_python_index = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -10,7 +10,7 @@ use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::{registry::Rule, RuleSelector};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_parser::{lexer, parse_program_tokens, Mode};
|
||||
use ruff_python_parser::{parse_program_tokens, tokenize, Mode};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[global_allocator]
|
||||
@@ -55,7 +55,7 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
|
||||
&case,
|
||||
|b, case| {
|
||||
// Tokenize the source.
|
||||
let tokens: Vec<_> = lexer::lex(case.code(), Mode::Module).collect();
|
||||
let tokens = tokenize(case.code(), Mode::Module);
|
||||
|
||||
// Parse the source.
|
||||
let ast = parse_program_tokens(tokens.clone(), case.code(), false).unwrap();
|
||||
|
||||
@@ -19,7 +19,7 @@ filetime = { workspace = true }
|
||||
seahash = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_macros = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -11,18 +11,18 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ruff = { path = "../ruff" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics" }
|
||||
ruff_formatter = { path = "../ruff_formatter" }
|
||||
ruff_linter = { path = "../ruff_linter", features = ["schemars"] }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "../ruff_python_codegen" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_workspace = { path = "../ruff_workspace", features = ["schemars"] }
|
||||
ruff = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["schemars"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_codegen = { workspace = true }
|
||||
ruff_python_formatter = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_stdlib = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_workspace = { workspace = true, features = ["schemars"] }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help"] }
|
||||
|
||||
@@ -14,7 +14,7 @@ license = { workspace = true }
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@@ -11,9 +11,9 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
drop_bomb = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
@@ -25,6 +25,10 @@ unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
# Used via `CacheKey` macro expansion.
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "ruff_text_size/serde"]
|
||||
schemars = ["dep:schemars", "ruff_text_size/schemars"]
|
||||
|
||||
@@ -14,7 +14,7 @@ license = { workspace = true }
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_macros = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
static_assertions = { workspace = true }
|
||||
|
||||
@@ -13,20 +13,20 @@ license = { workspace = true }
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||
ruff_python_codegen = { path = "../ruff_python_codegen" }
|
||||
ruff_python_index = { path = "../ruff_python_index" }
|
||||
ruff_python_literal = { path = "../ruff_python_literal" }
|
||||
ruff_python_semantic = { path = "../ruff_python_semantic" }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_diagnostics = { workspace = true, features = ["serde"] }
|
||||
ruff_notebook = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["serde"] }
|
||||
ruff_python_codegen = { workspace = true }
|
||||
ruff_python_index = { workspace = true }
|
||||
ruff_python_literal = { workspace = true }
|
||||
ruff_python_semantic = { workspace = true }
|
||||
ruff_python_stdlib = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_source_file = { workspace = true, features = ["serde"] }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
aho-corasick = { workspace = true }
|
||||
annotate-snippets = { workspace = true, features = ["color"] }
|
||||
|
||||
@@ -6,8 +6,8 @@ class Foo2:
|
||||
"""abc"""
|
||||
|
||||
a = 2
|
||||
"str" # Str (no raise)
|
||||
f"{int}" # JoinedStr (no raise)
|
||||
"str" # StringLiteral
|
||||
f"{int}" # FString
|
||||
1j # Number (complex)
|
||||
1 # Number (int)
|
||||
1.0 # Number (float)
|
||||
@@ -34,8 +34,8 @@ def foo1():
|
||||
def foo2():
|
||||
"""my docstring"""
|
||||
a = 2
|
||||
"str" # Str (no raise)
|
||||
f"{int}" # JoinedStr (no raise)
|
||||
"str" # StringLiteral
|
||||
f"{int}" # FString
|
||||
1j # Number (complex)
|
||||
1 # Number (int)
|
||||
1.0 # Number (float)
|
||||
|
||||
93
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B018_attribute_docstring.py
vendored
Normal file
93
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B018_attribute_docstring.py
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# These test cases not only check for `B018` but also verifies that the semantic model
|
||||
# correctly identifies certain strings as attribute docstring. And, by way of not
|
||||
# raising the `B018` violation, it can be verified.
|
||||
|
||||
a: int
|
||||
"a: docstring"
|
||||
|
||||
b = 1
|
||||
"b: docstring" " continue"
|
||||
"b: not a docstring"
|
||||
|
||||
c: int = 1
|
||||
"c: docstring"
|
||||
|
||||
_a: int
|
||||
"_a: docstring"
|
||||
|
||||
if True:
|
||||
d = 1
|
||||
"d: not a docstring"
|
||||
|
||||
(e := 1)
|
||||
"e: not a docstring"
|
||||
|
||||
f = 0
|
||||
f += 1
|
||||
"f: not a docstring"
|
||||
|
||||
g.h = 1
|
||||
"g.h: not a docstring"
|
||||
|
||||
(i) = 1
|
||||
"i: docstring"
|
||||
|
||||
(j): int = 1
|
||||
"j: docstring"
|
||||
|
||||
(k): int
|
||||
"k: docstring"
|
||||
|
||||
l = m = 1
|
||||
"l m: not a docstring"
|
||||
|
||||
n.a = n.b = n.c = 1
|
||||
"n.*: not a docstring"
|
||||
|
||||
(o, p) = (1, 2)
|
||||
"o p: not a docstring"
|
||||
|
||||
[q, r] = [1, 2]
|
||||
"q r: not a docstring"
|
||||
|
||||
*s = 1
|
||||
"s: not a docstring"
|
||||
|
||||
|
||||
class Foo:
|
||||
a = 1
|
||||
"Foo.a: docstring"
|
||||
|
||||
b: int
|
||||
"Foo.b: docstring"
|
||||
"Foo.b: not a docstring"
|
||||
|
||||
c: int = 1
|
||||
"Foo.c: docstring"
|
||||
|
||||
def __init__(self) -> None:
|
||||
# This is actually a docstring but we currently don't detect it.
|
||||
self.x = 1
|
||||
"self.x: not a docstring"
|
||||
|
||||
t = 2
|
||||
"t: not a docstring"
|
||||
|
||||
def random(self):
|
||||
self.y = 2
|
||||
"self.y: not a docstring"
|
||||
|
||||
u = 2
|
||||
"u: not a docstring"
|
||||
|
||||
def add(self, y: int):
|
||||
self.x += y
|
||||
|
||||
|
||||
def function():
|
||||
v = 2
|
||||
"v: not a docstring"
|
||||
|
||||
|
||||
function.a = 1
|
||||
"function.a: not a docstring"
|
||||
@@ -24,3 +24,7 @@ foo(**{},)
|
||||
# Duplicated key names won't be fixed, to avoid syntax errors.
|
||||
abc(**{'a': b}, **{'a': c}) # PIE804
|
||||
abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||
|
||||
# Some values need to be parenthesized.
|
||||
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
|
||||
@@ -43,3 +43,13 @@ from typing_extensions import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass # TCH005
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11368
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
elif test:
|
||||
pass
|
||||
|
||||
@@ -90,3 +90,10 @@ def f():
|
||||
|
||||
def func() -> DataFrame[[DataFrame[_P, _R]], DataFrame[_P, _R]]:
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
from pandas import DataFrame, Series
|
||||
|
||||
def func(self) -> DataFrame | list[Series]:
|
||||
pass
|
||||
|
||||
@@ -63,3 +63,16 @@ if (a and
|
||||
#: Okay
|
||||
def f():
|
||||
return 1
|
||||
|
||||
# Soft keywords
|
||||
|
||||
#: E271
|
||||
type Number = int
|
||||
|
||||
#: E273
|
||||
type Number = int
|
||||
|
||||
#: E275
|
||||
match(foo):
|
||||
case(1):
|
||||
pass
|
||||
|
||||
@@ -46,3 +46,15 @@ regex = '\\\_'
|
||||
|
||||
#: W605:1:7
|
||||
u'foo\ bar'
|
||||
|
||||
#: W605:1:13
|
||||
(
|
||||
"foo \
|
||||
bar \. baz"
|
||||
)
|
||||
|
||||
#: W605:1:6
|
||||
"foo \. bar \t"
|
||||
|
||||
#: W605:1:13
|
||||
"foo \t bar \."
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""__init__.py with __all__
|
||||
"""__init__.py with nonempty __all__
|
||||
|
||||
Unused stdlib and third party imports are unsafe removals
|
||||
|
||||
@@ -33,10 +33,10 @@ from . import aliased as aliased # Ok: is redundant alias
|
||||
from . import exported # Ok: is exported in __all__
|
||||
|
||||
|
||||
# from . import unused # F401: add to __all__
|
||||
from . import unused # F401: add to __all__
|
||||
|
||||
|
||||
# from . import renamed as bees # F401: add to __all__
|
||||
from . import renamed as bees # F401: add to __all__
|
||||
|
||||
|
||||
__all__ = ["argparse", "exported"]
|
||||
11
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/__init__.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""__init__.py with empty __all__
|
||||
"""
|
||||
|
||||
|
||||
from . import unused # F401: add to __all__
|
||||
|
||||
|
||||
from . import renamed as bees # F401: add to __all__
|
||||
|
||||
|
||||
__all__ = []
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/renamed.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/renamed.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/unused.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_26__all_empty/unused.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
11
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/__init__.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""__init__.py with mis-typed __all__
|
||||
"""
|
||||
|
||||
|
||||
from . import unused # F401: recommend add to all w/o fix
|
||||
|
||||
|
||||
from . import renamed as bees # F401: recommend add to all w/o fix
|
||||
|
||||
|
||||
__all__ = None
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/renamed.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/renamed.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/unused.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_27__all_mistyped/unused.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
8
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/__init__.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
"""__init__.py with multiple imports added to all in one edit
|
||||
"""
|
||||
|
||||
|
||||
from . import unused, renamed as bees # F401: add to __all__
|
||||
|
||||
|
||||
__all__ = [];
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/renamed.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/renamed.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/unused.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_28__all_multiple/unused.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
16
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/__init__.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"""__init__.py with __all__ populated by conditional plus-eq
|
||||
|
||||
multiple __all__ so cannot offer a fix to add to them
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from . import unused, exported, renamed as bees
|
||||
|
||||
if sys.version_info > (3, 9):
|
||||
from . import also_exported
|
||||
|
||||
__all__ = ["exported"]
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
__all__ += ["also_exported"]
|
||||
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/exported.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/exported.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/renamed.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/renamed.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/unused.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_29__all_conditional/unused.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -66,3 +66,19 @@ class StudentF(object):
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11358
|
||||
class Foo:
|
||||
__slots__ = ("bar",)
|
||||
|
||||
def __init__(self):
|
||||
self.qux = 2
|
||||
|
||||
@property
|
||||
def qux(self):
|
||||
return self.bar * 2
|
||||
|
||||
@qux.setter
|
||||
def qux(self, value):
|
||||
self.bar = value / 2
|
||||
|
||||
@@ -38,3 +38,12 @@ z = (
|
||||
else
|
||||
y
|
||||
)
|
||||
|
||||
# FURB110
|
||||
z = (
|
||||
x
|
||||
if x
|
||||
else y
|
||||
if y > 0
|
||||
else None
|
||||
)
|
||||
|
||||
@@ -103,7 +103,7 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
# Invalid - nonexistant error code with multibyte character
|
||||
# Invalid - nonexistent error code with multibyte character
|
||||
d = 1 #
noqa: F841, E50
|
||||
e = 1 #
noqa: E50
|
||||
|
||||
|
||||
@@ -1065,13 +1065,17 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyflakes::rules::invalid_print_syntax(checker, left);
|
||||
}
|
||||
}
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op: Operator::Mod,
|
||||
right,
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = left.as_ref() {
|
||||
Expr::BinOp(
|
||||
bin_op @ ast::ExprBinOp {
|
||||
left,
|
||||
op: Operator::Mod,
|
||||
right,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if let Expr::StringLiteral(format_string @ ast::ExprStringLiteral { value, .. }) =
|
||||
left.as_ref()
|
||||
{
|
||||
if checker.any_enabled(&[
|
||||
Rule::PercentFormatInvalidFormat,
|
||||
Rule::PercentFormatExpectedMapping,
|
||||
@@ -1151,10 +1155,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyupgrade::rules::printf_string_formatting(checker, expr, right);
|
||||
}
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::percent(checker, expr);
|
||||
pylint::rules::bad_string_format_character::percent(
|
||||
checker,
|
||||
expr,
|
||||
format_string,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BadStringFormatType) {
|
||||
pylint::rules::bad_string_format_type(checker, expr, right);
|
||||
pylint::rules::bad_string_format_type(checker, bin_op, format_string);
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(checker, expr);
|
||||
|
||||
@@ -2,7 +2,6 @@ use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::types::Node;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_semantic::ScopeKind;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -1098,9 +1097,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::too_many_nested_blocks(checker, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::EmptyTypeCheckingBlock) {
|
||||
if typing::is_type_checking_block(if_, &checker.semantic) {
|
||||
flake8_type_checking::rules::empty_type_checking_block(checker, if_);
|
||||
}
|
||||
flake8_type_checking::rules::empty_type_checking_block(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::IfTuple) {
|
||||
pyflakes::rules::if_tuple(checker, if_);
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_python_ast::StringLike;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bandit, flake8_pyi, flake8_quotes, ruff};
|
||||
use crate::rules::{flake8_bandit, flake8_pyi, flake8_quotes, pycodestyle, ruff};
|
||||
|
||||
/// Run lint rules over a [`StringLike`] syntax nodes.
|
||||
pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
|
||||
@@ -36,4 +36,7 @@ pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::AvoidableEscapedQuote) && checker.settings.flake8_quotes.avoid_escape {
|
||||
flake8_quotes::rules::avoidable_escaped_quote(checker, string_like);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidEscapeSequence) {
|
||||
pycodestyle::rules::invalid_escape_sequence(checker, string_like);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ mod annotation;
|
||||
mod deferred;
|
||||
|
||||
/// State representing whether a docstring is expected or not for the next statement.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum DocstringState {
|
||||
/// The next statement is expected to be a docstring, but not necessarily so.
|
||||
///
|
||||
@@ -92,15 +92,84 @@ enum DocstringState {
|
||||
/// For `Foo`, the state is expected when the checker is visiting the class
|
||||
/// body but isn't going to be present. While, for `bar` function, the docstring
|
||||
/// is expected and present.
|
||||
#[default]
|
||||
Expected,
|
||||
Expected(ExpectedDocstringKind),
|
||||
Other,
|
||||
}
|
||||
|
||||
impl Default for DocstringState {
|
||||
/// Returns the default docstring state which is to expect a module-level docstring.
|
||||
fn default() -> Self {
|
||||
Self::Expected(ExpectedDocstringKind::Module)
|
||||
}
|
||||
}
|
||||
|
||||
impl DocstringState {
|
||||
/// Returns `true` if the next statement is expected to be a docstring.
|
||||
const fn is_expected(self) -> bool {
|
||||
matches!(self, DocstringState::Expected)
|
||||
/// Returns the docstring kind if the state is expecting a docstring.
|
||||
const fn expected_kind(self) -> Option<ExpectedDocstringKind> {
|
||||
match self {
|
||||
DocstringState::Expected(kind) => Some(kind),
|
||||
DocstringState::Other => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of an expected docstring.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum ExpectedDocstringKind {
|
||||
/// A module-level docstring.
|
||||
///
|
||||
/// For example,
|
||||
/// ```python
|
||||
/// """This is a module-level docstring."""
|
||||
///
|
||||
/// a = 1
|
||||
/// ```
|
||||
Module,
|
||||
|
||||
/// A class-level docstring.
|
||||
///
|
||||
/// For example,
|
||||
/// ```python
|
||||
/// class Foo:
|
||||
/// """This is the docstring for `Foo` class."""
|
||||
///
|
||||
/// def __init__(self) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
Class,
|
||||
|
||||
/// A function-level docstring.
|
||||
///
|
||||
/// For example,
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// """This is the docstring for `foo` function."""
|
||||
/// pass
|
||||
/// ```
|
||||
Function,
|
||||
|
||||
/// An attribute-level docstring.
|
||||
///
|
||||
/// For example,
|
||||
/// ```python
|
||||
/// a = 1
|
||||
/// """This is the docstring for `a` variable."""
|
||||
///
|
||||
///
|
||||
/// class Foo:
|
||||
/// b = 1
|
||||
/// """This is the docstring for `Foo.b` class variable."""
|
||||
/// ```
|
||||
Attribute,
|
||||
}
|
||||
|
||||
impl ExpectedDocstringKind {
|
||||
/// Returns the semantic model flag that represents the current docstring state.
|
||||
const fn as_flag(self) -> SemanticModelFlags {
|
||||
match self {
|
||||
ExpectedDocstringKind::Attribute => SemanticModelFlags::ATTRIBUTE_DOCSTRING,
|
||||
_ => SemanticModelFlags::PEP_257_DOCSTRING,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,9 +452,9 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
|
||||
// Update the semantic model if it is in a docstring. This should be done after the
|
||||
// flags snapshot to ensure that it gets reset once the statement is analyzed.
|
||||
if self.docstring_state.is_expected() {
|
||||
if let Some(kind) = self.docstring_state.expected_kind() {
|
||||
if is_docstring_stmt(stmt) {
|
||||
self.semantic.flags |= SemanticModelFlags::DOCSTRING;
|
||||
self.semantic.flags |= kind.as_flag();
|
||||
}
|
||||
// Reset the state irrespective of whether the statement is a docstring or not.
|
||||
self.docstring_state = DocstringState::Other;
|
||||
@@ -709,7 +778,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
|
||||
// Set the docstring state before visiting the class body.
|
||||
self.docstring_state = DocstringState::Expected;
|
||||
self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Class);
|
||||
self.visit_body(body);
|
||||
|
||||
let scope_id = self.semantic.scope_id;
|
||||
@@ -874,6 +943,24 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
};
|
||||
|
||||
if self.semantic().at_top_level() || self.semantic().current_scope().kind.is_class() {
|
||||
match stmt {
|
||||
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
|
||||
if let [Expr::Name(_)] = targets.as_slice() {
|
||||
self.docstring_state =
|
||||
DocstringState::Expected(ExpectedDocstringKind::Attribute);
|
||||
}
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
|
||||
if target.is_name_expr() {
|
||||
self.docstring_state =
|
||||
DocstringState::Expected(ExpectedDocstringKind::Attribute);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Clean-up
|
||||
|
||||
// Step 4: Analysis
|
||||
@@ -2122,7 +2209,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.visit_parameters(parameters);
|
||||
// Set the docstring state before visiting the function body.
|
||||
self.docstring_state = DocstringState::Expected;
|
||||
self.docstring_state = DocstringState::Expected(ExpectedDocstringKind::Function);
|
||||
self.visit_body(body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ use std::path::Path;
|
||||
use ruff_notebook::CellOffsets;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::directives::TodoComment;
|
||||
use crate::linter::TokenSource;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::rules::BlankLinesChecker;
|
||||
use crate::rules::{
|
||||
@@ -22,7 +22,7 @@ use crate::settings::LinterSettings;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_tokens(
|
||||
tokens: &[LexResult],
|
||||
tokens: &TokenSource,
|
||||
path: &Path,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
@@ -42,7 +42,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::BlankLinesBeforeNestedDefinition,
|
||||
]) {
|
||||
BlankLinesChecker::new(locator, stylist, settings, source_type, cell_offsets)
|
||||
.check_lines(tokens, &mut diagnostics);
|
||||
.check_lines(tokens.kinds(), &mut diagnostics);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::BlanketTypeIgnore) {
|
||||
@@ -75,18 +75,6 @@ pub(crate) fn check_tokens(
|
||||
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::InvalidEscapeSequence) {
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
pycodestyle::rules::invalid_escape_sequence(
|
||||
&mut diagnostics,
|
||||
locator,
|
||||
indexer,
|
||||
tok,
|
||||
*range,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::TabIndentation) {
|
||||
pycodestyle::rules::tab_indentation(&mut diagnostics, locator, indexer);
|
||||
}
|
||||
@@ -98,8 +86,8 @@ pub(crate) fn check_tokens(
|
||||
Rule::InvalidCharacterNul,
|
||||
Rule::InvalidCharacterZeroWidthSpace,
|
||||
]) {
|
||||
for (tok, range) in tokens.iter().flatten() {
|
||||
pylint::rules::invalid_string_characters(&mut diagnostics, tok, *range, locator);
|
||||
for (token, range) in tokens.kinds() {
|
||||
pylint::rules::invalid_string_characters(&mut diagnostics, token, range, locator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +98,7 @@ pub(crate) fn check_tokens(
|
||||
]) {
|
||||
pycodestyle::rules::compound_statements(
|
||||
&mut diagnostics,
|
||||
tokens,
|
||||
tokens.kinds(),
|
||||
locator,
|
||||
indexer,
|
||||
source_type,
|
||||
@@ -124,7 +112,7 @@ pub(crate) fn check_tokens(
|
||||
]) {
|
||||
flake8_implicit_str_concat::rules::implicit(
|
||||
&mut diagnostics,
|
||||
tokens,
|
||||
tokens.kinds(),
|
||||
settings,
|
||||
locator,
|
||||
indexer,
|
||||
@@ -136,11 +124,11 @@ pub(crate) fn check_tokens(
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
]) {
|
||||
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, indexer);
|
||||
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens.kinds(), locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::ExtraneousParentheses) {
|
||||
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator);
|
||||
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens.kinds(), locator);
|
||||
}
|
||||
|
||||
if source_type.is_stub() && settings.rules.enabled(Rule::TypeCommentInStub) {
|
||||
@@ -184,7 +172,7 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::TooManyNewlinesAtEndOfFile) {
|
||||
pycodestyle::rules::too_many_newlines_at_end_of_file(&mut diagnostics, tokens);
|
||||
pycodestyle::rules::too_many_newlines_at_end_of_file(&mut diagnostics, tokens.kinds());
|
||||
}
|
||||
|
||||
diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.kind.rule()));
|
||||
|
||||
@@ -131,7 +131,7 @@ fn extract_noqa_line_for(lxr: &[LexResult], locator: &Locator, indexer: &Indexer
|
||||
|
||||
// For multi-line strings, we expect `noqa` directives on the last line of the
|
||||
// string.
|
||||
Tok::String { kind, .. } if kind.is_triple_quoted() => {
|
||||
Tok::String { flags, .. } if flags.is_triple_quoted() => {
|
||||
if locator.contains_line_break(*range) {
|
||||
string_mappings.push(TextRange::new(
|
||||
locator.line_start(range.start()),
|
||||
|
||||
@@ -4,27 +4,26 @@
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use ruff_python_ast::{self as ast, Stmt, Suite};
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_parser::{TokenKind, TokenKindIter};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||
use ruff_source_file::{Locator, UniversalNewlineIterator};
|
||||
|
||||
/// Extract doc lines (standalone comments) from a token sequence.
|
||||
pub(crate) fn doc_lines_from_tokens(lxr: &[LexResult]) -> DocLines {
|
||||
DocLines::new(lxr)
|
||||
pub(crate) fn doc_lines_from_tokens(tokens: TokenKindIter) -> DocLines {
|
||||
DocLines::new(tokens)
|
||||
}
|
||||
|
||||
pub(crate) struct DocLines<'a> {
|
||||
inner: std::iter::Flatten<core::slice::Iter<'a, LexResult>>,
|
||||
inner: TokenKindIter<'a>,
|
||||
prev: TextSize,
|
||||
}
|
||||
|
||||
impl<'a> DocLines<'a> {
|
||||
fn new(lxr: &'a [LexResult]) -> Self {
|
||||
fn new(tokens: TokenKindIter<'a>) -> Self {
|
||||
Self {
|
||||
inner: lxr.iter().flatten(),
|
||||
inner: tokens,
|
||||
prev: TextSize::default(),
|
||||
}
|
||||
}
|
||||
@@ -39,15 +38,15 @@ impl Iterator for DocLines<'_> {
|
||||
let (tok, range) = self.inner.next()?;
|
||||
|
||||
match tok {
|
||||
Tok::Comment(..) => {
|
||||
TokenKind::Comment => {
|
||||
if at_start_of_line {
|
||||
break Some(range.start());
|
||||
}
|
||||
}
|
||||
Tok::Newline | Tok::NonLogicalNewline => {
|
||||
TokenKind::Newline | TokenKind::NonLogicalNewline => {
|
||||
at_start_of_line = true;
|
||||
}
|
||||
Tok::Indent | Tok::Dedent => {
|
||||
TokenKind::Indent | TokenKind::Dedent => {
|
||||
// ignore
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
//! Interface for generating fix edits from higher-level actions (e.g., "remove an argument").
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Stmt};
|
||||
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
@@ -124,7 +126,7 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
|
||||
/// Edits to make the specified imports explicit, e.g. change `import x` to `import x as x`.
|
||||
pub(crate) fn make_redundant_alias<'a>(
|
||||
member_names: impl Iterator<Item = &'a str>,
|
||||
member_names: impl Iterator<Item = Cow<'a, str>>,
|
||||
stmt: &Stmt,
|
||||
) -> Vec<Edit> {
|
||||
let aliases = match stmt {
|
||||
@@ -144,6 +146,53 @@ pub(crate) fn make_redundant_alias<'a>(
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Fix to add the specified imports to the `__all__` export list.
|
||||
pub(crate) fn add_to_dunder_all<'a>(
|
||||
names: impl Iterator<Item = &'a str>,
|
||||
expr: &Expr,
|
||||
stylist: &Stylist,
|
||||
) -> Vec<Edit> {
|
||||
let (insertion_point, export_prefix_length) = match expr {
|
||||
Expr::List(ExprList { elts, range, .. }) => (
|
||||
elts.last()
|
||||
.map_or(range.end() - "]".text_len(), Ranged::end),
|
||||
elts.len(),
|
||||
),
|
||||
Expr::Tuple(tup) if tup.parenthesized => (
|
||||
tup.elts
|
||||
.last()
|
||||
.map_or(tup.end() - ")".text_len(), Ranged::end),
|
||||
tup.elts.len(),
|
||||
),
|
||||
Expr::Tuple(tup) if !tup.parenthesized => (
|
||||
tup.elts
|
||||
.last()
|
||||
.expect("unparenthesized empty tuple is not possible")
|
||||
.range()
|
||||
.end(),
|
||||
tup.elts.len(),
|
||||
),
|
||||
_ => {
|
||||
// we don't know how to insert into this expression
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
let quote = stylist.quote();
|
||||
let mut edits: Vec<_> = names
|
||||
.enumerate()
|
||||
.map(|(offset, name)| match export_prefix_length + offset {
|
||||
0 => Edit::insertion(format!("{quote}{name}{quote}"), insertion_point),
|
||||
_ => Edit::insertion(format!(", {quote}{name}{quote}"), insertion_point),
|
||||
})
|
||||
.collect();
|
||||
if let Expr::Tuple(tup) = expr {
|
||||
if tup.parenthesized && export_prefix_length + edits.len() == 1 {
|
||||
edits.push(Edit::insertion(",".to_string(), insertion_point));
|
||||
}
|
||||
}
|
||||
edits
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Parentheses {
|
||||
/// Remove parentheses, if the removed argument is the only argument left.
|
||||
@@ -477,14 +526,20 @@ fn all_lines_fit(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::borrow::Cow;
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{lexer, parse_expression, parse_suite, Mode};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::fix::edits::{make_redundant_alias, next_stmt_break, trailing_semicolon};
|
||||
use crate::fix::apply_fixes;
|
||||
use crate::fix::edits::{
|
||||
add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
@@ -562,7 +617,7 @@ x = 1 \
|
||||
let program = parse_suite(contents).unwrap();
|
||||
let stmt = program.first().unwrap();
|
||||
assert_eq!(
|
||||
make_redundant_alias(["x"].into_iter(), stmt),
|
||||
make_redundant_alias(["x"].into_iter().map(Cow::from), stmt),
|
||||
vec![Edit::range_replacement(
|
||||
String::from("x as x"),
|
||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||
@@ -570,7 +625,7 @@ x = 1 \
|
||||
"make just one item redundant"
|
||||
);
|
||||
assert_eq!(
|
||||
make_redundant_alias(vec!["x", "y"].into_iter(), stmt),
|
||||
make_redundant_alias(vec!["x", "y"].into_iter().map(Cow::from), stmt),
|
||||
vec![Edit::range_replacement(
|
||||
String::from("x as x"),
|
||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||
@@ -578,7 +633,7 @@ x = 1 \
|
||||
"the second item is already a redundant alias"
|
||||
);
|
||||
assert_eq!(
|
||||
make_redundant_alias(vec!["x", "z"].into_iter(), stmt),
|
||||
make_redundant_alias(vec!["x", "z"].into_iter().map(Cow::from), stmt),
|
||||
vec![Edit::range_replacement(
|
||||
String::from("x as x"),
|
||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||
@@ -586,4 +641,47 @@ x = 1 \
|
||||
"the third item is already aliased to something else"
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case("()", &["x", "y"], r#"("x", "y")"# ; "2 into empty tuple")]
|
||||
#[test_case("()", &["x"], r#"("x",)"# ; "1 into empty tuple adding a trailing comma")]
|
||||
#[test_case("[]", &["x", "y"], r#"["x", "y"]"# ; "2 into empty list")]
|
||||
#[test_case("[]", &["x"], r#"["x"]"# ; "1 into empty list")]
|
||||
#[test_case(r#""a", "b""#, &["x", "y"], r#""a", "b", "x", "y""# ; "2 into unparenthesized tuple")]
|
||||
#[test_case(r#""a", "b""#, &["x"], r#""a", "b", "x""# ; "1 into unparenthesized tuple")]
|
||||
#[test_case(r#""a", "b","#, &["x", "y"], r#""a", "b", "x", "y","# ; "2 into unparenthesized tuple w/trailing comma")]
|
||||
#[test_case(r#""a", "b","#, &["x"], r#""a", "b", "x","# ; "1 into unparenthesized tuple w/trailing comma")]
|
||||
#[test_case(r#"("a", "b")"#, &["x", "y"], r#"("a", "b", "x", "y")"# ; "2 into nonempty tuple")]
|
||||
#[test_case(r#"("a", "b")"#, &["x"], r#"("a", "b", "x")"# ; "1 into nonempty tuple")]
|
||||
#[test_case(r#"("a", "b",)"#, &["x", "y"], r#"("a", "b", "x", "y",)"# ; "2 into nonempty tuple w/trailing comma")]
|
||||
#[test_case(r#"("a", "b",)"#, &["x"], r#"("a", "b", "x",)"# ; "1 into nonempty tuple w/trailing comma")]
|
||||
#[test_case(r#"["a", "b",]"#, &["x", "y"], r#"["a", "b", "x", "y",]"# ; "2 into nonempty list w/trailing comma")]
|
||||
#[test_case(r#"["a", "b",]"#, &["x"], r#"["a", "b", "x",]"# ; "1 into nonempty list w/trailing comma")]
|
||||
#[test_case(r#"["a", "b"]"#, &["x", "y"], r#"["a", "b", "x", "y"]"# ; "2 into nonempty list")]
|
||||
#[test_case(r#"["a", "b"]"#, &["x"], r#"["a", "b", "x"]"# ; "1 into nonempty list")]
|
||||
fn add_to_dunder_all_test(raw: &str, names: &[&str], expect: &str) -> Result<()> {
|
||||
let locator = Locator::new(raw);
|
||||
let edits = {
|
||||
let expr = parse_expression(raw)?;
|
||||
let stylist = Stylist::from_tokens(
|
||||
&lexer::lex(raw, Mode::Expression).collect::<Vec<_>>(),
|
||||
&locator,
|
||||
);
|
||||
// SUT
|
||||
add_to_dunder_all(names.iter().copied(), &expr, &stylist)
|
||||
};
|
||||
let diag = {
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
let mut iter = edits.into_iter();
|
||||
Diagnostic::new(
|
||||
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
|
||||
TextRange::default(),
|
||||
)
|
||||
.with_fix(Fix::safe_edits(
|
||||
iter.next().ok_or(anyhow!("expected edits nonempty"))?,
|
||||
iter,
|
||||
))
|
||||
};
|
||||
assert_eq!(apply_fixes([diag].iter(), &locator).code, expect);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +321,6 @@ mod tests {
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{parse_suite, Mode};
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
use ruff_text_size::TextSize;
|
||||
@@ -332,7 +331,7 @@ mod tests {
|
||||
fn start_of_file() -> Result<()> {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = parse_suite(contents)?;
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let tokens = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Ok(Insertion::start_of_file(&program, &locator, &stylist))
|
||||
@@ -443,7 +442,7 @@ x = 1
|
||||
#[test]
|
||||
fn start_of_block() {
|
||||
fn insert(contents: &str, offset: TextSize) -> Insertion {
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let tokens = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Insertion::start_of_block(offset, &locator, &stylist, PySourceType::default())
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//!
|
||||
//! [Ruff]: https://github.com/astral-sh/ruff
|
||||
|
||||
pub use noqa::generate_noqa_edits;
|
||||
#[cfg(feature = "clap")]
|
||||
pub use registry::clap_completion::RuleParser;
|
||||
#[cfg(feature = "clap")]
|
||||
|
||||
@@ -14,7 +14,7 @@ use ruff_python_ast::{PySourceType, Suite};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
use ruff_python_parser::{AsMode, ParseError, TokenKindIter, Tokens};
|
||||
use ruff_source_file::{Locator, SourceFileBuilder};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -93,7 +93,7 @@ pub fn check_path(
|
||||
let use_doc_lines = settings.rules.enabled(Rule::DocLineTooLong);
|
||||
let mut doc_lines = vec![];
|
||||
if use_doc_lines {
|
||||
doc_lines.extend(doc_lines_from_tokens(&tokens));
|
||||
doc_lines.extend(doc_lines_from_tokens(tokens.kinds()));
|
||||
}
|
||||
|
||||
// Run the token-based rules.
|
||||
@@ -353,7 +353,7 @@ pub fn add_noqa_to_path(
|
||||
let contents = source_kind.source_code();
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
let tokens = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
@@ -518,8 +518,7 @@ pub fn lint_fix<'a>(
|
||||
// Continuously fix until the source code stabilizes.
|
||||
loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> =
|
||||
ruff_python_parser::tokenize(transformed.source_code(), source_type.as_mode());
|
||||
let tokens = ruff_python_parser::tokenize(transformed.source_code(), source_type.as_mode());
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(transformed.source_code());
|
||||
@@ -715,7 +714,7 @@ impl<'a> ParseSource<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TokenSource<'a> {
|
||||
/// Use the precomputed tokens to generate the AST.
|
||||
Tokens(Vec<LexResult>),
|
||||
Tokens(Tokens),
|
||||
/// Use the precomputed tokens and AST.
|
||||
Precomputed {
|
||||
tokens: &'a [LexResult],
|
||||
@@ -723,6 +722,18 @@ pub enum TokenSource<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
impl TokenSource<'_> {
|
||||
/// Returns an iterator over the [`TokenKind`] and the corresponding range.
|
||||
///
|
||||
/// [`TokenKind`]: ruff_python_parser::TokenKind
|
||||
pub fn kinds(&self) -> TokenKindIter {
|
||||
match self {
|
||||
TokenSource::Tokens(tokens) => tokens.kinds(),
|
||||
TokenSource::Precomputed { tokens, .. } => TokenKindIter::new(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TokenSource<'_> {
|
||||
type Target = [LexResult];
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::fmt::Display;
|
||||
use std::fs;
|
||||
use std::ops::Add;
|
||||
use std::path::Path;
|
||||
@@ -10,7 +10,7 @@ use itertools::Itertools;
|
||||
use log::warn;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{Diagnostic, Edit};
|
||||
use ruff_python_trivia::{indentation_at_offset, CommentRanges};
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
@@ -19,6 +19,27 @@ use crate::fs::relativize_path;
|
||||
use crate::registry::{AsRule, Rule, RuleSet};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
|
||||
/// Generates an array of edits that matches the length of `diagnostics`.
|
||||
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
||||
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
||||
/// the diagnostic. These edits may conflict with each other and should not be applied
|
||||
/// simultaneously.
|
||||
pub fn generate_noqa_edits(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
comment_ranges: &CommentRanges,
|
||||
external: &[String],
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
) -> Vec<Option<Edit>> {
|
||||
let exemption =
|
||||
FileExemption::try_extract(locator.contents(), comment_ranges, external, path, locator);
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
|
||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
||||
build_noqa_edits_by_diagnostic(comments, locator, line_ending)
|
||||
}
|
||||
|
||||
/// A directive to ignore a set of rules for a given line of Python source code (e.g.,
|
||||
/// `# noqa: F401, F841`).
|
||||
#[derive(Debug)]
|
||||
@@ -511,6 +532,7 @@ pub(crate) fn add_noqa(
|
||||
noqa_line_for,
|
||||
line_ending,
|
||||
);
|
||||
|
||||
fs::write(path, output)?;
|
||||
Ok(count)
|
||||
}
|
||||
@@ -524,9 +546,7 @@ fn add_noqa_inner(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
) -> (usize, String) {
|
||||
// Map of line start offset to set of (non-ignored) diagnostic codes that are triggered on that line.
|
||||
let mut matches_by_line: BTreeMap<TextSize, (RuleSet, Option<&Directive>)> =
|
||||
BTreeMap::default();
|
||||
let mut count = 0;
|
||||
|
||||
// Whether the file is exempted from all checks.
|
||||
// Codes that are globally exempted (within the current file).
|
||||
@@ -534,16 +554,117 @@ fn add_noqa_inner(
|
||||
FileExemption::try_extract(locator.contents(), comment_ranges, external, path, locator);
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator);
|
||||
|
||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
||||
|
||||
let edits = build_noqa_edits_by_line(comments, locator, line_ending);
|
||||
|
||||
let contents = locator.contents();
|
||||
|
||||
let mut output = String::with_capacity(contents.len());
|
||||
let mut last_append = TextSize::default();
|
||||
|
||||
for (_, edit) in edits {
|
||||
output.push_str(&contents[TextRange::new(last_append, edit.start())]);
|
||||
|
||||
edit.write(&mut output);
|
||||
|
||||
count += 1;
|
||||
|
||||
last_append = edit.end();
|
||||
}
|
||||
|
||||
output.push_str(&contents[TextRange::new(last_append, TextSize::of(contents))]);
|
||||
|
||||
(count, output)
|
||||
}
|
||||
|
||||
fn build_noqa_edits_by_diagnostic(
|
||||
comments: Vec<Option<NoqaComment>>,
|
||||
locator: &Locator,
|
||||
line_ending: LineEnding,
|
||||
) -> Vec<Option<Edit>> {
|
||||
let mut edits = Vec::default();
|
||||
for comment in comments {
|
||||
match comment {
|
||||
Some(comment) => {
|
||||
if let Some(noqa_edit) = generate_noqa_edit(
|
||||
comment.directive,
|
||||
comment.line,
|
||||
RuleSet::from_rule(comment.diagnostic.kind.rule()),
|
||||
locator,
|
||||
line_ending,
|
||||
) {
|
||||
edits.push(Some(noqa_edit.into_edit()));
|
||||
}
|
||||
}
|
||||
None => edits.push(None),
|
||||
}
|
||||
}
|
||||
edits
|
||||
}
|
||||
|
||||
fn build_noqa_edits_by_line<'a>(
|
||||
comments: Vec<Option<NoqaComment<'a>>>,
|
||||
locator: &Locator,
|
||||
line_ending: LineEnding,
|
||||
) -> BTreeMap<TextSize, NoqaEdit<'a>> {
|
||||
let mut comments_by_line = BTreeMap::default();
|
||||
for comment in comments.into_iter().flatten() {
|
||||
comments_by_line
|
||||
.entry(comment.line)
|
||||
.or_insert_with(Vec::default)
|
||||
.push(comment);
|
||||
}
|
||||
let mut edits = BTreeMap::default();
|
||||
for (offset, matches) in comments_by_line {
|
||||
let Some(first_match) = matches.first() else {
|
||||
continue;
|
||||
};
|
||||
let directive = first_match.directive;
|
||||
if let Some(edit) = generate_noqa_edit(
|
||||
directive,
|
||||
offset,
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|NoqaComment { diagnostic, .. }| diagnostic.kind.rule())
|
||||
.collect(),
|
||||
locator,
|
||||
line_ending,
|
||||
) {
|
||||
edits.insert(offset, edit);
|
||||
}
|
||||
}
|
||||
edits
|
||||
}
|
||||
|
||||
struct NoqaComment<'a> {
|
||||
line: TextSize,
|
||||
diagnostic: &'a Diagnostic,
|
||||
directive: Option<&'a Directive<'a>>,
|
||||
}
|
||||
|
||||
fn find_noqa_comments<'a>(
|
||||
diagnostics: &'a [Diagnostic],
|
||||
locator: &'a Locator,
|
||||
exemption: &Option<FileExemption>,
|
||||
directives: &'a NoqaDirectives,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
) -> Vec<Option<NoqaComment<'a>>> {
|
||||
// List of noqa comments, ordered to match up with `diagnostics`
|
||||
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
||||
|
||||
// Mark any non-ignored diagnostics.
|
||||
for diagnostic in diagnostics {
|
||||
match &exemption {
|
||||
Some(FileExemption::All) => {
|
||||
// If the file is exempted, don't add any noqa directives.
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
Some(FileExemption::Codes(codes)) => {
|
||||
// If the diagnostic is ignored by a global exemption, don't add a noqa directive.
|
||||
if codes.contains(&diagnostic.kind.rule().noqa_code()) {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -557,10 +678,12 @@ fn add_noqa_inner(
|
||||
{
|
||||
match &directive_line.directive {
|
||||
Directive::All(_) => {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
Directive::Codes(codes) => {
|
||||
if codes.includes(diagnostic.kind.rule()) {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -574,18 +697,17 @@ fn add_noqa_inner(
|
||||
if let Some(directive_line) = directives.find_line_with_directive(noqa_offset) {
|
||||
match &directive_line.directive {
|
||||
Directive::All(_) => {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
Directive::Codes(codes) => {
|
||||
directive @ Directive::Codes(codes) => {
|
||||
let rule = diagnostic.kind.rule();
|
||||
if !codes.includes(rule) {
|
||||
matches_by_line
|
||||
.entry(directive_line.start())
|
||||
.or_insert_with(|| {
|
||||
(RuleSet::default(), Some(&directive_line.directive))
|
||||
})
|
||||
.0
|
||||
.insert(rule);
|
||||
comments_by_line.push(Some(NoqaComment {
|
||||
line: directive_line.start(),
|
||||
diagnostic,
|
||||
directive: Some(directive),
|
||||
}));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -593,87 +715,106 @@ fn add_noqa_inner(
|
||||
}
|
||||
|
||||
// There's no existing noqa directive that suppresses the diagnostic.
|
||||
matches_by_line
|
||||
.entry(locator.line_start(noqa_offset))
|
||||
.or_insert_with(|| (RuleSet::default(), None))
|
||||
.0
|
||||
.insert(diagnostic.kind.rule());
|
||||
comments_by_line.push(Some(NoqaComment {
|
||||
line: locator.line_start(noqa_offset),
|
||||
diagnostic,
|
||||
directive: None,
|
||||
}));
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut prev_end = TextSize::default();
|
||||
comments_by_line
|
||||
}
|
||||
|
||||
for (offset, (rules, directive)) in matches_by_line {
|
||||
output.push_str(locator.slice(TextRange::new(prev_end, offset)));
|
||||
struct NoqaEdit<'a> {
|
||||
edit_range: TextRange,
|
||||
rules: RuleSet,
|
||||
codes: Option<&'a Codes<'a>>,
|
||||
line_ending: LineEnding,
|
||||
}
|
||||
|
||||
let line = locator.full_line(offset);
|
||||
impl<'a> NoqaEdit<'a> {
|
||||
fn into_edit(self) -> Edit {
|
||||
let mut edit_content = String::new();
|
||||
self.write(&mut edit_content);
|
||||
|
||||
match directive {
|
||||
None => {
|
||||
// Add existing content.
|
||||
output.push_str(line.trim_end());
|
||||
Edit::range_replacement(edit_content, self.edit_range)
|
||||
}
|
||||
|
||||
// Add `noqa` directive.
|
||||
output.push_str(" # noqa: ");
|
||||
|
||||
// Add codes.
|
||||
push_codes(&mut output, rules.iter().map(|rule| rule.noqa_code()));
|
||||
output.push_str(&line_ending);
|
||||
count += 1;
|
||||
}
|
||||
Some(Directive::All(_)) => {
|
||||
// Does not get inserted into the map.
|
||||
}
|
||||
Some(Directive::Codes(codes)) => {
|
||||
// Reconstruct the line based on the preserved rule codes.
|
||||
// This enables us to tally the number of edits.
|
||||
let output_start = output.len();
|
||||
|
||||
// Add existing content.
|
||||
output.push_str(
|
||||
locator
|
||||
.slice(TextRange::new(offset, codes.start()))
|
||||
.trim_end(),
|
||||
);
|
||||
|
||||
// Add `noqa` directive.
|
||||
output.push_str(" # noqa: ");
|
||||
|
||||
// Add codes.
|
||||
fn write(&self, writer: &mut impl std::fmt::Write) {
|
||||
write!(writer, " # noqa: ").unwrap();
|
||||
match self.codes {
|
||||
Some(codes) => {
|
||||
push_codes(
|
||||
&mut output,
|
||||
rules
|
||||
writer,
|
||||
self.rules
|
||||
.iter()
|
||||
.map(|rule| rule.noqa_code().to_string())
|
||||
.chain(codes.iter().map(ToString::to_string))
|
||||
.sorted_unstable(),
|
||||
);
|
||||
|
||||
// Only count if the new line is an actual edit.
|
||||
if &output[output_start..] != line.trim_end() {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
output.push_str(&line_ending);
|
||||
}
|
||||
None => {
|
||||
push_codes(
|
||||
writer,
|
||||
self.rules.iter().map(|rule| rule.noqa_code().to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
prev_end = offset + line.text_len();
|
||||
write!(writer, "{}", self.line_ending.as_str()).unwrap();
|
||||
}
|
||||
|
||||
output.push_str(locator.after(prev_end));
|
||||
|
||||
(count, output)
|
||||
}
|
||||
|
||||
fn push_codes<I: Display>(str: &mut String, codes: impl Iterator<Item = I>) {
|
||||
impl<'a> Ranged for NoqaEdit<'a> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.edit_range
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_noqa_edit<'a>(
|
||||
directive: Option<&'a Directive>,
|
||||
offset: TextSize,
|
||||
rules: RuleSet,
|
||||
locator: &Locator,
|
||||
line_ending: LineEnding,
|
||||
) -> Option<NoqaEdit<'a>> {
|
||||
let line_range = locator.full_line_range(offset);
|
||||
|
||||
let edit_range;
|
||||
let codes;
|
||||
|
||||
// Add codes.
|
||||
match directive {
|
||||
None => {
|
||||
let trimmed_line = locator.slice(line_range).trim_end();
|
||||
edit_range = TextRange::new(TextSize::of(trimmed_line), line_range.len()) + offset;
|
||||
codes = None;
|
||||
}
|
||||
Some(Directive::Codes(existing_codes)) => {
|
||||
// find trimmed line without the noqa
|
||||
let trimmed_line = locator
|
||||
.slice(TextRange::new(line_range.start(), existing_codes.start()))
|
||||
.trim_end();
|
||||
edit_range = TextRange::new(TextSize::of(trimmed_line), line_range.len()) + offset;
|
||||
codes = Some(existing_codes);
|
||||
}
|
||||
Some(Directive::All(_)) => return None,
|
||||
};
|
||||
|
||||
Some(NoqaEdit {
|
||||
edit_range,
|
||||
rules,
|
||||
codes,
|
||||
line_ending,
|
||||
})
|
||||
}
|
||||
|
||||
fn push_codes<I: Display>(writer: &mut dyn std::fmt::Write, codes: impl Iterator<Item = I>) {
|
||||
let mut first = true;
|
||||
for code in codes {
|
||||
if !first {
|
||||
str.push_str(", ");
|
||||
write!(writer, ", ").unwrap();
|
||||
}
|
||||
write!(str, "{code}").unwrap();
|
||||
write!(writer, "{code}").unwrap();
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
@@ -846,13 +987,15 @@ mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{Diagnostic, Edit};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use crate::generate_noqa_edits;
|
||||
use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption};
|
||||
use crate::rules::pycodestyle::rules::AmbiguousVariableName;
|
||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||
|
||||
#[test]
|
||||
fn noqa_all() {
|
||||
@@ -1130,4 +1273,41 @@ mod tests {
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, "x = 1 # noqa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_comment() {
|
||||
let path = Path::new("/tmp/foo.txt");
|
||||
let source = r#"
|
||||
print(
|
||||
"""First line
|
||||
second line
|
||||
third line
|
||||
%s"""
|
||||
% name
|
||||
)
|
||||
"#;
|
||||
let noqa_line_for = [TextRange::new(8.into(), 68.into())].into_iter().collect();
|
||||
let diagnostics = [Diagnostic::new(
|
||||
PrintfStringFormatting,
|
||||
TextRange::new(12.into(), 79.into()),
|
||||
)];
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
&diagnostics,
|
||||
&Locator::new(source),
|
||||
&comment_ranges,
|
||||
&[],
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
);
|
||||
assert_eq!(
|
||||
edits,
|
||||
vec![Some(Edit::replacement(
|
||||
" # noqa: UP031\n".to_string(),
|
||||
68.into(),
|
||||
69.into()
|
||||
))]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,6 @@ impl Rule {
|
||||
| Rule::InvalidCharacterNul
|
||||
| Rule::InvalidCharacterSub
|
||||
| Rule::InvalidCharacterZeroWidthSpace
|
||||
| Rule::InvalidEscapeSequence
|
||||
| Rule::InvalidTodoCapitalization
|
||||
| Rule::InvalidTodoTag
|
||||
| Rule::LineContainsFixme
|
||||
|
||||
@@ -57,7 +57,7 @@ pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: Strin
|
||||
}
|
||||
}
|
||||
ast::FStringPart::FString(f_string) => {
|
||||
for literal in f_string.literals() {
|
||||
for literal in f_string.elements.literals() {
|
||||
if &**literal == "0.0.0.0" {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
HardcodedBindAllInterfaces,
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
|
||||
check(checker, literal, literal.range());
|
||||
}
|
||||
ast::FStringPart::FString(f_string) => {
|
||||
for literal in f_string.literals() {
|
||||
for literal in f_string.elements.literals() {
|
||||
check(checker, literal, literal.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ mod tests {
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
@@ -62,6 +63,7 @@ mod tests {
|
||||
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018_attribute_docstring.py"))]
|
||||
#[test_case(Rule::LoopIteratorMutation, Path::new("B909.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
@@ -73,6 +75,26 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018_attribute_docstring.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bugbear").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_without_explicit_strict() -> Result<()> {
|
||||
let snapshot = "B905.py";
|
||||
|
||||
@@ -16,6 +16,9 @@ use super::super::helpers::at_last_top_level_expression_in_cell;
|
||||
/// by mistake. Assign a useless expression to a variable, or remove it
|
||||
/// entirely.
|
||||
///
|
||||
/// In [preview mode], this rule will also flag string literals and f-strings that
|
||||
/// are not used as a docstring or an attribute docstring.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// 1 + 1
|
||||
@@ -45,6 +48,8 @@ use super::super::helpers::at_last_top_level_expression_in_cell;
|
||||
/// with errors.ExceptionRaisedContext():
|
||||
/// _ = obj.attribute
|
||||
/// ```
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[violation]
|
||||
pub struct UselessExpression {
|
||||
kind: Kind,
|
||||
@@ -69,15 +74,16 @@ impl Violation for UselessExpression {
|
||||
/// B018
|
||||
pub(crate) fn useless_expression(checker: &mut Checker, value: &Expr) {
|
||||
// Ignore comparisons, as they're handled by `useless_comparison`.
|
||||
if value.is_compare_expr() {
|
||||
if matches!(value, Expr::Compare(_) | Expr::EllipsisLiteral(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore strings, to avoid false positives with docstrings.
|
||||
if matches!(
|
||||
value,
|
||||
Expr::FString(_) | Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
|
||||
) {
|
||||
if checker.settings.preview.is_enabled() {
|
||||
if checker.semantic().in_pep_257_docstring() || checker.semantic().in_attribute_docstring()
|
||||
{
|
||||
return;
|
||||
}
|
||||
} else if matches!(value, Expr::StringLiteral(_) | Expr::FString(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B018.py:11:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
9 | "str" # Str (no raise)
|
||||
10 | f"{int}" # JoinedStr (no raise)
|
||||
9 | "str" # StringLiteral
|
||||
10 | f"{int}" # FString
|
||||
11 | 1j # Number (complex)
|
||||
| ^^ B018
|
||||
12 | 1 # Number (int)
|
||||
@@ -13,7 +13,7 @@ B018.py:11:5: B018 Found useless expression. Either assign it to a variable or r
|
||||
|
||||
B018.py:12:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
10 | f"{int}" # JoinedStr (no raise)
|
||||
10 | f"{int}" # FString
|
||||
11 | 1j # Number (complex)
|
||||
12 | 1 # Number (int)
|
||||
| ^ B018
|
||||
@@ -117,8 +117,8 @@ B018.py:27:5: B018 Found useless expression. Either assign it to a variable or r
|
||||
|
||||
B018.py:39:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
37 | "str" # Str (no raise)
|
||||
38 | f"{int}" # JoinedStr (no raise)
|
||||
37 | "str" # StringLiteral
|
||||
38 | f"{int}" # FString
|
||||
39 | 1j # Number (complex)
|
||||
| ^^ B018
|
||||
40 | 1 # Number (int)
|
||||
@@ -127,7 +127,7 @@ B018.py:39:5: B018 Found useless expression. Either assign it to a variable or r
|
||||
|
||||
B018.py:40:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
38 | f"{int}" # JoinedStr (no raise)
|
||||
38 | f"{int}" # FString
|
||||
39 | 1j # Number (complex)
|
||||
40 | 1 # Number (int)
|
||||
| ^ B018
|
||||
@@ -254,5 +254,3 @@ B018.py:65:5: B018 Found useless expression. Either assign it to a variable or r
|
||||
65 | "foo" + "bar" # BinOp (raise)
|
||||
| ^^^^^^^^^^^^^ B018
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B018_attribute_docstring.py:22:2: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
20 | "d: not a docstring"
|
||||
21 |
|
||||
22 | (e := 1)
|
||||
| ^^^^^^ B018
|
||||
23 | "e: not a docstring"
|
||||
|
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B018.ipynb:5:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
3 | x
|
||||
4 | # Only skip the last expression
|
||||
5 | x # B018
|
||||
| ^ B018
|
||||
6 | x
|
||||
7 | # Nested expressions isn't relevant
|
||||
|
|
||||
|
||||
B018.ipynb:9:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
7 | # Nested expressions isn't relevant
|
||||
8 | if True:
|
||||
9 | x
|
||||
| ^ B018
|
||||
10 | # Semicolons shouldn't affect the output
|
||||
11 | x;
|
||||
|
|
||||
|
||||
B018.ipynb:13:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
11 | x;
|
||||
12 | # Semicolons with multiple expressions
|
||||
13 | x; x
|
||||
| ^ B018
|
||||
14 | # Comments, newlines and whitespace
|
||||
15 | x # comment
|
||||
|
|
||||
@@ -0,0 +1,295 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B018.py:10:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
8 | a = 2
|
||||
9 | "str" # StringLiteral
|
||||
10 | f"{int}" # FString
|
||||
| ^^^^^^^^ B018
|
||||
11 | 1j # Number (complex)
|
||||
12 | 1 # Number (int)
|
||||
|
|
||||
|
||||
B018.py:11:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
9 | "str" # StringLiteral
|
||||
10 | f"{int}" # FString
|
||||
11 | 1j # Number (complex)
|
||||
| ^^ B018
|
||||
12 | 1 # Number (int)
|
||||
13 | 1.0 # Number (float)
|
||||
|
|
||||
|
||||
B018.py:12:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
10 | f"{int}" # FString
|
||||
11 | 1j # Number (complex)
|
||||
12 | 1 # Number (int)
|
||||
| ^ B018
|
||||
13 | 1.0 # Number (float)
|
||||
14 | b"foo" # Binary
|
||||
|
|
||||
|
||||
B018.py:13:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
11 | 1j # Number (complex)
|
||||
12 | 1 # Number (int)
|
||||
13 | 1.0 # Number (float)
|
||||
| ^^^ B018
|
||||
14 | b"foo" # Binary
|
||||
15 | True # NameConstant (True)
|
||||
|
|
||||
|
||||
B018.py:14:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
12 | 1 # Number (int)
|
||||
13 | 1.0 # Number (float)
|
||||
14 | b"foo" # Binary
|
||||
| ^^^^^^ B018
|
||||
15 | True # NameConstant (True)
|
||||
16 | False # NameConstant (False)
|
||||
|
|
||||
|
||||
B018.py:15:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
13 | 1.0 # Number (float)
|
||||
14 | b"foo" # Binary
|
||||
15 | True # NameConstant (True)
|
||||
| ^^^^ B018
|
||||
16 | False # NameConstant (False)
|
||||
17 | None # NameConstant (None)
|
||||
|
|
||||
|
||||
B018.py:16:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
14 | b"foo" # Binary
|
||||
15 | True # NameConstant (True)
|
||||
16 | False # NameConstant (False)
|
||||
| ^^^^^ B018
|
||||
17 | None # NameConstant (None)
|
||||
18 | [1, 2] # list
|
||||
|
|
||||
|
||||
B018.py:17:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
15 | True # NameConstant (True)
|
||||
16 | False # NameConstant (False)
|
||||
17 | None # NameConstant (None)
|
||||
| ^^^^ B018
|
||||
18 | [1, 2] # list
|
||||
19 | {1, 2} # set
|
||||
|
|
||||
|
||||
B018.py:18:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
16 | False # NameConstant (False)
|
||||
17 | None # NameConstant (None)
|
||||
18 | [1, 2] # list
|
||||
| ^^^^^^ B018
|
||||
19 | {1, 2} # set
|
||||
20 | {"foo": "bar"} # dict
|
||||
|
|
||||
|
||||
B018.py:19:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
17 | None # NameConstant (None)
|
||||
18 | [1, 2] # list
|
||||
19 | {1, 2} # set
|
||||
| ^^^^^^ B018
|
||||
20 | {"foo": "bar"} # dict
|
||||
|
|
||||
|
||||
B018.py:20:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
18 | [1, 2] # list
|
||||
19 | {1, 2} # set
|
||||
20 | {"foo": "bar"} # dict
|
||||
| ^^^^^^^^^^^^^^ B018
|
||||
|
|
||||
|
||||
B018.py:24:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
23 | class Foo3:
|
||||
24 | 123
|
||||
| ^^^ B018
|
||||
25 | a = 2
|
||||
26 | "str"
|
||||
|
|
||||
|
||||
B018.py:27:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
25 | a = 2
|
||||
26 | "str"
|
||||
27 | 1
|
||||
| ^ B018
|
||||
|
|
||||
|
||||
B018.py:37:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
35 | """my docstring"""
|
||||
36 | a = 2
|
||||
37 | "str" # StringLiteral
|
||||
| ^^^^^ B018
|
||||
38 | f"{int}" # FString
|
||||
39 | 1j # Number (complex)
|
||||
|
|
||||
|
||||
B018.py:38:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
36 | a = 2
|
||||
37 | "str" # StringLiteral
|
||||
38 | f"{int}" # FString
|
||||
| ^^^^^^^^ B018
|
||||
39 | 1j # Number (complex)
|
||||
40 | 1 # Number (int)
|
||||
|
|
||||
|
||||
B018.py:39:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
37 | "str" # StringLiteral
|
||||
38 | f"{int}" # FString
|
||||
39 | 1j # Number (complex)
|
||||
| ^^ B018
|
||||
40 | 1 # Number (int)
|
||||
41 | 1.0 # Number (float)
|
||||
|
|
||||
|
||||
B018.py:40:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
38 | f"{int}" # FString
|
||||
39 | 1j # Number (complex)
|
||||
40 | 1 # Number (int)
|
||||
| ^ B018
|
||||
41 | 1.0 # Number (float)
|
||||
42 | b"foo" # Binary
|
||||
|
|
||||
|
||||
B018.py:41:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
39 | 1j # Number (complex)
|
||||
40 | 1 # Number (int)
|
||||
41 | 1.0 # Number (float)
|
||||
| ^^^ B018
|
||||
42 | b"foo" # Binary
|
||||
43 | True # NameConstant (True)
|
||||
|
|
||||
|
||||
B018.py:42:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
40 | 1 # Number (int)
|
||||
41 | 1.0 # Number (float)
|
||||
42 | b"foo" # Binary
|
||||
| ^^^^^^ B018
|
||||
43 | True # NameConstant (True)
|
||||
44 | False # NameConstant (False)
|
||||
|
|
||||
|
||||
B018.py:43:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
41 | 1.0 # Number (float)
|
||||
42 | b"foo" # Binary
|
||||
43 | True # NameConstant (True)
|
||||
| ^^^^ B018
|
||||
44 | False # NameConstant (False)
|
||||
45 | None # NameConstant (None)
|
||||
|
|
||||
|
||||
B018.py:44:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
42 | b"foo" # Binary
|
||||
43 | True # NameConstant (True)
|
||||
44 | False # NameConstant (False)
|
||||
| ^^^^^ B018
|
||||
45 | None # NameConstant (None)
|
||||
46 | [1, 2] # list
|
||||
|
|
||||
|
||||
B018.py:45:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
43 | True # NameConstant (True)
|
||||
44 | False # NameConstant (False)
|
||||
45 | None # NameConstant (None)
|
||||
| ^^^^ B018
|
||||
46 | [1, 2] # list
|
||||
47 | {1, 2} # set
|
||||
|
|
||||
|
||||
B018.py:46:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
44 | False # NameConstant (False)
|
||||
45 | None # NameConstant (None)
|
||||
46 | [1, 2] # list
|
||||
| ^^^^^^ B018
|
||||
47 | {1, 2} # set
|
||||
48 | {"foo": "bar"} # dict
|
||||
|
|
||||
|
||||
B018.py:47:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
45 | None # NameConstant (None)
|
||||
46 | [1, 2] # list
|
||||
47 | {1, 2} # set
|
||||
| ^^^^^^ B018
|
||||
48 | {"foo": "bar"} # dict
|
||||
|
|
||||
|
||||
B018.py:48:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
46 | [1, 2] # list
|
||||
47 | {1, 2} # set
|
||||
48 | {"foo": "bar"} # dict
|
||||
| ^^^^^^^^^^^^^^ B018
|
||||
|
|
||||
|
||||
B018.py:52:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
51 | def foo3():
|
||||
52 | 123
|
||||
| ^^^ B018
|
||||
53 | a = 2
|
||||
54 | "str"
|
||||
|
|
||||
|
||||
B018.py:54:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
52 | 123
|
||||
53 | a = 2
|
||||
54 | "str"
|
||||
| ^^^^^ B018
|
||||
55 | 3
|
||||
|
|
||||
|
||||
B018.py:55:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
53 | a = 2
|
||||
54 | "str"
|
||||
55 | 3
|
||||
| ^ B018
|
||||
|
|
||||
|
||||
B018.py:63:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
62 | def foo5():
|
||||
63 | foo.bar # Attribute (raise)
|
||||
| ^^^^^^^ B018
|
||||
64 | object().__class__ # Attribute (raise)
|
||||
65 | "foo" + "bar" # BinOp (raise)
|
||||
|
|
||||
|
||||
B018.py:64:5: B018 Found useless attribute access. Either assign it to a variable or remove it.
|
||||
|
|
||||
62 | def foo5():
|
||||
63 | foo.bar # Attribute (raise)
|
||||
64 | object().__class__ # Attribute (raise)
|
||||
| ^^^^^^^^^^^^^^^^^^ B018
|
||||
65 | "foo" + "bar" # BinOp (raise)
|
||||
|
|
||||
|
||||
B018.py:65:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
63 | foo.bar # Attribute (raise)
|
||||
64 | object().__class__ # Attribute (raise)
|
||||
65 | "foo" + "bar" # BinOp (raise)
|
||||
| ^^^^^^^^^^^^^ B018
|
||||
|
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B018_attribute_docstring.py:10:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
8 | b = 1
|
||||
9 | "b: docstring" " continue"
|
||||
10 | "b: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
11 |
|
||||
12 | c: int = 1
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:20:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
18 | if True:
|
||||
19 | d = 1
|
||||
20 | "d: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
21 |
|
||||
22 | (e := 1)
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:22:2: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
20 | "d: not a docstring"
|
||||
21 |
|
||||
22 | (e := 1)
|
||||
| ^^^^^^ B018
|
||||
23 | "e: not a docstring"
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:23:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
22 | (e := 1)
|
||||
23 | "e: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
24 |
|
||||
25 | f = 0
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:27:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
25 | f = 0
|
||||
26 | f += 1
|
||||
27 | "f: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
28 |
|
||||
29 | g.h = 1
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:30:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
29 | g.h = 1
|
||||
30 | "g.h: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
31 |
|
||||
32 | (i) = 1
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:42:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
41 | l = m = 1
|
||||
42 | "l m: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
43 |
|
||||
44 | n.a = n.b = n.c = 1
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:45:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
44 | n.a = n.b = n.c = 1
|
||||
45 | "n.*: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
46 |
|
||||
47 | (o, p) = (1, 2)
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:48:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
47 | (o, p) = (1, 2)
|
||||
48 | "o p: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
49 |
|
||||
50 | [q, r] = [1, 2]
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:51:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
50 | [q, r] = [1, 2]
|
||||
51 | "q r: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
52 |
|
||||
53 | *s = 1
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:54:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
53 | *s = 1
|
||||
54 | "s: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:63:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
61 | b: int
|
||||
62 | "Foo.b: docstring"
|
||||
63 | "Foo.b: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
64 |
|
||||
65 | c: int = 1
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:71:9: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
69 | # This is actually a docstring but we currently don't detect it.
|
||||
70 | self.x = 1
|
||||
71 | "self.x: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
72 |
|
||||
73 | t = 2
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:74:9: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
73 | t = 2
|
||||
74 | "t: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
75 |
|
||||
76 | def random(self):
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:78:9: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
76 | def random(self):
|
||||
77 | self.y = 2
|
||||
78 | "self.y: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
79 |
|
||||
80 | u = 2
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:81:9: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
80 | u = 2
|
||||
81 | "u: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
82 |
|
||||
83 | def add(self, y: int):
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:89:5: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
87 | def function():
|
||||
88 | v = 2
|
||||
89 | "v: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ B018
|
||||
|
|
||||
|
||||
B018_attribute_docstring.py:93:1: B018 Found useless expression. Either assign it to a variable or remove it.
|
||||
|
|
||||
92 | function.a = 1
|
||||
93 | "function.a: not a docstring"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B018
|
||||
|
|
||||
@@ -2,8 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_parser::{TokenKind, TokenKindIter};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -52,26 +51,26 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&Tok, TextRange)> for Token {
|
||||
fn from((tok, range): (&Tok, TextRange)) -> Self {
|
||||
impl From<(TokenKind, TextRange)> for Token {
|
||||
fn from((tok, range): (TokenKind, TextRange)) -> Self {
|
||||
let ty = match tok {
|
||||
Tok::Name { .. } => TokenType::Named,
|
||||
Tok::String { .. } => TokenType::String,
|
||||
Tok::Newline => TokenType::Newline,
|
||||
Tok::NonLogicalNewline => TokenType::NonLogicalNewline,
|
||||
Tok::Lpar => TokenType::OpeningBracket,
|
||||
Tok::Rpar => TokenType::ClosingBracket,
|
||||
Tok::Lsqb => TokenType::OpeningSquareBracket,
|
||||
Tok::Rsqb => TokenType::ClosingBracket,
|
||||
Tok::Colon => TokenType::Colon,
|
||||
Tok::Comma => TokenType::Comma,
|
||||
Tok::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
Tok::Rbrace => TokenType::ClosingBracket,
|
||||
Tok::Def => TokenType::Def,
|
||||
Tok::For => TokenType::For,
|
||||
Tok::Lambda => TokenType::Lambda,
|
||||
TokenKind::Name => TokenType::Named,
|
||||
TokenKind::String => TokenType::String,
|
||||
TokenKind::Newline => TokenType::Newline,
|
||||
TokenKind::NonLogicalNewline => TokenType::NonLogicalNewline,
|
||||
TokenKind::Lpar => TokenType::OpeningBracket,
|
||||
TokenKind::Rpar => TokenType::ClosingBracket,
|
||||
TokenKind::Lsqb => TokenType::OpeningSquareBracket,
|
||||
TokenKind::Rsqb => TokenType::ClosingBracket,
|
||||
TokenKind::Colon => TokenType::Colon,
|
||||
TokenKind::Comma => TokenType::Comma,
|
||||
TokenKind::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
TokenKind::Rbrace => TokenType::ClosingBracket,
|
||||
TokenKind::Def => TokenType::Def,
|
||||
TokenKind::For => TokenType::For,
|
||||
TokenKind::Lambda => TokenType::Lambda,
|
||||
// Import treated like a function.
|
||||
Tok::Import => TokenType::Named,
|
||||
TokenKind::Import => TokenType::Named,
|
||||
_ => TokenType::Irrelevant,
|
||||
};
|
||||
#[allow(clippy::inconsistent_struct_constructor)]
|
||||
@@ -227,27 +226,23 @@ impl AlwaysFixableViolation for ProhibitedTrailingComma {
|
||||
/// COM812, COM818, COM819
|
||||
pub(crate) fn trailing_commas(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
tokens: &[LexResult],
|
||||
tokens: TokenKindIter,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) {
|
||||
let mut fstrings = 0u32;
|
||||
let tokens = tokens.iter().filter_map(|result| {
|
||||
let Ok((tok, tok_range)) = result else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match tok {
|
||||
let tokens = tokens.filter_map(|(token, tok_range)| {
|
||||
match token {
|
||||
// Completely ignore comments -- they just interfere with the logic.
|
||||
Tok::Comment(_) => None,
|
||||
TokenKind::Comment => None,
|
||||
// F-strings are handled as `String` token type with the complete range
|
||||
// of the outermost f-string. This means that the expression inside the
|
||||
// f-string is not checked for trailing commas.
|
||||
Tok::FStringStart(_) => {
|
||||
TokenKind::FStringStart => {
|
||||
fstrings = fstrings.saturating_add(1);
|
||||
None
|
||||
}
|
||||
Tok::FStringEnd => {
|
||||
TokenKind::FStringEnd => {
|
||||
fstrings = fstrings.saturating_sub(1);
|
||||
if fstrings == 0 {
|
||||
indexer
|
||||
@@ -260,7 +255,7 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
_ => {
|
||||
if fstrings == 0 {
|
||||
Some(Token::from((tok, *tok_range)))
|
||||
Some(Token::from((token, tok_range)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -118,7 +118,11 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
|
||||
}
|
||||
}
|
||||
ast::FStringPart::FString(f_string) => {
|
||||
if f_string.literals().any(|literal| literal.contains("%z")) {
|
||||
if f_string
|
||||
.elements
|
||||
.literals()
|
||||
.any(|literal| literal.contains("%z"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ impl Violation for FutureRewritableTypeAnnotation {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let FutureRewritableTypeAnnotation { name } = self;
|
||||
format!("Missing `from __future__ import annotations`, but uses `{name}`")
|
||||
format!("Add `from __future__ import annotations` to simplify `{name}`")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
|
||||
---
|
||||
edge_case.py:5:13: FA100 Missing `from __future__ import annotations`, but uses `typing.List`
|
||||
edge_case.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
|
|
||||
5 | def main(_: List[int]) -> None:
|
||||
| ^^^^ FA100
|
||||
@@ -9,12 +9,10 @@ edge_case.py:5:13: FA100 Missing `from __future__ import annotations`, but uses
|
||||
7 | a_list.append("hello")
|
||||
|
|
||||
|
||||
edge_case.py:6:13: FA100 Missing `from __future__ import annotations`, but uses `typing.List`
|
||||
edge_case.py:6:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
|
|
||||
5 | def main(_: List[int]) -> None:
|
||||
6 | a_list: t.List[str] = []
|
||||
| ^^^^^^ FA100
|
||||
7 | a_list.append("hello")
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
|
||||
---
|
||||
from_typing_import.py:5:13: FA100 Missing `from __future__ import annotations`, but uses `typing.List`
|
||||
from_typing_import.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
|
|
||||
4 | def main() -> None:
|
||||
5 | a_list: List[str] = []
|
||||
| ^^^^ FA100
|
||||
6 | a_list.append("hello")
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
|
||||
---
|
||||
from_typing_import_many.py:5:13: FA100 Missing `from __future__ import annotations`, but uses `typing.List`
|
||||
from_typing_import_many.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
|
|
||||
4 | def main() -> None:
|
||||
5 | a_list: List[Optional[str]] = []
|
||||
@@ -10,7 +10,7 @@ from_typing_import_many.py:5:13: FA100 Missing `from __future__ import annotatio
|
||||
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
||||
|
|
||||
|
||||
from_typing_import_many.py:5:18: FA100 Missing `from __future__ import annotations`, but uses `typing.Optional`
|
||||
from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations` to simplify `typing.Optional`
|
||||
|
|
||||
4 | def main() -> None:
|
||||
5 | a_list: List[Optional[str]] = []
|
||||
@@ -18,5 +18,3 @@ from_typing_import_many.py:5:18: FA100 Missing `from __future__ import annotatio
|
||||
6 | a_list.append("hello")
|
||||
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
|
||||
---
|
||||
import_typing.py:5:13: FA100 Missing `from __future__ import annotations`, but uses `typing.List`
|
||||
import_typing.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
|
|
||||
4 | def main() -> None:
|
||||
5 | a_list: typing.List[str] = []
|
||||
| ^^^^^^^^^^^ FA100
|
||||
6 | a_list.append("hello")
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
|
||||
---
|
||||
import_typing_as.py:5:13: FA100 Missing `from __future__ import annotations`, but uses `typing.List`
|
||||
import_typing_as.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
|
||||
|
|
||||
4 | def main() -> None:
|
||||
5 | a_list: t.List[str] = []
|
||||
| ^^^^^^ FA100
|
||||
6 | a_list.append("hello")
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::str::{leading_quote, trailing_quote};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_parser::{TokenKind, TokenKindIter};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
@@ -93,36 +92,34 @@ impl Violation for MultiLineImplicitStringConcatenation {
|
||||
/// ISC001, ISC002
|
||||
pub(crate) fn implicit(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
tokens: &[LexResult],
|
||||
tokens: TokenKindIter,
|
||||
settings: &LinterSettings,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
) {
|
||||
for ((a_tok, a_range), (b_tok, b_range)) in tokens
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|(tok, _)| {
|
||||
!tok.is_comment()
|
||||
.filter(|(token, _)| {
|
||||
*token != TokenKind::Comment
|
||||
&& (settings.flake8_implicit_str_concat.allow_multiline
|
||||
|| !tok.is_non_logical_newline())
|
||||
|| *token != TokenKind::NonLogicalNewline)
|
||||
})
|
||||
.tuple_windows()
|
||||
{
|
||||
let (a_range, b_range) = match (a_tok, b_tok) {
|
||||
(Tok::String { .. }, Tok::String { .. }) => (*a_range, *b_range),
|
||||
(Tok::String { .. }, Tok::FStringStart(_)) => {
|
||||
(TokenKind::String, TokenKind::String) => (a_range, b_range),
|
||||
(TokenKind::String, TokenKind::FStringStart) => {
|
||||
match indexer.fstring_ranges().innermost(b_range.start()) {
|
||||
Some(b_range) => (*a_range, b_range),
|
||||
Some(b_range) => (a_range, b_range),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
(Tok::FStringEnd, Tok::String { .. }) => {
|
||||
(TokenKind::FStringEnd, TokenKind::String) => {
|
||||
match indexer.fstring_ranges().innermost(a_range.start()) {
|
||||
Some(a_range) => (a_range, *b_range),
|
||||
Some(a_range) => (a_range, b_range),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
(Tok::FStringEnd, Tok::FStringStart(_)) => {
|
||||
(TokenKind::FStringEnd, TokenKind::FStringStart) => {
|
||||
match (
|
||||
indexer.fstring_ranges().innermost(a_range.start()),
|
||||
indexer.fstring_ranges().innermost(b_range.start()),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user