Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3cc918b93 | ||
|
|
d864477876 | ||
|
|
e1ced89624 | ||
|
|
4470d7ba04 | ||
|
|
2a1601749f | ||
|
|
a01edad1c4 | ||
|
|
a81ac6705d | ||
|
|
d77675f30d | ||
|
|
fe7658199d | ||
|
|
cfa25ea4b0 | ||
|
|
c7f0f3b237 | ||
|
|
3b36030461 | ||
|
|
812df77246 | ||
|
|
69b356e9b9 | ||
|
|
033d7d7e91 | ||
|
|
a181ca7a3d | ||
|
|
92124001d5 | ||
|
|
06b389c5bc | ||
|
|
9dc66b5a65 | ||
|
|
3447dd3615 | ||
|
|
42cb106377 | ||
|
|
027382f891 | ||
|
|
931d41bff1 | ||
|
|
852aab5758 | ||
|
|
27fe4873f2 | ||
|
|
ee6c81d02a | ||
|
|
59542344e2 | ||
|
|
7b1ce72f86 | ||
|
|
156e09536e | ||
|
|
22341c4ae4 | ||
|
|
e4993bd7e2 | ||
|
|
82aff5f9ec | ||
|
|
403a004e03 | ||
|
|
0b92849996 | ||
|
|
12440ede9c | ||
|
|
fc3f722df5 | ||
|
|
84ef7a0171 | ||
|
|
66b1d09362 | ||
|
|
3ae01db226 | ||
|
|
048e5774e8 | ||
|
|
b47e8e6770 | ||
|
|
ef17c82998 | ||
|
|
9aeb5df5fe | ||
|
|
7ffba7b552 | ||
|
|
06473bb1b5 | ||
|
|
bf5c048502 | ||
|
|
eaed08ae79 | ||
|
|
e0fdc4c5e8 | ||
|
|
590bec57f4 | ||
|
|
3110d342c7 | ||
|
|
39aae28eb4 | ||
|
|
dcccfe2591 | ||
|
|
38f5e8f423 | ||
|
|
74f14182ea | ||
|
|
bbc1e7804e | ||
|
|
c6320b29e4 | ||
|
|
1a90408e8c | ||
|
|
07134c50c8 | ||
|
|
b36d4a15b0 | ||
|
|
d8162ce79d | ||
|
|
e11ef54bda | ||
|
|
9a07b0623e | ||
|
|
f450e2e79d | ||
|
|
329946f162 | ||
|
|
588399e415 | ||
|
|
4523885268 | ||
|
|
de81b0cd38 | ||
|
|
4fce296e3f | ||
|
|
9d48d7bbd1 | ||
|
|
c56f263618 | ||
|
|
fb2382fbc3 | ||
|
|
c92a5a8704 | ||
|
|
d7cf3147b7 | ||
|
|
bf4d35c705 | ||
|
|
4e97e9c7cf | ||
|
|
a3fcc3b28d | ||
|
|
cfbd068dd5 | ||
|
|
8aed23fe0a | ||
|
|
c016c41c71 | ||
|
|
f1a5e53f06 | ||
|
|
1e94e0221f | ||
|
|
543865c96b | ||
|
|
b8e3f0bc13 | ||
|
|
643cedb200 | ||
|
|
91620c378a | ||
|
|
b732135795 | ||
|
|
9384a081f9 | ||
|
|
edab268d50 | ||
|
|
e4fad70a57 | ||
|
|
1a09fff991 | ||
|
|
b85105d2ec | ||
|
|
f7ac28a935 | ||
|
|
9532f342a6 | ||
|
|
0ee37aa0aa | ||
|
|
8a26c8b4e0 | ||
|
|
2cb59b0f45 | ||
|
|
2729f3d207 | ||
|
|
59155ce9f6 | ||
|
|
caf6c65de7 | ||
|
|
147d594b38 | ||
|
|
f18078a1eb | ||
|
|
fe4eb13601 | ||
|
|
161ab05533 | ||
|
|
2c537e24cc | ||
|
|
0fe349b5f8 | ||
|
|
09dc3c7225 | ||
|
|
498134b7ee | ||
|
|
0152814a00 | ||
|
|
0b3fab256b | ||
|
|
212ce4d331 | ||
|
|
491b1e4968 | ||
|
|
8b01b53d89 | ||
|
|
f9a5867d3e | ||
|
|
4149627f19 | ||
|
|
7d24146df7 | ||
|
|
1c6ef3666c | ||
|
|
16d933fcf5 | ||
|
|
a9cc56b2ac | ||
|
|
4de6c26ff9 | ||
|
|
98856e05d6 | ||
|
|
edf46c06d0 | ||
|
|
9cfce61f36 | ||
|
|
bdb9a4d1a7 | ||
|
|
82e0c0ced6 | ||
|
|
6a723b50c7 | ||
|
|
6208eb7bbf | ||
|
|
43db446dfa | ||
|
|
3c8fdbf107 | ||
|
|
54fb47ea6a | ||
|
|
efadfeda96 | ||
|
|
90198f7563 | ||
|
|
15084dff9d | ||
|
|
eea1379a74 | ||
|
|
3e80c0d43e | ||
|
|
76a366e05a | ||
|
|
07f72990a9 | ||
|
|
402feffe85 | ||
|
|
5cdd7ccdb8 | ||
|
|
f1c3ebfe0f | ||
|
|
c7e4e41a7a | ||
|
|
311a823a4a | ||
|
|
8c836aeecf | ||
|
|
3abd205f94 | ||
|
|
0527fb9335 | ||
|
|
d5256f89b6 | ||
|
|
3f84746d66 | ||
|
|
c39f687fca | ||
|
|
dd35e724dd | ||
|
|
43599a9e78 | ||
|
|
53ed52dc59 | ||
|
|
24999019e0 | ||
|
|
12d2526edb | ||
|
|
81b812d94c | ||
|
|
9409b49ea2 | ||
|
|
1392170dbf | ||
|
|
0a940b3cb4 | ||
|
|
6aba43a9b0 | ||
|
|
8a3a6a901a | ||
|
|
18a301a214 | ||
|
|
53157bc634 | ||
|
|
76a9dc61f0 | ||
|
|
16d7e13c72 | ||
|
|
5ed58ae595 | ||
|
|
cecd4b166c | ||
|
|
4ddcdd02d6 | ||
|
|
43575da537 | ||
|
|
fe67a0d239 | ||
|
|
8caa73df6a | ||
|
|
ee4cae97d5 | ||
|
|
2e3787adff | ||
|
|
81b211d1b7 | ||
|
|
1ad72261f1 | ||
|
|
914287d31b | ||
|
|
75bb6ad456 | ||
|
|
04111da3f3 | ||
|
|
2464cf6fe9 | ||
|
|
d34e6c02a1 | ||
|
|
e6611c4830 | ||
|
|
2d23b1ae69 | ||
|
|
5eb03d5e09 | ||
|
|
9f8ef1737e | ||
|
|
1991d618a3 | ||
|
|
2045b739a9 | ||
|
|
53e3dd8548 | ||
|
|
78c9056173 | ||
|
|
8d56e412ef | ||
|
|
3400be18a6 |
10
.github/ISSUE_TEMPLATE.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
Thank you for taking the time to report an issue! We're glad to have you involved with Ruff.
|
||||
|
||||
If you're filing a bug report, please consider including the following information:
|
||||
|
||||
- A minimal code snippet that reproduces the bug.
|
||||
- The command you invoked (e.g., `ruff /path/to/file.py --fix`), ideally including the `--isolated` flag.
|
||||
- The current Ruff settings (any relevant sections from your `pyproject.toml`).
|
||||
- The current Ruff version (`ruff --version`).
|
||||
-->
|
||||
79
.github/workflows/ci.yaml
vendored
79
.github/workflows/ci.yaml
vendored
@@ -26,25 +26,12 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo build --all --release
|
||||
- run: ./target/release/ruff_dev generate-all
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo build --all
|
||||
- run: ./target/debug/ruff_dev generate-all
|
||||
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet src/registry_gen.rs || echo "::error file=src/registry_gen.rs::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo +nightly dev generate-all'."
|
||||
- run: git diff --exit-code -- README.md src/registry_gen.rs ruff.schema.json
|
||||
- run: git diff --exit-code -- README.md ruff.schema.json
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -57,18 +44,6 @@ jobs:
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo fmt --all --check
|
||||
|
||||
cargo_clippy:
|
||||
@@ -83,20 +58,9 @@ jobs:
|
||||
override: true
|
||||
components: clippy
|
||||
target: wasm32-unknown-unknown
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
|
||||
- run: cargo clippy --workspace --target wasm32-unknown-unknown --all-features -- -D warnings -W clippy::pedantic
|
||||
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings -W clippy::pedantic
|
||||
|
||||
cargo-test:
|
||||
name: "cargo test"
|
||||
@@ -108,25 +72,17 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo install cargo-insta
|
||||
- run: pip install black[d]==22.12.0
|
||||
- name: Run tests
|
||||
run: |
|
||||
cargo insta test --all --delete-unreferenced-snapshots
|
||||
git diff --exit-code
|
||||
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
|
||||
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
|
||||
# Check for broken links in the documentation.
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
- run: RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps
|
||||
|
||||
# TODO(charlie): Re-enable the `wasm-pack` tests.
|
||||
# See: https://github.com/charliermarsh/ruff/issues/1425
|
||||
@@ -168,22 +124,11 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: nightly-2022-11-01
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- run: pip install maturin
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: maturin build -b bin
|
||||
|
||||
typos:
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- uses: jetli/wasm-bindgen-action@v0.2.0
|
||||
- name: "Run wasm-pack"
|
||||
run: wasm-pack build --target web --out-dir playground/src/pkg
|
||||
run: wasm-pack build --target web --out-dir playground/src/pkg . -- -p ruff
|
||||
- name: "Install Node dependencies"
|
||||
run: npm ci
|
||||
working-directory: playground
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.211
|
||||
rev: v0.0.222
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.222
|
||||
|
||||
### `--max-complexity` has been removed from the CLI ([#1877](https://github.com/charliermarsh/ruff/pull/1877))
|
||||
|
||||
The McCabe plugin's `--max-complexity` setting has been removed from the CLI, for consistency with
|
||||
the treatment of other, similar settings.
|
||||
|
||||
To set the maximum complexity, use the `max-complexity` property in your `pyproject.toml` file,
|
||||
like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
```
|
||||
|
||||
## 0.0.181
|
||||
|
||||
### Files excluded by `.gitignore` are now ignored ([#1234](https://github.com/charliermarsh/ruff/pull/1234))
|
||||
|
||||
@@ -56,33 +56,36 @@ prior to merging.
|
||||
|
||||
There are four phases to adding a new lint rule:
|
||||
|
||||
1. Define the rule in `src/registry.rs`.
|
||||
2. Define the _logic_ for triggering the rule in `src/checkers/ast.rs` (for AST-based checks),
|
||||
1. Define the violation struct in `src/violations.rs` (e.g., `ModuleImportNotAtTopOfFile`).
|
||||
2. Map the violation struct to a rule code in `src/registry.rs` (e.g., `E402`).
|
||||
3. Define the logic for triggering the violation in `src/checkers/ast.rs` (for AST-based checks),
|
||||
`src/checkers/tokens.rs` (for token-based checks), or `src/checkers/lines.rs` (for text-based checks).
|
||||
3. Add a test fixture.
|
||||
4. Update the generated files (documentation and generated code).
|
||||
4. Add a test fixture.
|
||||
5. Update the generated files (documentation and generated code).
|
||||
|
||||
To define the rule, open up `src/registry.rs`. You'll need to define both a `CheckCode` and
|
||||
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
|
||||
pattern implemented therein.
|
||||
To define the violation, open up `src/violations.rs`, and define a new struct using the
|
||||
`define_violation!` macro. There are plenty of examples in that file, so feel free to pattern-match
|
||||
against the existing structs.
|
||||
|
||||
To trigger the rule, you'll likely want to augment the logic in `src/checkers/ast.rs`, which defines
|
||||
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
|
||||
lint-rule violations as it goes. If you need to inspect the AST, you can run
|
||||
`cargo +nightly dev print-ast` with a Python file. Grep for the `Check::new` invocations to
|
||||
understand how other, similar rules are implemented.
|
||||
To trigger the violation, you'll likely want to augment the logic in `src/checkers/ast.rs`, which
|
||||
defines the Python AST visitor, responsible for iterating over the abstract syntax tree and
|
||||
collecting diagnostics as it goes.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures/[plugin-name]`, named to match
|
||||
the `CheckCode` you defined earlier (e.g., `E402.py`). This file should contain a variety of
|
||||
violations and non-violations designed to evaluate and demonstrate the behavior of your lint rule.
|
||||
If you need to inspect the AST, you can run `cargo +nightly dev print-ast` with a Python file. Grep
|
||||
for the `Check::new` invocations to understand how other, similar rules are implemented.
|
||||
|
||||
To add a test fixture, create a file under `resources/test/fixtures/[origin]`, named to match
|
||||
the code you defined earlier (e.g., `resources/test/fixtures/pycodestyle/E402.py`). This file should
|
||||
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
|
||||
of your lint rule.
|
||||
|
||||
Run `cargo +nightly dev generate-all` to generate the code for your new fixture. Then run Ruff
|
||||
locally with (e.g.) `cargo run resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
|
||||
|
||||
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
|
||||
`test_case` macro in the relevant `src/[plugin-name]/mod.rs` file. Then, run `cargo test`. Your
|
||||
test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the generated
|
||||
snapshot, then commit the snapshot file alongside the rest of your changes.
|
||||
`test_case` macro in the relevant `src/[origin]/mod.rs` file. Then, run `cargo test --all`.
|
||||
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
|
||||
generated snapshot, then commit the snapshot file alongside the rest of your changes.
|
||||
|
||||
Finally, regenerate the documentation and generated code with `cargo +nightly dev generate-all`.
|
||||
|
||||
|
||||
165
Cargo.lock
generated
165
Cargo.lock
generated
@@ -367,15 +367,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codegen"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@@ -744,10 +735,11 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.211-dev.0"
|
||||
version = "0.0.222-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
"colored",
|
||||
"configparser",
|
||||
"once_cell",
|
||||
"regex",
|
||||
@@ -757,7 +749,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1294,6 +1286,15 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom8"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.0.0"
|
||||
@@ -1364,10 +1365,31 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
name = "num_enum"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -1620,6 +1642,17 @@ dependencies = [
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
@@ -1873,27 +1906,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"atty",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"cachedir",
|
||||
"cfg-if 1.0.0",
|
||||
"chrono",
|
||||
"clap 4.0.32",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored",
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"criterion",
|
||||
"dirs 4.0.0",
|
||||
"fern",
|
||||
"filetime",
|
||||
"getrandom 0.2.8",
|
||||
"glob",
|
||||
"globset",
|
||||
@@ -1905,13 +1930,10 @@ dependencies = [
|
||||
"log",
|
||||
"natord",
|
||||
"nohash-hasher",
|
||||
"notify",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"quick-junit",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ropey",
|
||||
"ruff_macros",
|
||||
@@ -1923,33 +1945,65 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"similar",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"textwrap",
|
||||
"titlecase",
|
||||
"toml",
|
||||
"update-informer",
|
||||
"ureq",
|
||||
"walkdir",
|
||||
"toml_edit",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.222"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"atty",
|
||||
"bincode",
|
||||
"cachedir",
|
||||
"chrono",
|
||||
"clap 4.0.32",
|
||||
"clap_complete_command",
|
||||
"clearscreen",
|
||||
"colored",
|
||||
"filetime",
|
||||
"glob",
|
||||
"ignore",
|
||||
"itertools",
|
||||
"log",
|
||||
"notify",
|
||||
"path-absolutize",
|
||||
"quick-junit",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ruff",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"strum",
|
||||
"textwrap",
|
||||
"update-informer",
|
||||
"ureq",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
"codegen",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"once_cell",
|
||||
"ruff",
|
||||
"ruff_cli",
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
"rustpython-parser",
|
||||
@@ -1962,8 +2016,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -2004,8 +2059,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -2014,8 +2069,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"bitflags",
|
||||
@@ -2039,8 +2094,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2049,15 +2104,15 @@ dependencies = [
|
||||
"lz4_flex",
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num_enum",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=4d53c7cb27c0379adf8b51c4d3d0d2174f41d590#4d53c7cb27c0379adf8b51c4d3d0d2174f41d590"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython.git?rev=acbc517b55406c76da83d7b2711941d8d3f65b87#acbc517b55406c76da83d7b2711941d8d3f65b87"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2504,6 +2559,28 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808b51e57d0ef8f71115d8f3a01e7d3750d01c79cac4b3eda910f4389fdf92fd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34cc558345efd7e88b9eda9626df2138b80bb46a7606f695e751c892bc7dac6"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"nom8",
|
||||
"serde",
|
||||
"toml_datetime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "1.6.3"
|
||||
|
||||
39
Cargo.toml
39
Cargo.toml
@@ -2,11 +2,13 @@
|
||||
members = [
|
||||
"flake8_to_ruff",
|
||||
"ruff_dev",
|
||||
"ruff_cli",
|
||||
]
|
||||
default-members = [".", "ruff_cli"]
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
@@ -19,22 +21,17 @@ license = "MIT"
|
||||
[lib]
|
||||
name = "ruff"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
anyhow = { version = "1.0.66" }
|
||||
atty = { version = "0.2.14" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "1.3.2" }
|
||||
cachedir = { version = "0.3.0" }
|
||||
cfg-if = { version = "1.0.0" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.4.0" }
|
||||
clap = { version = "4.0.1", features = ["derive", "env"] }
|
||||
colored = { version = "2.0.0" }
|
||||
dirs = { version = "4.0.0" }
|
||||
fern = { version = "0.6.1" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
globset = { version = "0.4.9" }
|
||||
ignore = { version = "0.4.18" }
|
||||
@@ -43,36 +40,26 @@ libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a87
|
||||
log = { version = "0.4.17" }
|
||||
natord = { version = "1.0.9" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
notify = { version = "5.0.0" }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = "0.2.15"
|
||||
once_cell = { version = "1.16.0" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.211", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.222", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4d53c7cb27c0379adf8b51c4d3d0d2174f41d590" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "acbc517b55406c76da83d7b2711941d8d3f65b87" }
|
||||
schemars = { version = "0.8.11" }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
titlecase = { version = "2.2.1" }
|
||||
toml = { version = "0.5.9" }
|
||||
walkdir = { version = "2.3.2" }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
clearscreen = { version = "2.0.0" }
|
||||
rayon = { version = "1.5.3" }
|
||||
update-informer = { version = "0.6.0", default-features = false, features = ["pypi"], optional = true }
|
||||
toml_edit = { version = "0.17.1", features = ["easy"] }
|
||||
|
||||
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
|
||||
# For (future) wasm-pack support
|
||||
@@ -87,17 +74,11 @@ wasm-bindgen = { version = "0.2.83" }
|
||||
[dev-dependencies]
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
ureq = { version = "2.5.0", features = [] }
|
||||
wasm-bindgen-test = { version = "0.3.33" }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
assert_cmd = { version = "2.0.4" }
|
||||
criterion = { version = "0.4.0" }
|
||||
|
||||
[features]
|
||||
default = ["update-informer"]
|
||||
update-informer = ["dep:update-informer"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"label": "",
|
||||
"message": "Ruff",
|
||||
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"128\" height=\"128\"><path d=\"M115.36 61.84 70.22 50.49 114.45 2.4a1.222 1.222 0 0 0-1.54-1.87L12.3 61.98c-.41.25-.64.72-.57 1.2.06.48.4.87.87 1.01l45.07 13.25-44.29 48.16c-.42.46-.44 1.15-.04 1.61.24.29.58.44.94.44.22 0 .45-.06.65-.19l100.78-63.41c.42-.26.64-.75.56-1.22-.08-.49-.43-.88-.91-.99z\" style=\"fill:#fcc21b\"/></svg>",
|
||||
"logoWidth": 10,
|
||||
"labelColor": "grey",
|
||||
"color": "#E15759"
|
||||
"label": "",
|
||||
"message": "Ruff",
|
||||
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"128\" height=\"128\"><path d=\"M115.36 61.84 70.22 50.49 114.45 2.4a1.222 1.222 0 0 0-1.54-1.87L12.3 61.98c-.41.25-.64.72-.57 1.2.06.48.4.87.87 1.01l45.07 13.25-44.29 48.16c-.42.46-.44 1.15-.04 1.61.24.29.58.44.94.44.22 0 .45-.06.65-.19l100.78-63.41c.42-.26.64-.75.56-1.22-.08-.49-.43-.88-.91-.99z\" style=\"fill:#fcc21b\"/></svg>",
|
||||
"logoWidth": 10,
|
||||
"labelColor": "grey",
|
||||
"color": "#E15759"
|
||||
}
|
||||
|
||||
8
assets/badge/v1.json
Normal file
8
assets/badge/v1.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "",
|
||||
"message": "Ruff",
|
||||
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"109\" height=\"132\" fill=\"none\"><g filter=\"url(#a)\"><path fill=\"#FCC21B\" d=\"m103.642 61.492-45.14-11.35 44.23-48.09a1.222 1.222 0 0 0-1.54-1.87L.582 61.632c-.41.25-.64.72-.57 1.2.06.48.4.87.87 1.01l45.07 13.25-44.29 48.16c-.42.46-.44 1.15-.04 1.61.24.29.58.44.94.44.22 0 .45-.06.65-.19l100.78-63.41c.42-.26.64-.75.56-1.22-.08-.49-.43-.88-.91-.99Z\"/></g><defs><filter id=\"a\" width=\"108.569\" height=\"131.302\" x=\"0\" y=\"0\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feColorMatrix in=\"SourceAlpha\" result=\"hardAlpha\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\"/><feOffset dx=\"4\" dy=\"4\"/><feComposite in2=\"hardAlpha\" operator=\"out\"/><feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/><feBlend in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_7_4\"/><feBlend in=\"SourceGraphic\" in2=\"effect1_dropShadow_7_4\" result=\"shape\"/></filter></defs></svg>",
|
||||
"logoWidth": 10,
|
||||
"labelColor": "grey",
|
||||
"color": "#E15759"
|
||||
}
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.211-dev.0"
|
||||
version = "0.0.222-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "flake8_to_ruff"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.66" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
configparser = { version = "3.0.2" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
regex = { version = "1.6.0" }
|
||||
@@ -18,7 +16,7 @@ serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.24.3" }
|
||||
toml = { version = "0.5.9" }
|
||||
toml_edit = { version = "0.17.1", features = ["easy"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
|
||||
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
|
||||
configuration options that don't exist in Flake8.)
|
||||
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
|
||||
2. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
|
||||
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
|
||||
for the complete list of supported plugins.)
|
||||
|
||||
|
||||
19
flake8_to_ruff/examples/jupyterhub.ini
Normal file
19
flake8_to_ruff/examples/jupyterhub.ini
Normal file
@@ -0,0 +1,19 @@
|
||||
[flake8]
|
||||
# Ignore style and complexity
|
||||
# E: style errors
|
||||
# W: style warnings
|
||||
# C: complexity
|
||||
# D: docstring warnings (unused pydocstyle extension)
|
||||
# F841: local variable assigned but never used
|
||||
ignore = E, C, W, D, F841
|
||||
builtins = c, get_config
|
||||
exclude =
|
||||
.cache,
|
||||
.github,
|
||||
docs,
|
||||
jupyterhub/alembic*,
|
||||
onbuild,
|
||||
scripts,
|
||||
share,
|
||||
tools,
|
||||
setup.py
|
||||
@@ -1,17 +0,0 @@
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod black;
|
||||
pub mod converter;
|
||||
mod parser;
|
||||
pub mod plugin;
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
|
||||
//! Utility to generate Ruff's `pyproject.toml` section from a Flake8 INI file.
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
@@ -11,15 +11,14 @@
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use configparser::ini::Ini;
|
||||
use flake8_to_ruff::black::parse_black_options;
|
||||
use flake8_to_ruff::converter;
|
||||
use flake8_to_ruff::plugin::Plugin;
|
||||
use ruff::flake8_to_ruff;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -37,7 +36,7 @@ struct Cli {
|
||||
pyproject: Option<PathBuf>,
|
||||
/// List of plugins to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Option<Vec<Plugin>>,
|
||||
plugin: Option<Vec<flake8_to_ruff::Plugin>>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -51,13 +50,13 @@ fn main() -> Result<()> {
|
||||
// Read the pyproject.toml file.
|
||||
let black = cli
|
||||
.pyproject
|
||||
.map(parse_black_options)
|
||||
.map(flake8_to_ruff::parse_black_options)
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
// Create Ruff's pyproject.toml section.
|
||||
let pyproject = converter::convert(&config, black.as_ref(), cli.plugin)?;
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
let pyproject = flake8_to_ruff::convert(&config, black.as_ref(), cli.plugin)?;
|
||||
println!("{}", toml_edit::easy::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { DEFAULT_PYTHON_SOURCE } from "../constants";
|
||||
import init, { check, Check, currentVersion, defaultSettings } from "../pkg";
|
||||
import init, {
|
||||
check,
|
||||
Diagnostic,
|
||||
currentVersion,
|
||||
defaultSettings,
|
||||
} from "../pkg";
|
||||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import Header from "./Header";
|
||||
import { useTheme } from "./theme";
|
||||
@@ -18,7 +23,7 @@ export default function Editor() {
|
||||
const [edit, setEdit] = useState<number>(0);
|
||||
const [settingsSource, setSettingsSource] = useState<string | null>(null);
|
||||
const [pythonSource, setPythonSource] = useState<string | null>(null);
|
||||
const [checks, setChecks] = useState<Check[]>([]);
|
||||
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [theme, setTheme] = useTheme();
|
||||
|
||||
@@ -32,25 +37,25 @@ export default function Editor() {
|
||||
}
|
||||
|
||||
let config: any;
|
||||
let checks: Check[];
|
||||
let diagnostics: Diagnostic[];
|
||||
|
||||
try {
|
||||
config = JSON.parse(settingsSource);
|
||||
} catch (e) {
|
||||
setChecks([]);
|
||||
setDiagnostics([]);
|
||||
setError((e as Error).message);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
checks = check(pythonSource, config);
|
||||
diagnostics = check(pythonSource, config);
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setChecks(checks);
|
||||
setDiagnostics(diagnostics);
|
||||
}, [initialized, settingsSource, pythonSource]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -122,7 +127,7 @@ export default function Editor() {
|
||||
visible={tab === "Source"}
|
||||
source={pythonSource}
|
||||
theme={theme}
|
||||
checks={checks}
|
||||
diagnostics={diagnostics}
|
||||
onChange={handlePythonSourceChange}
|
||||
/>
|
||||
<SettingsEditor
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { MarkerSeverity, MarkerTag } from "monaco-editor";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Check } from "../pkg";
|
||||
import { Diagnostic } from "../pkg";
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export default function SourceEditor({
|
||||
visible,
|
||||
source,
|
||||
theme,
|
||||
checks,
|
||||
diagnostics,
|
||||
onChange,
|
||||
}: {
|
||||
visible: boolean;
|
||||
source: string;
|
||||
checks: Check[];
|
||||
diagnostics: Diagnostic[];
|
||||
theme: Theme;
|
||||
onChange: (pythonSource: string) => void;
|
||||
}) {
|
||||
@@ -33,15 +33,15 @@ export default function SourceEditor({
|
||||
editor.setModelMarkers(
|
||||
model,
|
||||
"owner",
|
||||
checks.map((check) => ({
|
||||
startLineNumber: check.location.row,
|
||||
startColumn: check.location.column + 1,
|
||||
endLineNumber: check.end_location.row,
|
||||
endColumn: check.end_location.column + 1,
|
||||
message: `${check.code}: ${check.message}`,
|
||||
diagnostics.map((diagnostic) => ({
|
||||
startLineNumber: diagnostic.location.row,
|
||||
startColumn: diagnostic.location.column + 1,
|
||||
endLineNumber: diagnostic.end_location.row,
|
||||
endColumn: diagnostic.end_location.column + 1,
|
||||
message: `${diagnostic.code}: ${diagnostic.message}`,
|
||||
severity: MarkerSeverity.Error,
|
||||
tags:
|
||||
check.code === "F401" || check.code === "F841"
|
||||
diagnostic.code === "F401" || diagnostic.code === "F841"
|
||||
? [MarkerTag.Unnecessary]
|
||||
: [],
|
||||
})),
|
||||
@@ -52,7 +52,7 @@ export default function SourceEditor({
|
||||
{
|
||||
// @ts-expect-error: The type definition is wrong.
|
||||
provideCodeActions: function (model, position) {
|
||||
const actions = checks
|
||||
const actions = diagnostics
|
||||
.filter((check) => position.startLineNumber === check.location.row)
|
||||
.filter((check) => check.fix)
|
||||
.map((check) => ({
|
||||
@@ -89,7 +89,7 @@ export default function SourceEditor({
|
||||
return () => {
|
||||
codeActionProvider?.dispose();
|
||||
};
|
||||
}, [checks, monaco]);
|
||||
}, [diagnostics, monaco]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=0.14,<0.15"]
|
||||
requires = ["maturin>=0.14.10,<0.15"]
|
||||
# We depend on >=0.14.10 because we specify name in
|
||||
# [package.metadata.maturin] in ruff_cli/Cargo.toml.
|
||||
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.211"
|
||||
version = "0.0.222"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [
|
||||
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
|
||||
@@ -35,6 +38,8 @@ urls = { repository = "https://github.com/charliermarsh/ruff" }
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
manifest-path = "ruff_cli/Cargo.toml"
|
||||
python-source = "python"
|
||||
strip = true
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
9
resources/test/fixtures/flake8_annotations/allow_nested_overload.py
vendored
Normal file
9
resources/test/fixtures/flake8_annotations/allow_nested_overload.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
class C:
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(self, x: int, y: int) -> None:
|
||||
...
|
||||
|
||||
def f(self, x, y):
|
||||
pass
|
||||
23
resources/test/fixtures/flake8_bandit/S113.py
vendored
Normal file
23
resources/test/fixtures/flake8_bandit/S113.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import requests
|
||||
|
||||
requests.get('https://gmail.com')
|
||||
requests.get('https://gmail.com', timeout=None)
|
||||
requests.get('https://gmail.com', timeout=5)
|
||||
requests.post('https://gmail.com')
|
||||
requests.post('https://gmail.com', timeout=None)
|
||||
requests.post('https://gmail.com', timeout=5)
|
||||
requests.put('https://gmail.com')
|
||||
requests.put('https://gmail.com', timeout=None)
|
||||
requests.put('https://gmail.com', timeout=5)
|
||||
requests.delete('https://gmail.com')
|
||||
requests.delete('https://gmail.com', timeout=None)
|
||||
requests.delete('https://gmail.com', timeout=5)
|
||||
requests.patch('https://gmail.com')
|
||||
requests.patch('https://gmail.com', timeout=None)
|
||||
requests.patch('https://gmail.com', timeout=5)
|
||||
requests.options('https://gmail.com')
|
||||
requests.options('https://gmail.com', timeout=None)
|
||||
requests.options('https://gmail.com', timeout=5)
|
||||
requests.head('https://gmail.com')
|
||||
requests.head('https://gmail.com', timeout=None)
|
||||
requests.head('https://gmail.com', timeout=5)
|
||||
52
resources/test/fixtures/flake8_bandit/S324.py
vendored
Normal file
52
resources/test/fixtures/flake8_bandit/S324.py
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import hashlib
|
||||
from hashlib import new as hashlib_new
|
||||
from hashlib import sha1 as hashlib_sha1
|
||||
|
||||
# Invalid
|
||||
|
||||
hashlib.new('md5')
|
||||
|
||||
hashlib.new('md4', b'test')
|
||||
|
||||
hashlib.new(name='md5', data=b'test')
|
||||
|
||||
hashlib.new('MD4', data=b'test')
|
||||
|
||||
hashlib.new('sha1')
|
||||
|
||||
hashlib.new('sha1', data=b'test')
|
||||
|
||||
hashlib.new('sha', data=b'test')
|
||||
|
||||
hashlib.new(name='SHA', data=b'test')
|
||||
|
||||
hashlib.sha(data=b'test')
|
||||
|
||||
hashlib.md5()
|
||||
|
||||
hashlib_new('sha1')
|
||||
|
||||
hashlib_sha1('sha1')
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib.new('sha1', usedforsecurity=True)
|
||||
|
||||
# Valid
|
||||
|
||||
hashlib.new('sha256')
|
||||
|
||||
hashlib.new('SHA512')
|
||||
|
||||
hashlib.sha256(data=b'test')
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib_new(name='sha1', usedforsecurity=False)
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib_sha1(name='sha1', usedforsecurity=False)
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib.md4(usedforsecurity=False)
|
||||
|
||||
# usedforsecurity arg only available in Python 3.9+
|
||||
hashlib.new(name='sha256', usedforsecurity=False)
|
||||
40
resources/test/fixtures/flake8_bandit/S501.py
vendored
Normal file
40
resources/test/fixtures/flake8_bandit/S501.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import httpx
|
||||
import requests
|
||||
|
||||
requests.get('https://gmail.com', timeout=30, verify=True)
|
||||
requests.get('https://gmail.com', timeout=30, verify=False)
|
||||
requests.post('https://gmail.com', timeout=30, verify=True)
|
||||
requests.post('https://gmail.com', timeout=30, verify=False)
|
||||
requests.put('https://gmail.com', timeout=30, verify=True)
|
||||
requests.put('https://gmail.com', timeout=30, verify=False)
|
||||
requests.delete('https://gmail.com', timeout=30, verify=True)
|
||||
requests.delete('https://gmail.com', timeout=30, verify=False)
|
||||
requests.patch('https://gmail.com', timeout=30, verify=True)
|
||||
requests.patch('https://gmail.com', timeout=30, verify=False)
|
||||
requests.options('https://gmail.com', timeout=30, verify=True)
|
||||
requests.options('https://gmail.com', timeout=30, verify=False)
|
||||
requests.head('https://gmail.com', timeout=30, verify=True)
|
||||
requests.head('https://gmail.com', timeout=30, verify=False)
|
||||
|
||||
httpx.request('GET', 'https://gmail.com', verify=True)
|
||||
httpx.request('GET', 'https://gmail.com', verify=False)
|
||||
httpx.get('https://gmail.com', verify=True)
|
||||
httpx.get('https://gmail.com', verify=False)
|
||||
httpx.options('https://gmail.com', verify=True)
|
||||
httpx.options('https://gmail.com', verify=False)
|
||||
httpx.head('https://gmail.com', verify=True)
|
||||
httpx.head('https://gmail.com', verify=False)
|
||||
httpx.post('https://gmail.com', verify=True)
|
||||
httpx.post('https://gmail.com', verify=False)
|
||||
httpx.put('https://gmail.com', verify=True)
|
||||
httpx.put('https://gmail.com', verify=False)
|
||||
httpx.patch('https://gmail.com', verify=True)
|
||||
httpx.patch('https://gmail.com', verify=False)
|
||||
httpx.delete('https://gmail.com', verify=True)
|
||||
httpx.delete('https://gmail.com', verify=False)
|
||||
httpx.stream('https://gmail.com', verify=True)
|
||||
httpx.stream('https://gmail.com', verify=False)
|
||||
httpx.Client()
|
||||
httpx.Client(verify=False)
|
||||
httpx.AsyncClient()
|
||||
httpx.AsyncClient(verify=False)
|
||||
31
resources/test/fixtures/flake8_bandit/S506.py
vendored
Normal file
31
resources/test/fixtures/flake8_bandit/S506.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
import yaml
|
||||
from yaml import CSafeLoader
|
||||
from yaml import SafeLoader
|
||||
from yaml import SafeLoader as NewSafeLoader
|
||||
|
||||
|
||||
def test_yaml_load():
|
||||
ystr = yaml.dump({"a": 1, "b": 2, "c": 3})
|
||||
y = yaml.load(ystr)
|
||||
yaml.dump(y)
|
||||
try:
|
||||
y = yaml.load(ystr, Loader=yaml.CSafeLoader)
|
||||
except AttributeError:
|
||||
# CSafeLoader only exists if you build yaml with LibYAML
|
||||
y = yaml.load(ystr, Loader=yaml.SafeLoader)
|
||||
|
||||
|
||||
def test_json_load():
|
||||
# no issue should be found
|
||||
j = json.load("{}")
|
||||
|
||||
|
||||
yaml.load("{}", Loader=yaml.Loader)
|
||||
|
||||
# no issue should be found
|
||||
yaml.load("{}", SafeLoader)
|
||||
yaml.load("{}", yaml.SafeLoader)
|
||||
yaml.load("{}", CSafeLoader)
|
||||
yaml.load("{}", yaml.CSafeLoader)
|
||||
yaml.load("{}", NewSafeLoader)
|
||||
6
resources/test/fixtures/flake8_bandit/S508.py
vendored
Normal file
6
resources/test/fixtures/flake8_bandit/S508.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from pysnmp.hlapi import CommunityData
|
||||
|
||||
CommunityData("public", mpModel=0) # S508
|
||||
CommunityData("public", mpModel=1) # S508
|
||||
|
||||
CommunityData("public", mpModel=2) # OK
|
||||
7
resources/test/fixtures/flake8_bandit/S509.py
vendored
Normal file
7
resources/test/fixtures/flake8_bandit/S509.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from pysnmp.hlapi import UsmUserData
|
||||
|
||||
|
||||
insecure = UsmUserData("securityName") # S509
|
||||
auth_no_priv = UsmUserData("securityName", "authName") # S509
|
||||
|
||||
less_insecure = UsmUserData("securityName", "authName", "privName") # OK
|
||||
29
resources/test/fixtures/flake8_bandit/S701.py
vendored
Normal file
29
resources/test/fixtures/flake8_bandit/S701.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import jinja2
|
||||
from jinja2 import Environment, select_autoescape
|
||||
templateLoader = jinja2.FileSystemLoader( searchpath="/" )
|
||||
something = ''
|
||||
|
||||
Environment(loader=templateLoader, load=templateLoader, autoescape=True)
|
||||
templateEnv = jinja2.Environment(autoescape=True,
|
||||
loader=templateLoader )
|
||||
Environment(loader=templateLoader, load=templateLoader, autoescape=something) # S701
|
||||
templateEnv = jinja2.Environment(autoescape=False, loader=templateLoader ) # S701
|
||||
Environment(loader=templateLoader,
|
||||
load=templateLoader,
|
||||
autoescape=False) # S701
|
||||
|
||||
Environment(loader=templateLoader, # S701
|
||||
load=templateLoader)
|
||||
|
||||
Environment(loader=templateLoader, autoescape=select_autoescape())
|
||||
|
||||
Environment(loader=templateLoader,
|
||||
autoescape=select_autoescape(['html', 'htm', 'xml']))
|
||||
|
||||
Environment(loader=templateLoader,
|
||||
autoescape=jinja2.select_autoescape(['html', 'htm', 'xml']))
|
||||
|
||||
|
||||
def fake_func():
|
||||
return 'foobar'
|
||||
Environment(loader=templateLoader, autoescape=fake_func()) # S701
|
||||
114
resources/test/fixtures/flake8_bugbear/B023.py
vendored
114
resources/test/fixtures/flake8_bugbear/B023.py
vendored
@@ -25,10 +25,10 @@ for x in range(3):
|
||||
|
||||
|
||||
def check_inside_functions_too():
|
||||
ls = [lambda: x for x in range(2)]
|
||||
st = {lambda: x for x in range(2)}
|
||||
gn = (lambda: x for x in range(2))
|
||||
dt = {x: lambda: x for x in range(2)}
|
||||
ls = [lambda: x for x in range(2)] # error
|
||||
st = {lambda: x for x in range(2)} # error
|
||||
gn = (lambda: x for x in range(2)) # error
|
||||
dt = {x: lambda: x for x in range(2)} # error
|
||||
|
||||
|
||||
async def pointless_async_iterable():
|
||||
@@ -37,9 +37,9 @@ async def pointless_async_iterable():
|
||||
|
||||
async def container_for_problems():
|
||||
async for x in pointless_async_iterable():
|
||||
functions.append(lambda: x)
|
||||
functions.append(lambda: x) # error
|
||||
|
||||
[lambda: x async for x in pointless_async_iterable()]
|
||||
[lambda: x async for x in pointless_async_iterable()] # error
|
||||
|
||||
|
||||
a = 10
|
||||
@@ -47,10 +47,10 @@ b = 0
|
||||
while True:
|
||||
a = a_ = a - 1
|
||||
b += 1
|
||||
functions.append(lambda: a)
|
||||
functions.append(lambda: a_)
|
||||
functions.append(lambda: b)
|
||||
functions.append(lambda: c) # not a name error because of late binding!
|
||||
functions.append(lambda: a) # error
|
||||
functions.append(lambda: a_) # error
|
||||
functions.append(lambda: b) # error
|
||||
functions.append(lambda: c) # error, but not a name error due to late binding
|
||||
c: bool = a > 3
|
||||
if not c:
|
||||
break
|
||||
@@ -58,7 +58,7 @@ while True:
|
||||
# Nested loops should not duplicate reports
|
||||
for j in range(2):
|
||||
for k in range(3):
|
||||
lambda: j * k
|
||||
lambda: j * k # error
|
||||
|
||||
|
||||
for j, k, l in [(1, 2, 3)]:
|
||||
@@ -80,3 +80,95 @@ for var in range(2):
|
||||
|
||||
for i in range(3):
|
||||
lambda: f"{i}"
|
||||
|
||||
|
||||
# `query` is defined in the function, so also defining it in the loop should be OK.
|
||||
for name in ["a", "b"]:
|
||||
query = name
|
||||
|
||||
def myfunc(x):
|
||||
query = x
|
||||
query_post = x
|
||||
_ = query
|
||||
_ = query_post
|
||||
|
||||
query_post = name # in case iteration order matters
|
||||
|
||||
|
||||
# Bug here because two dict comprehensions reference `name`, one of which is inside
|
||||
# the lambda. This should be totally fine, of course.
|
||||
_ = {
|
||||
k: v
|
||||
for k, v in reduce(
|
||||
lambda data, event: merge_mappings(
|
||||
[data, {name: f(caches, data, event) for name, f in xx}]
|
||||
),
|
||||
events,
|
||||
{name: getattr(group, name) for name in yy},
|
||||
).items()
|
||||
if k in backfill_fields
|
||||
}
|
||||
|
||||
|
||||
# OK to define lambdas if they're immediately consumed, typically as the `key=`
|
||||
# argument or in a consumed `filter()` (even if a comprehension is better style)
|
||||
for x in range(2):
|
||||
# It's not a complete get-out-of-linting-free construct - these should fail:
|
||||
min([None, lambda: x], key=repr)
|
||||
sorted([None, lambda: x], key=repr)
|
||||
any(filter(bool, [None, lambda: x]))
|
||||
list(filter(bool, [None, lambda: x]))
|
||||
all(reduce(bool, [None, lambda: x]))
|
||||
|
||||
# But all these should be OK:
|
||||
min(range(3), key=lambda y: x * y)
|
||||
max(range(3), key=lambda y: x * y)
|
||||
sorted(range(3), key=lambda y: x * y)
|
||||
|
||||
any(map(lambda y: x < y, range(3)))
|
||||
all(map(lambda y: x < y, range(3)))
|
||||
set(map(lambda y: x < y, range(3)))
|
||||
list(map(lambda y: x < y, range(3)))
|
||||
tuple(map(lambda y: x < y, range(3)))
|
||||
sorted(map(lambda y: x < y, range(3)))
|
||||
frozenset(map(lambda y: x < y, range(3)))
|
||||
|
||||
any(filter(lambda y: x < y, range(3)))
|
||||
all(filter(lambda y: x < y, range(3)))
|
||||
set(filter(lambda y: x < y, range(3)))
|
||||
list(filter(lambda y: x < y, range(3)))
|
||||
tuple(filter(lambda y: x < y, range(3)))
|
||||
sorted(filter(lambda y: x < y, range(3)))
|
||||
frozenset(filter(lambda y: x < y, range(3)))
|
||||
|
||||
any(reduce(lambda y: x | y, range(3)))
|
||||
all(reduce(lambda y: x | y, range(3)))
|
||||
set(reduce(lambda y: x | y, range(3)))
|
||||
list(reduce(lambda y: x | y, range(3)))
|
||||
tuple(reduce(lambda y: x | y, range(3)))
|
||||
sorted(reduce(lambda y: x | y, range(3)))
|
||||
frozenset(reduce(lambda y: x | y, range(3)))
|
||||
|
||||
import functools
|
||||
|
||||
any(functools.reduce(lambda y: x | y, range(3)))
|
||||
all(functools.reduce(lambda y: x | y, range(3)))
|
||||
set(functools.reduce(lambda y: x | y, range(3)))
|
||||
list(functools.reduce(lambda y: x | y, range(3)))
|
||||
tuple(functools.reduce(lambda y: x | y, range(3)))
|
||||
sorted(functools.reduce(lambda y: x | y, range(3)))
|
||||
frozenset(functools.reduce(lambda y: x | y, range(3)))
|
||||
|
||||
# OK because the lambda which references a loop variable is defined in a `return`
|
||||
# statement, and after we return the loop variable can't be redefined.
|
||||
# In principle we could do something fancy with `break`, but it's not worth it.
|
||||
def iter_f(names):
|
||||
for name in names:
|
||||
if exists(name):
|
||||
return lambda: name if exists(name) else None
|
||||
|
||||
if foo(name):
|
||||
return [lambda: name] # known false alarm
|
||||
|
||||
if False:
|
||||
return [lambda: i for i in range(3)] # error
|
||||
|
||||
19
resources/test/fixtures/flake8_bugbear/B024.py
vendored
19
resources/test/fixtures/flake8_bugbear/B024.py
vendored
@@ -1,15 +1,16 @@
|
||||
"""
|
||||
Should emit:
|
||||
B024 - on lines 17, 34, 52, 58, 69, 74, 79, 84, 89
|
||||
B024 - on lines 18, 71, 82, 87, 92, 141
|
||||
"""
|
||||
|
||||
import abc
|
||||
import abc as notabc
|
||||
from abc import ABC, ABCMeta
|
||||
from abc import abstractmethod
|
||||
from abc import abstractmethod, abstractproperty
|
||||
from abc import abstractmethod as abstract
|
||||
from abc import abstractmethod as abstractaoeuaoeuaoeu
|
||||
from abc import abstractmethod as notabstract
|
||||
from abc import abstractproperty as notabstract_property
|
||||
|
||||
import foo
|
||||
|
||||
@@ -49,12 +50,24 @@ class Base_6(ABC):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_7(ABC): # error
|
||||
class Base_7(ABC):
|
||||
@notabstract
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_8(ABC):
|
||||
@notabstract_property
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_9(ABC):
|
||||
@abstractproperty
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class MetaBase_1(metaclass=ABCMeta): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
17
resources/test/fixtures/flake8_bugbear/B027.py
vendored
17
resources/test/fixtures/flake8_bugbear/B027.py
vendored
@@ -1,11 +1,12 @@
|
||||
"""
|
||||
Should emit:
|
||||
B027 - on lines 12, 15, 18, 22, 30
|
||||
B027 - on lines 13, 16, 19, 23
|
||||
"""
|
||||
import abc
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from abc import abstractmethod, abstractproperty
|
||||
from abc import abstractmethod as notabstract
|
||||
from abc import abstractproperty as notabstract_property
|
||||
|
||||
|
||||
class AbstractClass(ABC):
|
||||
@@ -42,6 +43,18 @@ class AbstractClass(ABC):
|
||||
def abstract_3(self):
|
||||
...
|
||||
|
||||
@abc.abstractproperty
|
||||
def abstract_4(self):
|
||||
...
|
||||
|
||||
@abstractproperty
|
||||
def abstract_5(self):
|
||||
...
|
||||
|
||||
@notabstract_property
|
||||
def abstract_6(self):
|
||||
...
|
||||
|
||||
def body_1(self):
|
||||
print("foo")
|
||||
...
|
||||
|
||||
@@ -2,3 +2,10 @@ x = list(x for x in range(3))
|
||||
x = list(
|
||||
x for x in range(3)
|
||||
)
|
||||
|
||||
|
||||
def list(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
list(x for x in range(3))
|
||||
|
||||
@@ -2,3 +2,10 @@ x = set(x for x in range(3))
|
||||
x = set(
|
||||
x for x in range(3)
|
||||
)
|
||||
|
||||
|
||||
def set(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
set(x for x in range(3))
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
s1 = set([1, 2])
|
||||
s2 = set((1, 2))
|
||||
s3 = set([])
|
||||
s4 = set(())
|
||||
s5 = set()
|
||||
set([1, 2])
|
||||
set((1, 2))
|
||||
set([])
|
||||
set(())
|
||||
set()
|
||||
set((1,))
|
||||
set((
|
||||
1,
|
||||
))
|
||||
set([
|
||||
1,
|
||||
])
|
||||
set(
|
||||
(1,)
|
||||
)
|
||||
set(
|
||||
[1,]
|
||||
)
|
||||
|
||||
@@ -3,3 +3,10 @@ l = list()
|
||||
d1 = dict()
|
||||
d2 = dict(a=1)
|
||||
d3 = dict(**d2)
|
||||
|
||||
|
||||
def list():
|
||||
return [1, 2, 3]
|
||||
|
||||
|
||||
a = list()
|
||||
|
||||
@@ -4,3 +4,10 @@ list(sorted(x))
|
||||
reversed(sorted(x))
|
||||
reversed(sorted(x, key=lambda e: e))
|
||||
reversed(sorted(x, reverse=True))
|
||||
|
||||
|
||||
def reversed(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
reversed(sorted(x, reverse=True))
|
||||
|
||||
7
resources/test/fixtures/flake8_pie/PIE794.py
vendored
7
resources/test/fixtures/flake8_pie/PIE794.py
vendored
@@ -31,3 +31,10 @@ class User(BaseModel):
|
||||
@buzz.setter
|
||||
def buzz(self, value: str | int) -> None:
|
||||
...
|
||||
|
||||
|
||||
class User:
|
||||
bar: str = StringField()
|
||||
foo: bool = BooleanField()
|
||||
# ...
|
||||
bar = StringField() # PIE794
|
||||
|
||||
@@ -1,9 +1,76 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
|
||||
def test_xxx():
|
||||
assert 1 == 1 # OK no parameters
|
||||
class Test(unittest.TestCase):
|
||||
def test_xxx(self):
|
||||
assert 1 == 1 # OK no parameters
|
||||
|
||||
def test_assert_true(self):
|
||||
expr = 1
|
||||
msg = "Must be True"
|
||||
self.assertTrue(expr) # Error
|
||||
self.assertTrue(expr=expr) # Error
|
||||
self.assertTrue(expr, msg) # Error
|
||||
self.assertTrue(expr=expr, msg=msg) # Error
|
||||
self.assertTrue(msg=msg, expr=expr) # Error
|
||||
self.assertTrue(*(expr, msg)) # Error, unfixable
|
||||
self.assertTrue(**{"expr": expr, "msg": msg}) # Error, unfixable
|
||||
self.assertTrue(msg=msg, expr=expr, unexpected_arg=False) # Error, unfixable
|
||||
self.assertTrue(msg=msg) # Error, unfixable
|
||||
|
||||
def test_xxx():
|
||||
self.assertEqual(1, 1) # Error
|
||||
def test_assert_false(self):
|
||||
self.assertFalse(True) # Error
|
||||
|
||||
def test_assert_equal(self):
|
||||
self.assertEqual(1, 2) # Error
|
||||
|
||||
def test_assert_not_equal(self):
|
||||
self.assertNotEqual(1, 1) # Error
|
||||
|
||||
def test_assert_greater(self):
|
||||
self.assertGreater(1, 2) # Error
|
||||
|
||||
def test_assert_greater_equal(self):
|
||||
self.assertGreaterEqual(1, 2) # Error
|
||||
|
||||
def test_assert_less(self):
|
||||
self.assertLess(2, 1) # Error
|
||||
|
||||
def test_assert_less_equal(self):
|
||||
self.assertLessEqual(1, 2) # Error
|
||||
|
||||
def test_assert_in(self):
|
||||
self.assertIn(1, [2, 3]) # Error
|
||||
|
||||
def test_assert_not_in(self):
|
||||
self.assertNotIn(2, [2, 3]) # Error
|
||||
|
||||
def test_assert_is_none(self):
|
||||
self.assertIsNone(0) # Error
|
||||
|
||||
def test_assert_is_not_none(self):
|
||||
self.assertIsNotNone(0) # Error
|
||||
|
||||
def test_assert_is(self):
|
||||
self.assertIs([], []) # Error
|
||||
|
||||
def test_assert_is_not(self):
|
||||
self.assertIsNot(1, 1) # Error
|
||||
|
||||
def test_assert_is_instance(self):
|
||||
self.assertIsInstance(1, str) # Error
|
||||
|
||||
def test_assert_is_not_instance(self):
|
||||
self.assertNotIsInstance(1, int) # Error
|
||||
|
||||
def test_assert_regex(self):
|
||||
self.assertRegex("abc", r"def") # Error
|
||||
|
||||
def test_assert_not_regex(self):
|
||||
self.assertNotRegex("abc", r"abc") # Error
|
||||
|
||||
def test_assert_regexp_matches(self):
|
||||
self.assertRegexpMatches("abc", r"def") # Error
|
||||
|
||||
def test_assert_not_regexp_matches(self):
|
||||
self.assertNotRegex("abc", r"abc") # Error
|
||||
|
||||
@@ -17,6 +17,15 @@ def fun_with_params_no_docstring(a, b="""
|
||||
""" """docstring"""):
|
||||
pass
|
||||
|
||||
|
||||
def fun_with_params_no_docstring2(a, b=c[foo():], c=\
|
||||
""" not a docstring """):
|
||||
pass
|
||||
|
||||
|
||||
def function_with_single_docstring(a):
|
||||
"Single line docstring"
|
||||
|
||||
|
||||
def double_inside_single(a):
|
||||
'Double inside "single "'
|
||||
|
||||
@@ -13,11 +13,19 @@ def foo2():
|
||||
|
||||
|
||||
def fun_with_params_no_docstring(a, b='''
|
||||
not a
|
||||
not a
|
||||
''' '''docstring'''):
|
||||
pass
|
||||
|
||||
|
||||
def fun_with_params_no_docstring2(a, b=c[foo():], c=\
|
||||
''' not a docstring '''):
|
||||
pass
|
||||
|
||||
|
||||
def function_with_single_docstring(a):
|
||||
'Single line docstring'
|
||||
|
||||
|
||||
def double_inside_single(a):
|
||||
"Double inside 'single '"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
this_should_raise_Q003 = 'This is a \'string\''
|
||||
this_should_raise_Q003 = 'This is \\ a \\\'string\''
|
||||
this_is_fine = '"This" is a \'string\''
|
||||
this_is_fine = "This is a 'string'"
|
||||
this_is_fine = "\"This\" is a 'string'"
|
||||
|
||||
23
resources/test/fixtures/flake8_simplify/SIM101.py
vendored
Normal file
23
resources/test/fixtures/flake8_simplify/SIM101.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
if isinstance(a, int) or isinstance(a, float): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a, (int, float)) or isinstance(a, bool): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a, int) or isinstance(a, float) or isinstance(b, bool): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(b, bool) or isinstance(a, int) or isinstance(a, float): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a, int) or isinstance(b, bool) or isinstance(a, float): # SIM101
|
||||
pass
|
||||
|
||||
if (isinstance(a, int) or isinstance(a, float)) and isinstance(b, bool): # SIM101
|
||||
pass
|
||||
|
||||
if isinstance(a, int) and isinstance(b, bool) or isinstance(a, float):
|
||||
pass
|
||||
|
||||
if isinstance(a, bool) or isinstance(b, str):
|
||||
pass
|
||||
20
resources/test/fixtures/flake8_simplify/SIM103.py
vendored
Normal file
20
resources/test/fixtures/flake8_simplify/SIM103.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
def f():
|
||||
if a: # SIM103
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
if a: # OK
|
||||
foo()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
if a: # OK
|
||||
return "foo"
|
||||
else:
|
||||
return False
|
||||
93
resources/test/fixtures/flake8_simplify/SIM108.py
vendored
Normal file
93
resources/test/fixtures/flake8_simplify/SIM108.py
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# SIM108
|
||||
if a:
|
||||
b = c
|
||||
else:
|
||||
b = d
|
||||
|
||||
# OK
|
||||
b = c if a else d
|
||||
|
||||
# OK
|
||||
if a:
|
||||
b = c
|
||||
elif c:
|
||||
b = a
|
||||
else:
|
||||
b = d
|
||||
|
||||
# OK
|
||||
if True:
|
||||
pass
|
||||
elif a:
|
||||
b = 1
|
||||
else:
|
||||
b = 2
|
||||
|
||||
# OK (false negative)
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
if a:
|
||||
b = 1
|
||||
else:
|
||||
b = 2
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
# OK
|
||||
if sys.version_info >= (3, 9):
|
||||
randbytes = random.randbytes
|
||||
else:
|
||||
randbytes = _get_random_bytes
|
||||
|
||||
# OK
|
||||
if sys.platform == "darwin":
|
||||
randbytes = random.randbytes
|
||||
else:
|
||||
randbytes = _get_random_bytes
|
||||
|
||||
# OK
|
||||
if sys.platform.startswith("linux"):
|
||||
randbytes = random.randbytes
|
||||
else:
|
||||
randbytes = _get_random_bytes
|
||||
|
||||
|
||||
# OK (includes comments)
|
||||
if x > 0:
|
||||
# test test
|
||||
abc = x
|
||||
else:
|
||||
# test test test
|
||||
abc = -x
|
||||
|
||||
|
||||
# OK (too long)
|
||||
if parser.errno == BAD_FIRST_LINE:
|
||||
req = wrappers.Request(sock, server=self._server)
|
||||
else:
|
||||
req = wrappers.Request(
|
||||
sock,
|
||||
parser.get_method(),
|
||||
parser.get_scheme() or _scheme,
|
||||
parser.get_path(),
|
||||
parser.get_version(),
|
||||
parser.get_query_string(),
|
||||
server=self._server,
|
||||
)
|
||||
|
||||
|
||||
# SIM108
|
||||
if a:
|
||||
b = cccccccccccccccccccccccccccccccccccc
|
||||
else:
|
||||
b = ddddddddddddddddddddddddddddddddddddd
|
||||
|
||||
|
||||
# OK (too long)
|
||||
if True:
|
||||
if a:
|
||||
b = cccccccccccccccccccccccccccccccccccc
|
||||
else:
|
||||
b = ddddddddddddddddddddddddddddddddddddd
|
||||
7
resources/test/fixtures/flake8_simplify/SIM109.py
vendored
Normal file
7
resources/test/fixtures/flake8_simplify/SIM109.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Bad
|
||||
if a == b or a == c:
|
||||
d
|
||||
|
||||
# Good
|
||||
if a in (b, c):
|
||||
d
|
||||
@@ -1,5 +1,6 @@
|
||||
def f():
|
||||
for x in iterable: # SIM110
|
||||
# SIM110
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
return False
|
||||
@@ -20,14 +21,16 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable: # SIM111
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable: # SIM111
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if not x.is_empty():
|
||||
return False
|
||||
return True
|
||||
@@ -45,3 +48,70 @@ def f():
|
||||
if check(x):
|
||||
return "foo"
|
||||
return "bar"
|
||||
|
||||
|
||||
def f():
|
||||
# SIM110
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM110
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
elif x.is_empty():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
elif x.is_empty():
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
def f():
|
||||
for x in iterable: # SIM110
|
||||
# SIM110
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
for el in [1, 2, 3]:
|
||||
if is_true(el):
|
||||
@@ -13,21 +21,97 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable: # SIM111
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable: # SIM 111
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if not x.is_empty():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return "foo"
|
||||
return "bar"
|
||||
|
||||
|
||||
def f():
|
||||
# SIM110
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM110
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
elif x.is_empty():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
for x in iterable:
|
||||
if check(x):
|
||||
return True
|
||||
elif x.is_empty():
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
19
resources/test/fixtures/flake8_simplify/SIM112.py
vendored
Normal file
19
resources/test/fixtures/flake8_simplify/SIM112.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
|
||||
# Bad
|
||||
os.environ['foo']
|
||||
|
||||
os.environ.get('foo')
|
||||
|
||||
os.environ.get('foo', 'bar')
|
||||
|
||||
os.getenv('foo')
|
||||
|
||||
# Good
|
||||
os.environ['FOO']
|
||||
|
||||
os.environ.get('FOO')
|
||||
|
||||
os.environ.get('FOO', 'bar')
|
||||
|
||||
os.getenv('FOO')
|
||||
6
resources/test/fixtures/flake8_simplify/SIM115.py
vendored
Normal file
6
resources/test/fixtures/flake8_simplify/SIM115.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
f = open('foo.txt') # SIM115
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
with open('foo.txt') as f: # OK
|
||||
data = f.read()
|
||||
@@ -2,6 +2,11 @@ with A() as a: # SIM117
|
||||
with B() as b:
|
||||
print("hello")
|
||||
|
||||
with A(): # SIM117
|
||||
with B():
|
||||
with C():
|
||||
print("hello")
|
||||
|
||||
with A() as a:
|
||||
a()
|
||||
with B() as b:
|
||||
|
||||
17
resources/test/fixtures/flake8_simplify/SIM201.py
vendored
Normal file
17
resources/test/fixtures/flake8_simplify/SIM201.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
if not a == b: # SIM201
|
||||
pass
|
||||
|
||||
if not a == (b + c): # SIM201
|
||||
pass
|
||||
|
||||
if not (a + b) == c: # SIM201
|
||||
pass
|
||||
|
||||
if not a != b: # OK
|
||||
pass
|
||||
|
||||
if a == b: # OK
|
||||
pass
|
||||
|
||||
if not a == b: # OK
|
||||
raise ValueError()
|
||||
14
resources/test/fixtures/flake8_simplify/SIM202.py
vendored
Normal file
14
resources/test/fixtures/flake8_simplify/SIM202.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
if not a != b: # SIM202
|
||||
pass
|
||||
|
||||
if not a != (b + c): # SIM202
|
||||
pass
|
||||
|
||||
if not (a + b) != c: # SIM202
|
||||
pass
|
||||
|
||||
if not a == b: # OK
|
||||
pass
|
||||
|
||||
if a != b: # OK
|
||||
pass
|
||||
14
resources/test/fixtures/flake8_simplify/SIM208.py
vendored
Normal file
14
resources/test/fixtures/flake8_simplify/SIM208.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
if not (not a): # SIM208
|
||||
pass
|
||||
|
||||
if not (not (a == b)): # SIM208
|
||||
pass
|
||||
|
||||
if not a: # OK
|
||||
pass
|
||||
|
||||
if not a == b: # OK
|
||||
pass
|
||||
|
||||
if not a != b: # OK
|
||||
pass
|
||||
7
resources/test/fixtures/flake8_simplify/SIM210.py
vendored
Normal file
7
resources/test/fixtures/flake8_simplify/SIM210.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
a = True if b else False # SIM210
|
||||
|
||||
a = True if b != c else False # SIM210
|
||||
|
||||
a = True if b + c else False # SIM210
|
||||
|
||||
a = False if b else True # OK
|
||||
7
resources/test/fixtures/flake8_simplify/SIM211.py
vendored
Normal file
7
resources/test/fixtures/flake8_simplify/SIM211.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
a = False if b else True # SIM211
|
||||
|
||||
a = False if b != c else True # SIM211
|
||||
|
||||
a = False if b + c else True # SIM211
|
||||
|
||||
a = True if b else False # OK
|
||||
7
resources/test/fixtures/flake8_simplify/SIM212.py
vendored
Normal file
7
resources/test/fixtures/flake8_simplify/SIM212.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
c = b if not a else a # SIM212
|
||||
|
||||
c = b + c if not a else a # SIM212
|
||||
|
||||
c = b if not x else a # OK
|
||||
|
||||
c = a if a else b # OK
|
||||
87
resources/test/fixtures/flake8_simplify/SIM401.py
vendored
Normal file
87
resources/test/fixtures/flake8_simplify/SIM401.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
###
|
||||
# Positive cases
|
||||
###
|
||||
|
||||
# SIM401 (pattern-1)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
else:
|
||||
var = "default1"
|
||||
|
||||
# SIM401 (pattern-2)
|
||||
if key not in a_dict:
|
||||
var = "default2"
|
||||
else:
|
||||
var = a_dict[key]
|
||||
|
||||
# SIM401 (default with a complex expression)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
else:
|
||||
var = val1 + val2
|
||||
|
||||
# SIM401 (complex expression in key)
|
||||
if keys[idx] in a_dict:
|
||||
var = a_dict[keys[idx]]
|
||||
else:
|
||||
var = "default"
|
||||
|
||||
# SIM401 (complex expression in dict)
|
||||
if key in dicts[idx]:
|
||||
var = dicts[idx][key]
|
||||
else:
|
||||
var = "default"
|
||||
|
||||
# SIM401 (complex expression in var)
|
||||
if key in a_dict:
|
||||
vars[idx] = a_dict[key]
|
||||
else:
|
||||
vars[idx] = "default"
|
||||
|
||||
###
|
||||
# Negative cases
|
||||
###
|
||||
|
||||
# OK (false negative)
|
||||
if not key in a_dict:
|
||||
var = "default"
|
||||
else:
|
||||
var = a_dict[key]
|
||||
|
||||
# OK (different dict)
|
||||
if key in a_dict:
|
||||
var = other_dict[key]
|
||||
else:
|
||||
var = "default"
|
||||
|
||||
# OK (different key)
|
||||
if key in a_dict:
|
||||
var = a_dict[other_key]
|
||||
else:
|
||||
var = "default"
|
||||
|
||||
# OK (different var)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
else:
|
||||
other_var = "default"
|
||||
|
||||
# OK (extra vars in body)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
var2 = value2
|
||||
else:
|
||||
var = "default"
|
||||
|
||||
# OK (extra vars in orelse)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
else:
|
||||
var2 = value2
|
||||
var = "default"
|
||||
|
||||
# OK (complex default value)
|
||||
if key in a_dict:
|
||||
var = a_dict[key]
|
||||
else:
|
||||
var = foo()
|
||||
@@ -77,6 +77,34 @@ class C:
|
||||
def f(x):
|
||||
...
|
||||
|
||||
def f(self, x):
|
||||
"""Docstring."""
|
||||
|
||||
def f(self, x):
|
||||
"""Docstring."""
|
||||
...
|
||||
|
||||
def f(self, x):
|
||||
pass
|
||||
|
||||
def f(self, x):
|
||||
raise NotImplementedError
|
||||
|
||||
def f(self, x):
|
||||
raise NotImplementedError()
|
||||
|
||||
def f(self, x):
|
||||
raise NotImplementedError("...")
|
||||
|
||||
def f(self, x):
|
||||
raise NotImplemented
|
||||
|
||||
def f(self, x):
|
||||
raise NotImplemented()
|
||||
|
||||
def f(self, x):
|
||||
raise NotImplemented("...")
|
||||
|
||||
###
|
||||
# Unused functions attached to abstract methods (OK).
|
||||
###
|
||||
@@ -153,3 +181,17 @@ def f(a: int, b: int) -> str:
|
||||
|
||||
def f(a, b):
|
||||
return f"{a}{b}"
|
||||
|
||||
|
||||
###
|
||||
# Unused arguments on magic methods.
|
||||
###
|
||||
class C:
|
||||
def __init__(self, x) -> None:
|
||||
print("Hello, world!")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||
print("Hello, world!")
|
||||
|
||||
11
resources/test/fixtures/isort/force_sort_within_sections.py
vendored
Normal file
11
resources/test/fixtures/isort/force_sort_within_sections.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from a import a1 # import_from
|
||||
from c import * # import_from_star
|
||||
import a # import
|
||||
import c.d
|
||||
import b as b1 # import_as
|
||||
|
||||
from ..parent import *
|
||||
from .my import fn
|
||||
from . import my
|
||||
from .my.nested import fn2
|
||||
from ...grandparent import fn3
|
||||
4
resources/test/fixtures/isort/order_by_type_with_custom_classes.py
vendored
Normal file
4
resources/test/fixtures/isort/order_by_type_with_custom_classes.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
from sklearn.svm import func, SVC, CONST, Klass
|
||||
from subprocess import N_CLASS, PIPE, Popen, STDOUT
|
||||
from module import CLASS, Class, CONSTANT, function, BASIC, Apple
|
||||
from torch.nn import SELU, AClass, A_CONSTANT
|
||||
3
resources/test/fixtures/isort/relative_imports_order.py
vendored
Normal file
3
resources/test/fixtures/isort/relative_imports_order.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from ... import a
|
||||
from .. import b
|
||||
from . import c
|
||||
3
resources/test/fixtures/isort/required_imports/comment.py
vendored
Normal file
3
resources/test/fixtures/isort/required_imports/comment.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
x = 1
|
||||
3
resources/test/fixtures/isort/required_imports/docstring.py
vendored
Normal file
3
resources/test/fixtures/isort/required_imports/docstring.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Hello, world!"""
|
||||
|
||||
x = 1
|
||||
1
resources/test/fixtures/isort/required_imports/docstring_only.py
vendored
Normal file
1
resources/test/fixtures/isort/required_imports/docstring_only.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"""Hello, world!"""
|
||||
2
resources/test/fixtures/isort/required_imports/docstring_with_continuation.py
vendored
Normal file
2
resources/test/fixtures/isort/required_imports/docstring_with_continuation.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Hello, world!"""; x = \
|
||||
1; y = 2
|
||||
1
resources/test/fixtures/isort/required_imports/docstring_with_semicolon.py
vendored
Normal file
1
resources/test/fixtures/isort/required_imports/docstring_with_semicolon.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"""Hello, world!"""; x = 1
|
||||
0
resources/test/fixtures/isort/required_imports/empty.py
vendored
Normal file
0
resources/test/fixtures/isort/required_imports/empty.py
vendored
Normal file
2
resources/test/fixtures/isort/required_imports/existing_import.py
vendored
Normal file
2
resources/test/fixtures/isort/required_imports/existing_import.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
from __future__ import generator_stop
|
||||
import os
|
||||
28
resources/test/fixtures/isort/skip.py
vendored
28
resources/test/fixtures/isort/skip.py
vendored
@@ -1,10 +1,20 @@
|
||||
# isort: off
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
# isort: on
|
||||
def f():
|
||||
# isort: off
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
# isort: on
|
||||
|
||||
import sys
|
||||
import os # isort: skip
|
||||
import collections
|
||||
import abc
|
||||
|
||||
def f():
|
||||
import sys
|
||||
import os # isort: skip
|
||||
import collections
|
||||
import abc
|
||||
|
||||
|
||||
def f():
|
||||
import sys
|
||||
import os # isort:skip
|
||||
import collections
|
||||
import abc
|
||||
|
||||
7
resources/test/fixtures/isort/split.py
vendored
7
resources/test/fixtures/isort/split.py
vendored
@@ -3,7 +3,10 @@ import f
|
||||
|
||||
# isort: split
|
||||
|
||||
import a
|
||||
import b
|
||||
import c
|
||||
import d
|
||||
|
||||
# isort: split
|
||||
|
||||
import a
|
||||
import b
|
||||
|
||||
2
resources/test/fixtures/pycodestyle/E999.py
vendored
2
resources/test/fixtures/pycodestyle/E999.py
vendored
@@ -1,2 +1,4 @@
|
||||
|
||||
def x():
|
||||
|
||||
|
||||
|
||||
15
resources/test/fixtures/pycodestyle/W505.py
vendored
Normal file
15
resources/test/fixtures/pycodestyle/W505.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Here's a top-level docstring that's over the limit."""
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a docstring that's also over the limit."""
|
||||
|
||||
x = 1 # Here's a comment that's over the limit, but it's not standalone.
|
||||
|
||||
# Here's a standalone comment that's over the limit.
|
||||
|
||||
print("Here's a string that's over the limit, but it's not a docstring.")
|
||||
|
||||
|
||||
"This is also considered a docstring, and is over the limit."
|
||||
1
resources/test/fixtures/pyflakes/F401_6.py
vendored
1
resources/test/fixtures/pyflakes/F401_6.py
vendored
@@ -9,7 +9,6 @@ from .background import BackgroundTasks
|
||||
# F401 `datastructures.UploadFile` imported but unused
|
||||
from .datastructures import UploadFile as FileUpload
|
||||
|
||||
|
||||
# OK
|
||||
import applications as applications
|
||||
|
||||
|
||||
38
resources/test/fixtures/pyflakes/F601.py
vendored
38
resources/test/fixtures/pyflakes/F601.py
vendored
@@ -10,3 +10,41 @@ x = {
|
||||
b"123": 1,
|
||||
b"123": 4,
|
||||
}
|
||||
|
||||
x = {
|
||||
"a": 1,
|
||||
"a": 2,
|
||||
"a": 3,
|
||||
"a": 3,
|
||||
}
|
||||
|
||||
x = {
|
||||
"a": 1,
|
||||
"a": 2,
|
||||
"a": 3,
|
||||
"a": 3,
|
||||
"a": 4,
|
||||
}
|
||||
|
||||
x = {
|
||||
"a": 1,
|
||||
"a": 1,
|
||||
"a": 2,
|
||||
"a": 3,
|
||||
"a": 4,
|
||||
}
|
||||
|
||||
x = {
|
||||
a: 1,
|
||||
"a": 1,
|
||||
a: 1,
|
||||
"a": 2,
|
||||
a: 2,
|
||||
"a": 3,
|
||||
a: 3,
|
||||
"a": 3,
|
||||
a: 4,
|
||||
}
|
||||
|
||||
x = {"a": 1, "a": 1}
|
||||
x = {"a": 1, "b": 2, "a": 1}
|
||||
|
||||
38
resources/test/fixtures/pyflakes/F602.py
vendored
38
resources/test/fixtures/pyflakes/F602.py
vendored
@@ -5,3 +5,41 @@ x = {
|
||||
a: 2,
|
||||
b: 3,
|
||||
}
|
||||
|
||||
x = {
|
||||
a: 1,
|
||||
a: 2,
|
||||
a: 3,
|
||||
a: 3,
|
||||
}
|
||||
|
||||
x = {
|
||||
a: 1,
|
||||
a: 2,
|
||||
a: 3,
|
||||
a: 3,
|
||||
a: 4,
|
||||
}
|
||||
|
||||
x = {
|
||||
a: 1,
|
||||
a: 1,
|
||||
a: 2,
|
||||
a: 3,
|
||||
a: 4,
|
||||
}
|
||||
|
||||
x = {
|
||||
a: 1,
|
||||
"a": 1,
|
||||
a: 1,
|
||||
"a": 2,
|
||||
a: 2,
|
||||
"a": 3,
|
||||
a: 3,
|
||||
"a": 3,
|
||||
a: 4,
|
||||
}
|
||||
|
||||
x = {a: 1, a: 1}
|
||||
x = {a: 1, b: 2, a: 1}
|
||||
|
||||
46
resources/test/fixtures/pyflakes/F841_3.py
vendored
Normal file
46
resources/test/fixtures/pyflakes/F841_3.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Test case for autofixing F841 violations."""
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
z = 3
|
||||
print(z)
|
||||
|
||||
|
||||
def f():
|
||||
x: int = 1
|
||||
y: int = 2
|
||||
|
||||
z: int = 3
|
||||
print(z)
|
||||
|
||||
|
||||
def f():
|
||||
with foo() as x1:
|
||||
pass
|
||||
|
||||
with foo() as (x2, y2):
|
||||
pass
|
||||
|
||||
with (foo() as x3, foo() as y3, foo() as z3):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
(x1, y1) = (1, 2)
|
||||
(x2, y2) = coords2 = (1, 2)
|
||||
coords3 = (x3, y3) = (1, 2)
|
||||
|
||||
|
||||
def f():
|
||||
try:
|
||||
1 / 0
|
||||
except ValueError as x1:
|
||||
pass
|
||||
|
||||
try:
|
||||
1 / 0
|
||||
except (ValueError, ZeroDivisionError) as x2:
|
||||
pass
|
||||
1
resources/test/fixtures/pyflakes/builtins.py
vendored
Normal file
1
resources/test/fixtures/pyflakes/builtins.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_("Translations")
|
||||
7
resources/test/fixtures/pyflakes/typing_modules.py
vendored
Normal file
7
resources/test/fixtures/pyflakes/typing_modules.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from typing import Union
|
||||
|
||||
from airflow.typing_compat import Literal, Optional
|
||||
|
||||
|
||||
X = Union[Literal[False], Literal["db"]]
|
||||
y = Optional["Class"]
|
||||
@@ -5,4 +5,3 @@ from warnings import warn
|
||||
warnings.warn("this is ok")
|
||||
warn("by itself is also ok")
|
||||
logging.warning("this is fine")
|
||||
log.warning("this is ok")
|
||||
|
||||
10
resources/test/fixtures/pygrep-hooks/PGH002_1.py
vendored
10
resources/test/fixtures/pygrep-hooks/PGH002_1.py
vendored
@@ -2,14 +2,4 @@ import logging
|
||||
from logging import warn
|
||||
|
||||
logging.warn("this is not ok")
|
||||
log.warn("this is also not ok")
|
||||
warn("not ok")
|
||||
|
||||
|
||||
def foo():
|
||||
from logging import warn
|
||||
|
||||
def warn():
|
||||
pass
|
||||
|
||||
warn("has been redefined, but we will still report it")
|
||||
|
||||
59
resources/test/fixtures/pylint/constant_comparison.py
vendored
Normal file
59
resources/test/fixtures/pylint/constant_comparison.py
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Check that magic values are not used in comparisons"""
|
||||
|
||||
if 100 == 100: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if 1 == 3: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if 1 != 3: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
x = 0
|
||||
if 4 == 3 == x: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if x == 0: # correct
|
||||
pass
|
||||
|
||||
y = 1
|
||||
if x == y: # correct
|
||||
pass
|
||||
|
||||
if 1 > 0: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if x > 0: # correct
|
||||
pass
|
||||
|
||||
if 1 >= 0: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if x >= 0: # correct
|
||||
pass
|
||||
|
||||
if 1 < 0: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if x < 0: # correct
|
||||
pass
|
||||
|
||||
if 1 <= 0: # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
if x <= 0: # correct
|
||||
pass
|
||||
|
||||
word = "hello"
|
||||
if word == "": # correct
|
||||
pass
|
||||
|
||||
if "hello" == "": # [comparison-of-constants]
|
||||
pass
|
||||
|
||||
truthy = True
|
||||
if truthy == True: # correct
|
||||
pass
|
||||
|
||||
if True == False: # [comparison-of-constants]
|
||||
pass
|
||||
71
resources/test/fixtures/pylint/magic_value_comparison.py
vendored
Normal file
71
resources/test/fixtures/pylint/magic_value_comparison.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Check that magic values are not used in comparisons"""
|
||||
import cmath
|
||||
|
||||
user_input = 10
|
||||
|
||||
if 10 > user_input: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if 10 == 100: # [comparison-of-constants] R0133
|
||||
pass
|
||||
|
||||
if 1 == 3: # [comparison-of-constants] R0133
|
||||
pass
|
||||
|
||||
x = 0
|
||||
if 4 == 3 == x: # [comparison-of-constants] R0133
|
||||
pass
|
||||
|
||||
time_delta = 7224
|
||||
ONE_HOUR = 3600
|
||||
|
||||
if time_delta > ONE_HOUR: # correct
|
||||
pass
|
||||
|
||||
argc = 1
|
||||
|
||||
if argc != -1: # correct
|
||||
pass
|
||||
|
||||
if argc != 0: # correct
|
||||
pass
|
||||
|
||||
if argc != 1: # correct
|
||||
pass
|
||||
|
||||
if argc != 2: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if __name__ == "__main__": # correct
|
||||
pass
|
||||
|
||||
ADMIN_PASSWORD = "SUPERSECRET"
|
||||
input_password = "password"
|
||||
|
||||
if input_password == "": # correct
|
||||
pass
|
||||
|
||||
if input_password == ADMIN_PASSWORD: # correct
|
||||
pass
|
||||
|
||||
if input_password == "Hunter2": # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
PI = 3.141592653589793238
|
||||
pi_estimation = 3.14
|
||||
|
||||
if pi_estimation == 3.141592653589793238: # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if pi_estimation == PI: # correct
|
||||
pass
|
||||
|
||||
HELLO_WORLD = b"Hello, World!"
|
||||
user_input = b"Hello, There!"
|
||||
|
||||
if user_input == b"something": # [magic-value-comparison]
|
||||
pass
|
||||
|
||||
if user_input == HELLO_WORLD: # correct
|
||||
pass
|
||||
|
||||
8
resources/test/fixtures/pyupgrade/UP024_3.py
vendored
Normal file
8
resources/test/fixtures/pyupgrade/UP024_3.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
class SocketError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
raise SocketError()
|
||||
except SocketError:
|
||||
pass
|
||||
10
resources/test/fixtures/pyupgrade/UP028_1.py
vendored
10
resources/test/fixtures/pyupgrade/UP028_1.py
vendored
@@ -111,3 +111,13 @@ def f():
|
||||
class C:
|
||||
def __init__(self):
|
||||
print(x)
|
||||
|
||||
|
||||
def f():
|
||||
for x in y:
|
||||
yield x, x + 1
|
||||
|
||||
|
||||
def f():
|
||||
for x, y in z:
|
||||
yield x, y, x + y
|
||||
|
||||
36
resources/test/fixtures/pyupgrade/UP030_0.py
vendored
Normal file
36
resources/test/fixtures/pyupgrade/UP030_0.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Invalid calls; errors expected.
|
||||
|
||||
"{0}" "{1}" "{2}".format(1, 2, 3)
|
||||
|
||||
"a {3} complicated {1} string with {0} {2}".format(
|
||||
"first", "second", "third", "fourth"
|
||||
)
|
||||
|
||||
'{0}'.format(1)
|
||||
|
||||
'{0:x}'.format(30)
|
||||
|
||||
x = '{0}'.format(1)
|
||||
|
||||
'''{0}\n{1}\n'''.format(1, 2)
|
||||
|
||||
x = "foo {0}" \
|
||||
"bar {1}".format(1, 2)
|
||||
|
||||
("{0}").format(1)
|
||||
|
||||
"\N{snowman} {0}".format(1)
|
||||
|
||||
'{' '0}'.format(1)
|
||||
|
||||
# These will not change because we are waiting for libcst to fix this issue:
|
||||
# https://github.com/Instagram/LibCST/issues/846
|
||||
print(
|
||||
'foo{0}'
|
||||
'bar{1}'.format(1, 2)
|
||||
)
|
||||
|
||||
print(
|
||||
'foo{0}' # ohai\n"
|
||||
'bar{1}'.format(1, 2)
|
||||
)
|
||||
23
resources/test/fixtures/pyupgrade/UP030_1.py
vendored
Normal file
23
resources/test/fixtures/pyupgrade/UP030_1.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Valid calls; no errors expected.
|
||||
|
||||
'{}'.format(1)
|
||||
|
||||
|
||||
x = ('{0} {1}',)
|
||||
|
||||
'{0} {0}'.format(1)
|
||||
|
||||
'{0:<{1}}'.format(1, 4)
|
||||
|
||||
f"{0}".format(a)
|
||||
|
||||
f"{0}".format(1)
|
||||
|
||||
print(f"{0}".format(1))
|
||||
|
||||
# I did not include the following tests because ruff does not seem to work with
|
||||
# invalid python syntax (which is a good thing)
|
||||
|
||||
# "{0}"format(1)
|
||||
# '{'.format(1)", "'}'.format(1)
|
||||
# ("{0}" # {1}\n"{2}").format(1, 2, 3)
|
||||
@@ -17,7 +17,7 @@ resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` i
|
||||
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
|
||||
resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
Found 7 error(s).
|
||||
6 potentially fixable with the --fix option.
|
||||
7 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from the project directory itself should exhibit the same behavior:
|
||||
@@ -32,7 +32,7 @@ examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never
|
||||
project/file.py:1:8: F401 `os` imported but unused
|
||||
project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
Found 7 error(s).
|
||||
6 potentially fixable with the --fix option.
|
||||
7 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
|
||||
@@ -43,7 +43,7 @@ files:
|
||||
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
Found 2 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
2 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
|
||||
@@ -74,7 +74,7 @@ docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
|
||||
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
|
||||
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
|
||||
Found 4 error(s).
|
||||
1 potentially fixable with the --fix option.
|
||||
4 potentially fixable with the --fix option.
|
||||
```
|
||||
|
||||
Passing an excluded directory directly should report errors in the contained files:
|
||||
|
||||
1241
ruff.schema.json
1241
ruff.schema.json
File diff suppressed because it is too large
Load Diff
68
ruff_cli/Cargo.toml
Normal file
68
ruff_cli/Cargo.toml
Normal file
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.222"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
documentation = "https://github.com/charliermarsh/ruff"
|
||||
homepage = "https://github.com/charliermarsh/ruff"
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
readme = "../README.md"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "ruff"
|
||||
path = "src/main.rs"
|
||||
doctest = false
|
||||
|
||||
# Since the name of the binary is the same as the name of the `ruff` crate
|
||||
# running `cargo doc --no-deps --all` results in an `output filename collision`
|
||||
# See also https://github.com/rust-lang/cargo/issues/6313.
|
||||
# We therefore disable the documentation generation for the binary.
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
ruff = { path = ".." }
|
||||
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
anyhow = { version = "1.0.66" }
|
||||
atty = { version = "0.2.14" }
|
||||
bincode = { version = "1.3.3" }
|
||||
cachedir = { version = "0.3.0" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.1", features = ["derive", "env"] }
|
||||
clap_complete_command = { version = "0.4.0" }
|
||||
clearscreen = { version = "2.0.0" }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.17" }
|
||||
glob = { version = "0.3.0" }
|
||||
ignore = { version = "0.4.18" }
|
||||
itertools = { version = "0.10.5" }
|
||||
log = { version = "0.4.17" }
|
||||
notify = { version = "5.0.0" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache"] }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = { version = "1.0.87" }
|
||||
similar = { version = "2.2.1" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
update-informer = { version = "0.6.0", default-features = false, features = ["pypi"], optional = true }
|
||||
walkdir = { version = "2.3.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { version = "2.0.4" }
|
||||
strum = { version = "0.24.1" }
|
||||
ureq = { version = "2.5.0", features = [] }
|
||||
|
||||
[features]
|
||||
default = ["update-informer"]
|
||||
update-informer = ["dep:update-informer"]
|
||||
|
||||
[package.metadata.maturin]
|
||||
name = "ruff"
|
||||
# Setting the name here is necessary for maturin to include the package in its builds.
|
||||
124
ruff_cli/src/cache.rs
Normal file
124
ruff_cli/src/cache.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use filetime::FileTime;
|
||||
use log::error;
|
||||
use path_absolutize::Absolutize;
|
||||
use ruff::message::Message;
|
||||
use ruff::settings::{flags, Settings};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CacheMetadata {
|
||||
mtime: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CheckResultRef<'a> {
|
||||
metadata: &'a CacheMetadata,
|
||||
messages: &'a [Message],
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CheckResult {
|
||||
metadata: CacheMetadata,
|
||||
messages: Vec<Message>,
|
||||
}
|
||||
|
||||
fn content_dir() -> &'static Path {
|
||||
Path::new("content")
|
||||
}
|
||||
|
||||
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: flags::Autofix) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
CARGO_PKG_VERSION.hash(&mut hasher);
|
||||
path.as_ref().absolutize().unwrap().hash(&mut hasher);
|
||||
settings.hash(&mut hasher);
|
||||
autofix.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Initialize the cache at the specified `Path`.
|
||||
pub fn init(path: &Path) -> Result<()> {
|
||||
// Create the cache directories.
|
||||
fs::create_dir_all(path.join(content_dir()))?;
|
||||
|
||||
// Add the CACHEDIR.TAG.
|
||||
if !cachedir::is_tagged(path)? {
|
||||
cachedir::add_tag(path)?;
|
||||
}
|
||||
|
||||
// Add the .gitignore.
|
||||
let gitignore_path = path.join(".gitignore");
|
||||
if !gitignore_path.exists() {
|
||||
let mut file = fs::File::create(gitignore_path)?;
|
||||
file.write_all(b"*")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_sync(cache_dir: &Path, key: u64, value: &[u8]) -> Result<(), std::io::Error> {
|
||||
fs::write(
|
||||
cache_dir.join(content_dir()).join(format!("{key:x}")),
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
fn read_sync(cache_dir: &Path, key: u64) -> Result<Vec<u8>, std::io::Error> {
|
||||
fs::read(cache_dir.join(content_dir()).join(format!("{key:x}")))
|
||||
}
|
||||
|
||||
/// Get a value from the cache.
|
||||
pub fn get<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata: &fs::Metadata,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
) -> Option<Vec<Message>> {
|
||||
let encoded = read_sync(&settings.cache_dir, cache_key(path, settings, autofix)).ok()?;
|
||||
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult {
|
||||
metadata: CacheMetadata { mtime },
|
||||
messages,
|
||||
}) => (mtime, messages),
|
||||
Err(e) => {
|
||||
error!("Failed to deserialize encoded cache entry: {e:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if FileTime::from_last_modification_time(metadata).unix_seconds() != mtime {
|
||||
return None;
|
||||
}
|
||||
Some(messages)
|
||||
}
|
||||
|
||||
/// Set a value in the cache.
|
||||
pub fn set<P: AsRef<Path>>(
|
||||
path: P,
|
||||
metadata: &fs::Metadata,
|
||||
settings: &Settings,
|
||||
autofix: flags::Autofix,
|
||||
messages: &[Message],
|
||||
) {
|
||||
let check_result = CheckResultRef {
|
||||
metadata: &CacheMetadata {
|
||||
mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),
|
||||
},
|
||||
messages,
|
||||
};
|
||||
if let Err(e) = write_sync(
|
||||
&settings.cache_dir,
|
||||
cache_key(path, settings, autofix),
|
||||
&bincode::serialize(&check_result).unwrap(),
|
||||
) {
|
||||
error!("Failed to write to cache: {e:?}");
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,21 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::fs;
|
||||
use crate::logging::LogLevel;
|
||||
use crate::registry::CheckCode;
|
||||
use crate::registry_gen::CheckCodePrefix;
|
||||
use crate::settings::types::{
|
||||
use ruff::fs;
|
||||
use ruff::logging::LogLevel;
|
||||
use ruff::registry::{RuleCode, RuleCodePrefix};
|
||||
use ruff::resolver::ConfigProcessor;
|
||||
use ruff::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "Ruff: An extremely fast Python linter.")]
|
||||
#[command(
|
||||
author,
|
||||
name = "ruff",
|
||||
about = "Ruff: An extremely fast Python linter."
|
||||
)]
|
||||
#[command(version)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Cli {
|
||||
@@ -21,31 +24,31 @@ pub struct Cli {
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
|
||||
/// configuration.
|
||||
#[arg(long)]
|
||||
#[arg(long, conflicts_with = "isolated")]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub verbose: bool,
|
||||
/// Only log errors.
|
||||
/// Print lint violations, but nothing else.
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub quiet: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting
|
||||
/// errors).
|
||||
/// lint violations).
|
||||
#[arg(short, long, group = "verbosity")]
|
||||
pub silent: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
/// Exit with status code "0", even upon detecting lint violations.
|
||||
#[arg(short, long)]
|
||||
pub exit_zero: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
pub watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
/// Attempt to automatically fix lint violations.
|
||||
#[arg(long, overrides_with("no_fix"))]
|
||||
fix: bool,
|
||||
#[clap(long, overrides_with("fix"), hide = true)]
|
||||
no_fix: bool,
|
||||
/// Fix any fixable lint errors, but don't report on leftover violations.
|
||||
/// Implies `--fix`.
|
||||
/// Fix any fixable lint violations, but don't report on leftover
|
||||
/// violations. Implies `--fix`.
|
||||
#[arg(long, overrides_with("no_fix_only"))]
|
||||
fix_only: bool,
|
||||
#[clap(long, overrides_with("fix_only"), hide = true)]
|
||||
@@ -57,47 +60,50 @@ pub struct Cli {
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
pub no_cache: bool,
|
||||
/// Comma-separated list of error codes to enable (or ALL, to enable all
|
||||
/// checks).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
/// Like --select, but adds additional error codes on top of the selected
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config")]
|
||||
pub isolated: bool,
|
||||
/// Comma-separated list of rule codes to enable (or ALL, to enable all
|
||||
/// rules).
|
||||
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
|
||||
pub select: Option<Vec<RuleCodePrefix>>,
|
||||
/// Like --select, but adds additional rule codes on top of the selected
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
/// Comma-separated list of error codes to disable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored
|
||||
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
|
||||
pub extend_select: Option<Vec<RuleCodePrefix>>,
|
||||
/// Comma-separated list of rule codes to disable.
|
||||
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
|
||||
pub ignore: Option<Vec<RuleCodePrefix>>,
|
||||
/// Like --ignore, but adds additional rule codes on top of the ignored
|
||||
/// ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
|
||||
pub extend_ignore: Option<Vec<RuleCodePrefix>>,
|
||||
/// List of paths, used to omit files and/or directories from analysis.
|
||||
#[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")]
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the
|
||||
/// excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
/// Like --exclude, but adds additional files and directories on top of
|
||||
/// those already excluded.
|
||||
#[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")]
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
/// List of error codes to treat as eligible for autofix. Only applicable
|
||||
/// List of rule codes to treat as eligible for autofix. Only applicable
|
||||
/// when autofix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub fixable: Option<Vec<CheckCodePrefix>>,
|
||||
/// List of error codes to treat as ineligible for autofix. Only applicable
|
||||
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
|
||||
pub fixable: Option<Vec<RuleCodePrefix>>,
|
||||
/// List of rule codes to treat as ineligible for autofix. Only applicable
|
||||
/// when autofix itself is enabled (e.g., via `--fix`).
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub unfixable: Option<Vec<CheckCodePrefix>>,
|
||||
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
|
||||
pub unfixable: Option<Vec<RuleCodePrefix>>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum)]
|
||||
/// Output serialization format for violations.
|
||||
#[arg(long, value_enum, env = "RUFF_FORMAT")]
|
||||
pub format: Option<SerializationFormat>,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "RUFF_CACHE_DIR")]
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
/// Show violations with source code.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
@@ -127,13 +133,10 @@ pub struct Cli {
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Set the line-length for length-associated checks and automatic
|
||||
/// Set the line-length for length-associated rules and automatic
|
||||
/// formatting.
|
||||
#[arg(long)]
|
||||
pub line_length: Option<usize>,
|
||||
/// Maximum McCabe complexity allowed for a given function.
|
||||
#[arg(long)]
|
||||
pub max_complexity: Option<usize>,
|
||||
/// Enable automatic additions of `noqa` directives to failing lines.
|
||||
#[arg(
|
||||
long,
|
||||
@@ -177,7 +180,7 @@ pub struct Cli {
|
||||
conflicts_with = "stdin_filename",
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub explain: Option<CheckCode>,
|
||||
pub explain: Option<RuleCode>,
|
||||
/// Generate shell completion
|
||||
#[arg(
|
||||
long,
|
||||
@@ -210,7 +213,7 @@ pub struct Cli {
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub show_files: bool,
|
||||
/// See the settings Ruff will use to check a given Python file.
|
||||
/// See the settings Ruff will use to lint a given Python file.
|
||||
#[arg(
|
||||
long,
|
||||
// Fake subcommands.
|
||||
@@ -241,6 +244,7 @@ impl Cli {
|
||||
explain: self.explain,
|
||||
files: self.files,
|
||||
generate_shell_completion: self.generate_shell_completion,
|
||||
isolated: self.isolated,
|
||||
no_cache: self.no_cache,
|
||||
quiet: self.quiet,
|
||||
show_files: self.show_files,
|
||||
@@ -259,7 +263,6 @@ impl Cli {
|
||||
fixable: self.fixable,
|
||||
ignore: self.ignore,
|
||||
line_length: self.line_length,
|
||||
max_complexity: self.max_complexity,
|
||||
per_file_ignores: self.per_file_ignores,
|
||||
respect_gitignore: resolve_bool_arg(
|
||||
self.respect_gitignore,
|
||||
@@ -299,9 +302,10 @@ pub struct Arguments {
|
||||
pub config: Option<PathBuf>,
|
||||
pub diff: bool,
|
||||
pub exit_zero: bool,
|
||||
pub explain: Option<CheckCode>,
|
||||
pub explain: Option<RuleCode>,
|
||||
pub files: Vec<PathBuf>,
|
||||
pub generate_shell_completion: Option<clap_complete_command::Shell>,
|
||||
pub isolated: bool,
|
||||
pub no_cache: bool,
|
||||
pub quiet: bool,
|
||||
pub show_files: bool,
|
||||
@@ -319,18 +323,17 @@ pub struct Overrides {
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub extend_select: Option<Vec<CheckCodePrefix>>,
|
||||
pub fixable: Option<Vec<CheckCodePrefix>>,
|
||||
pub ignore: Option<Vec<CheckCodePrefix>>,
|
||||
pub extend_ignore: Option<Vec<RuleCodePrefix>>,
|
||||
pub extend_select: Option<Vec<RuleCodePrefix>>,
|
||||
pub fixable: Option<Vec<RuleCodePrefix>>,
|
||||
pub ignore: Option<Vec<RuleCodePrefix>>,
|
||||
pub line_length: Option<usize>,
|
||||
pub max_complexity: Option<usize>,
|
||||
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
|
||||
pub respect_gitignore: Option<bool>,
|
||||
pub select: Option<Vec<CheckCodePrefix>>,
|
||||
pub select: Option<Vec<RuleCodePrefix>>,
|
||||
pub show_source: Option<bool>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
pub unfixable: Option<Vec<CheckCodePrefix>>,
|
||||
pub unfixable: Option<Vec<RuleCodePrefix>>,
|
||||
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
@@ -340,6 +343,82 @@ pub struct Overrides {
|
||||
pub update_check: Option<bool>,
|
||||
}
|
||||
|
||||
impl ConfigProcessor for &Overrides {
|
||||
fn process_config(&self, config: &mut ruff::settings::configuration::Configuration) {
|
||||
if let Some(cache_dir) = &self.cache_dir {
|
||||
config.cache_dir = Some(cache_dir.clone());
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = &self.dummy_variable_rgx {
|
||||
config.dummy_variable_rgx = Some(dummy_variable_rgx.clone());
|
||||
}
|
||||
if let Some(exclude) = &self.exclude {
|
||||
config.exclude = Some(exclude.clone());
|
||||
}
|
||||
if let Some(extend_exclude) = &self.extend_exclude {
|
||||
config.extend_exclude.extend(extend_exclude.clone());
|
||||
}
|
||||
if let Some(fix) = &self.fix {
|
||||
config.fix = Some(*fix);
|
||||
}
|
||||
if let Some(fix_only) = &self.fix_only {
|
||||
config.fix_only = Some(*fix_only);
|
||||
}
|
||||
if let Some(fixable) = &self.fixable {
|
||||
config.fixable = Some(fixable.clone());
|
||||
}
|
||||
if let Some(format) = &self.format {
|
||||
config.format = Some(*format);
|
||||
}
|
||||
if let Some(force_exclude) = &self.force_exclude {
|
||||
config.force_exclude = Some(*force_exclude);
|
||||
}
|
||||
if let Some(ignore) = &self.ignore {
|
||||
config.ignore = Some(ignore.clone());
|
||||
}
|
||||
if let Some(line_length) = &self.line_length {
|
||||
config.line_length = Some(*line_length);
|
||||
}
|
||||
if let Some(per_file_ignores) = &self.per_file_ignores {
|
||||
config.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
||||
}
|
||||
if let Some(respect_gitignore) = &self.respect_gitignore {
|
||||
config.respect_gitignore = Some(*respect_gitignore);
|
||||
}
|
||||
if let Some(select) = &self.select {
|
||||
config.select = Some(select.clone());
|
||||
}
|
||||
if let Some(show_source) = &self.show_source {
|
||||
config.show_source = Some(*show_source);
|
||||
}
|
||||
if let Some(target_version) = &self.target_version {
|
||||
config.target_version = Some(*target_version);
|
||||
}
|
||||
if let Some(unfixable) = &self.unfixable {
|
||||
config.unfixable = Some(unfixable.clone());
|
||||
}
|
||||
if let Some(update_check) = &self.update_check {
|
||||
config.update_check = Some(*update_check);
|
||||
}
|
||||
// Special-case: `extend_ignore` and `extend_select` are parallel arrays, so
|
||||
// push an empty array if only one of the two is provided.
|
||||
match (&self.extend_ignore, &self.extend_select) {
|
||||
(Some(extend_ignore), Some(extend_select)) => {
|
||||
config.extend_ignore.push(extend_ignore.clone());
|
||||
config.extend_select.push(extend_select.clone());
|
||||
}
|
||||
(Some(extend_ignore), None) => {
|
||||
config.extend_ignore.push(extend_ignore.clone());
|
||||
config.extend_select.push(Vec::new());
|
||||
}
|
||||
(None, Some(extend_select)) => {
|
||||
config.extend_ignore.push(Vec::new());
|
||||
config.extend_select.push(extend_select.clone());
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the CLI settings to a `LogLevel`.
|
||||
pub fn extract_log_level(cli: &Arguments) -> LogLevel {
|
||||
if cli.silent {
|
||||
@@ -355,7 +434,7 @@ pub fn extract_log_level(cli: &Arguments) -> LogLevel {
|
||||
|
||||
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
|
||||
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<CheckCodePrefix>> = FxHashMap::default();
|
||||
let mut per_file_ignores: FxHashMap<String, Vec<RuleCodePrefix>> = FxHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
@@ -11,22 +11,22 @@ use log::{debug, error};
|
||||
use path_absolutize::path_dedot;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustpython_ast::Location;
|
||||
use ruff::cache::CACHE_DIR_NAME;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
use ruff::logging::LogLevel;
|
||||
use ruff::message::{Location, Message};
|
||||
use ruff::registry::RuleCode;
|
||||
use ruff::resolver::{FileDiscovery, PyprojectDiscovery};
|
||||
use ruff::settings::flags;
|
||||
use ruff::settings::types::SerializationFormat;
|
||||
use ruff::{fix, fs, packaging, resolver, warn_user_once, IOError};
|
||||
use serde::Serialize;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::cache::DEFAULT_CACHE_DIR_NAME;
|
||||
use crate::cache;
|
||||
use crate::cli::Overrides;
|
||||
use crate::diagnostics::{lint_path, lint_stdin, Diagnostics};
|
||||
use crate::iterators::par_iter;
|
||||
use crate::linter::{add_noqa_to_path, lint_path, lint_stdin, Diagnostics};
|
||||
use crate::logging::LogLevel;
|
||||
use crate::message::Message;
|
||||
use crate::registry::{CheckCode, CheckKind};
|
||||
use crate::resolver::{FileDiscovery, PyprojectDiscovery};
|
||||
use crate::settings::flags;
|
||||
use crate::settings::types::SerializationFormat;
|
||||
use crate::{cache, fs, one_time_warning, packages, resolver};
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
pub fn run(
|
||||
@@ -35,7 +35,7 @@ pub fn run(
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
cache: flags::Cache,
|
||||
autofix: fixer::Mode,
|
||||
autofix: fix::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Collect all the Python files to check.
|
||||
let start = Instant::now();
|
||||
@@ -45,12 +45,7 @@ pub fn run(
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"No Python files found under the given path(s)".bold()
|
||||
);
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
@@ -82,12 +77,14 @@ pub fn run(
|
||||
};
|
||||
|
||||
// Discover the package root for each Python file.
|
||||
let package_roots = packages::detect_package_roots(
|
||||
let package_roots = packaging::detect_package_roots(
|
||||
&paths
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(ignore::DirEntry::path)
|
||||
.collect::<Vec<_>>(),
|
||||
&resolver,
|
||||
pyproject_strategy,
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -117,9 +114,9 @@ pub fn run(
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
if settings.enabled.contains(&CheckCode::E902) {
|
||||
if settings.enabled.contains(&RuleCode::E902) {
|
||||
Diagnostics::new(vec![Message {
|
||||
kind: CheckKind::IOError(message),
|
||||
kind: IOError(message).into(),
|
||||
location: Location::default(),
|
||||
end_location: Location::default(),
|
||||
fix: None,
|
||||
@@ -161,7 +158,7 @@ pub fn run_stdin(
|
||||
pyproject_strategy: &PyprojectDiscovery,
|
||||
file_strategy: &FileDiscovery,
|
||||
overrides: &Overrides,
|
||||
autofix: fixer::Mode,
|
||||
autofix: fix::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
if let Some(filename) = filename {
|
||||
if !resolver::python_file_at_path(filename, pyproject_strategy, file_strategy, overrides)? {
|
||||
@@ -174,7 +171,7 @@ pub fn run_stdin(
|
||||
};
|
||||
let package_root = filename
|
||||
.and_then(Path::parent)
|
||||
.and_then(packages::detect_package_root);
|
||||
.and_then(|path| packaging::detect_package_root(path, &settings.namespace_packages));
|
||||
let stdin = read_from_stdin()?;
|
||||
let mut diagnostics = lint_stdin(filename, package_root, &stdin, settings, autofix)?;
|
||||
diagnostics.messages.sort_unstable();
|
||||
@@ -196,12 +193,7 @@ pub fn add_noqa(
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"No Python files found under the given path(s)".bold()
|
||||
);
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
@@ -271,12 +263,7 @@ pub fn show_files(
|
||||
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;
|
||||
|
||||
if paths.is_empty() {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"No Python files found under the given path(s)".bold()
|
||||
);
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -298,18 +285,18 @@ pub fn show_files(
|
||||
#[derive(Serialize)]
|
||||
struct Explanation<'a> {
|
||||
code: &'a str,
|
||||
category: &'a str,
|
||||
origin: &'a str,
|
||||
summary: &'a str,
|
||||
}
|
||||
|
||||
/// Explain a `CheckCode` to the user.
|
||||
pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
|
||||
/// Explain a `RuleCode` to the user.
|
||||
pub fn explain(code: &RuleCode, format: SerializationFormat) -> Result<()> {
|
||||
match format {
|
||||
SerializationFormat::Text | SerializationFormat::Grouped => {
|
||||
println!(
|
||||
"{} ({}): {}",
|
||||
code.as_ref(),
|
||||
code.category().title(),
|
||||
code.origin().title(),
|
||||
code.kind().summary()
|
||||
);
|
||||
}
|
||||
@@ -318,7 +305,7 @@ pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&Explanation {
|
||||
code: code.as_ref(),
|
||||
category: code.category().title(),
|
||||
origin: code.origin().title(),
|
||||
summary: &code.kind().summary(),
|
||||
})?
|
||||
);
|
||||
@@ -340,10 +327,10 @@ pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
|
||||
pub fn clean(level: &LogLevel) -> Result<()> {
|
||||
for entry in WalkDir::new(&*path_dedot::CWD)
|
||||
.into_iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| entry.file_type().is_dir())
|
||||
{
|
||||
let cache = entry.path().join(DEFAULT_CACHE_DIR_NAME);
|
||||
let cache = entry.path().join(CACHE_DIR_NAME);
|
||||
if cache.is_dir() {
|
||||
if level >= &LogLevel::Default {
|
||||
eprintln!("Removing cache at: {}", fs::relativize_path(&cache).bold());
|
||||
155
ruff_cli/src/diagnostics.rs
Normal file
155
ruff_cli/src/diagnostics.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
#![cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
use std::fs::write;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::AddAssign;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use ruff::linter::{lint_fix, lint_only};
|
||||
use ruff::message::Message;
|
||||
use ruff::settings::{flags, Settings};
|
||||
use ruff::{fix, fs};
|
||||
use similar::TextDiff;
|
||||
|
||||
use crate::cache;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Diagnostics {
|
||||
pub messages: Vec<Message>,
|
||||
pub fixed: usize,
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub fn new(messages: Vec<Message>) -> Self {
|
||||
Self { messages, fixed: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Diagnostics {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.messages.extend(other.messages);
|
||||
self.fixed += other.fixed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Lint the source code at the given `Path`.
|
||||
pub fn lint_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
cache: flags::Cache,
|
||||
autofix: fix::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Validate the `Settings` and return any errors.
|
||||
settings.validate()?;
|
||||
|
||||
// Check the cache.
|
||||
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
|
||||
// side-effects that aren't captured in the cache. (In practice, it's fine
|
||||
// to cache `fixer::Mode::Apply`, since a file either has no fixes, or we'll
|
||||
// write the fixes to disk, thus invalidating the cache. But it's a bit hard
|
||||
// to reason about. We need to come up with a better solution here.)
|
||||
let metadata = if matches!(cache, flags::Cache::Enabled)
|
||||
&& matches!(autofix, fix::FixMode::None | fix::FixMode::Generate)
|
||||
{
|
||||
let metadata = path.metadata()?;
|
||||
if let Some(messages) = cache::get(path, &metadata, settings, autofix.into()) {
|
||||
debug!("Cache hit for: {}", path.to_string_lossy());
|
||||
return Ok(Diagnostics::new(messages));
|
||||
}
|
||||
Some(metadata)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
// Lint the file.
|
||||
let (messages, fixed) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
|
||||
let (transformed, fixed, messages) = lint_fix(&contents, path, package, settings)?;
|
||||
if fixed > 0 {
|
||||
if matches!(autofix, fix::FixMode::Apply) {
|
||||
write(path, transformed)?;
|
||||
} else if matches!(autofix, fix::FixMode::Diff) {
|
||||
let mut stdout = io::stdout().lock();
|
||||
TextDiff::from_lines(&contents, &transformed)
|
||||
.unified_diff()
|
||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
||||
.to_writer(&mut stdout)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
(messages, fixed)
|
||||
} else {
|
||||
let messages = lint_only(&contents, path, package, settings, autofix.into())?;
|
||||
let fixed = 0;
|
||||
(messages, fixed)
|
||||
};
|
||||
|
||||
// Re-populate the cache.
|
||||
if let Some(metadata) = metadata {
|
||||
cache::set(path, &metadata, settings, autofix.into(), &messages);
|
||||
}
|
||||
|
||||
Ok(Diagnostics { messages, fixed })
|
||||
}
|
||||
|
||||
/// Generate `Diagnostic`s from source code content derived from
|
||||
/// stdin.
|
||||
pub fn lint_stdin(
|
||||
path: Option<&Path>,
|
||||
package: Option<&Path>,
|
||||
contents: &str,
|
||||
settings: &Settings,
|
||||
autofix: fix::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Validate the `Settings` and return any errors.
|
||||
settings.validate()?;
|
||||
|
||||
// Lint the inputs.
|
||||
let (messages, fixed) = if matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff) {
|
||||
let (transformed, fixed, messages) = lint_fix(
|
||||
contents,
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
settings,
|
||||
)?;
|
||||
|
||||
if matches!(autofix, fix::FixMode::Apply) {
|
||||
// Write the contents to stdout, regardless of whether any errors were fixed.
|
||||
io::stdout().write_all(transformed.as_bytes())?;
|
||||
} else if matches!(autofix, fix::FixMode::Diff) {
|
||||
// But only write a diff if it's non-empty.
|
||||
if fixed > 0 {
|
||||
let text_diff = TextDiff::from_lines(contents, &transformed);
|
||||
let mut unified_diff = text_diff.unified_diff();
|
||||
if let Some(path) = path {
|
||||
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
|
||||
}
|
||||
|
||||
let mut stdout = io::stdout().lock();
|
||||
unified_diff.to_writer(&mut stdout)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
(messages, fixed)
|
||||
} else {
|
||||
let messages = lint_only(
|
||||
contents,
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
settings,
|
||||
autofix.into(),
|
||||
)?;
|
||||
let fixed = 0;
|
||||
(messages, fixed)
|
||||
};
|
||||
|
||||
Ok(Diagnostics { messages, fixed })
|
||||
}
|
||||
14
ruff_cli/src/lib.rs
Normal file
14
ruff_cli/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! This library only exists to enable the Ruff internal tooling (`ruff_dev`)
|
||||
//! to automatically update the `ruff --help` output in the `README.md`.
|
||||
//!
|
||||
//! For the actual Ruff library, see [`ruff`].
|
||||
#![allow(clippy::must_use_candidate, dead_code)]
|
||||
|
||||
mod cli;
|
||||
|
||||
use clap::CommandFactory;
|
||||
|
||||
/// Returns the output of `ruff --help`.
|
||||
pub fn help() -> String {
|
||||
cli::Cli::command().render_help().to_string()
|
||||
}
|
||||
@@ -1,70 +1,79 @@
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::io::{self};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use ::ruff::autofix::fixer;
|
||||
use ::ruff::cli::{extract_log_level, Cli, Overrides};
|
||||
use ::ruff::commands;
|
||||
use ::ruff::logging::{set_up_logging, LogLevel};
|
||||
use ::ruff::printer::{Printer, Violations};
|
||||
use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity};
|
||||
use ::ruff::resolver::{
|
||||
resolve_settings_with_processor, ConfigProcessor, FileDiscovery, PyprojectDiscovery, Relativity,
|
||||
};
|
||||
use ::ruff::settings::configuration::Configuration;
|
||||
use ::ruff::settings::types::SerializationFormat;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use ::ruff::{fix, fs, warn_user_once};
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use cli::{extract_log_level, Cli, Overrides};
|
||||
use colored::Colorize;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use path_absolutize::path_dedot;
|
||||
use ruff::one_time_warning;
|
||||
use printer::{Printer, Violations};
|
||||
|
||||
mod cache;
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod diagnostics;
|
||||
mod iterators;
|
||||
mod printer;
|
||||
#[cfg(all(feature = "update-informer"))]
|
||||
pub mod updates;
|
||||
|
||||
/// Resolve the relevant settings strategy and defaults for the current
|
||||
/// invocation.
|
||||
fn resolve(
|
||||
isolated: bool,
|
||||
config: Option<&Path>,
|
||||
overrides: &Overrides,
|
||||
stdin_filename: Option<&Path>,
|
||||
) -> Result<PyprojectDiscovery> {
|
||||
if let Some(pyproject) = config {
|
||||
// First priority: the user specified a `pyproject.toml` file. Use that
|
||||
if isolated {
|
||||
// First priority: if we're running in isolated mode, use the default settings.
|
||||
let mut config = Configuration::default();
|
||||
overrides.process_config(&mut config);
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Fixed(settings))
|
||||
} else if let Some(pyproject) = config {
|
||||
// Second priority: the user specified a `pyproject.toml` file. Use that
|
||||
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
|
||||
// current working directory. (This matches ESLint's behavior.)
|
||||
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
let settings = resolve_settings_with_processor(pyproject, &Relativity::Cwd, overrides)?;
|
||||
Ok(PyprojectDiscovery::Fixed(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_settings_toml(
|
||||
stdin_filename
|
||||
.as_ref()
|
||||
.unwrap_or(&path_dedot::CWD.as_path()),
|
||||
)? {
|
||||
// Second priority: find a `pyproject.toml` file in either an ancestor of
|
||||
// Third priority: find a `pyproject.toml` file in either an ancestor of
|
||||
// `stdin_filename` (if set) or the current working path all paths relative to
|
||||
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
|
||||
// the "closest" `pyproject.toml` file for every Python file later on,
|
||||
// so these act as the "default" settings.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
|
||||
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Parent, overrides)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
|
||||
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
|
||||
// Fourth priority: find a user-specific `pyproject.toml`, but resolve all paths
|
||||
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
|
||||
// end up the "closest" `pyproject.toml` file for every Python file later on, so
|
||||
// these act as the "default" settings.)
|
||||
let settings = resolve_settings(&pyproject, &Relativity::Cwd, Some(overrides))?;
|
||||
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
} else {
|
||||
// Fallback: load Ruff's default settings, and resolve all paths relative to the
|
||||
@@ -72,14 +81,13 @@ fn resolve(
|
||||
// "closest" `pyproject.toml` file for every Python file later on, so these act
|
||||
// as the "default" settings.)
|
||||
let mut config = Configuration::default();
|
||||
// Apply command-line options that override defaults.
|
||||
config.apply(overrides.clone());
|
||||
overrides.process_config(&mut config);
|
||||
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
|
||||
Ok(PyprojectDiscovery::Hierarchical(settings))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
pub fn main() -> Result<ExitCode> {
|
||||
// Extract command-line arguments.
|
||||
let (cli, overrides) = Cli::parse().partition();
|
||||
let log_level = extract_log_level(&cli);
|
||||
@@ -97,6 +105,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let pyproject_strategy = resolve(
|
||||
cli.isolated,
|
||||
cli.config.as_deref(),
|
||||
&overrides,
|
||||
cli.stdin_filename.as_deref(),
|
||||
@@ -136,7 +145,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
};
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
commands::explain(&code, &format)?;
|
||||
commands::explain(&code, format)?;
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
if cli.show_settings {
|
||||
@@ -159,13 +168,13 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
// but not apply fixes. That would allow us to avoid special-casing JSON
|
||||
// here.
|
||||
let autofix = if cli.diff {
|
||||
fixer::Mode::Diff
|
||||
fix::FixMode::Diff
|
||||
} else if fix || fix_only {
|
||||
fixer::Mode::Apply
|
||||
fix::FixMode::Apply
|
||||
} else if matches!(format, SerializationFormat::Json) {
|
||||
fixer::Mode::Generate
|
||||
fix::FixMode::Generate
|
||||
} else {
|
||||
fixer::Mode::None
|
||||
fix::FixMode::None
|
||||
};
|
||||
let violations = if cli.diff || fix_only {
|
||||
Violations::Hide
|
||||
@@ -178,31 +187,16 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
if cache {
|
||||
// `--no-cache` doesn't respect code changes, and so is often confusing during
|
||||
// development.
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"debug build without --no-cache.".bold()
|
||||
);
|
||||
warn_user_once!("debug build without --no-cache.");
|
||||
}
|
||||
|
||||
let printer = Printer::new(&format, &log_level, &autofix, &violations);
|
||||
if cli.watch {
|
||||
if !matches!(autofix, fixer::Mode::None) {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"--fix is not enabled in watch mode.".bold()
|
||||
);
|
||||
if !matches!(autofix, fix::FixMode::None) {
|
||||
warn_user_once!("--fix is not enabled in watch mode.");
|
||||
}
|
||||
if format != SerializationFormat::Text {
|
||||
one_time_warning!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"--format 'text' is used in watch mode.".bold()
|
||||
);
|
||||
warn_user_once!("--format 'text' is used in watch mode.");
|
||||
}
|
||||
|
||||
// Perform an initial run instantly.
|
||||
@@ -215,9 +209,9 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
cache.into(),
|
||||
fixer::Mode::None,
|
||||
fix::FixMode::None,
|
||||
)?;
|
||||
printer.write_continuously(&messages)?;
|
||||
printer.write_continuously(&messages);
|
||||
|
||||
// Configure the file watcher.
|
||||
let (tx, rx) = channel();
|
||||
@@ -245,9 +239,9 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
cache.into(),
|
||||
fixer::Mode::None,
|
||||
fix::FixMode::None,
|
||||
)?;
|
||||
printer.write_continuously(&messages)?;
|
||||
printer.write_continuously(&messages);
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
@@ -265,7 +259,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
// Generate lint violations.
|
||||
let diagnostics = if is_stdin {
|
||||
commands::run_stdin(
|
||||
cli.stdin_filename.as_deref(),
|
||||
cli.stdin_filename.map(fs::normalize_path).as_deref(),
|
||||
&pyproject_strategy,
|
||||
&file_strategy,
|
||||
&overrides,
|
||||
@@ -285,7 +279,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
|
||||
// Always try to print violations (the printer itself may suppress output),
|
||||
// unless we're writing fixes via stdin (in which case, the transformed
|
||||
// source code goes to stdout).
|
||||
if !(is_stdin && matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff)) {
|
||||
if !(is_stdin && matches!(autofix, fix::FixMode::Apply | fix::FixMode::Diff)) {
|
||||
printer.write_once(&diagnostics)?;
|
||||
}
|
||||
|
||||
@@ -6,18 +6,16 @@ use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, Sou
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use itertools::iterate;
|
||||
use rustpython_parser::ast::Location;
|
||||
use ruff::fs::relativize_path;
|
||||
use ruff::logging::LogLevel;
|
||||
use ruff::message::{Location, Message};
|
||||
use ruff::registry::RuleCode;
|
||||
use ruff::settings::types::SerializationFormat;
|
||||
use ruff::{fix, notify_user};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::linter::Diagnostics;
|
||||
use crate::logging::LogLevel;
|
||||
use crate::message::Message;
|
||||
use crate::registry::CheckCode;
|
||||
use crate::settings::types::SerializationFormat;
|
||||
use crate::tell_user;
|
||||
use crate::diagnostics::Diagnostics;
|
||||
|
||||
/// Enum to control whether lint violations are shown to the user.
|
||||
pub enum Violations {
|
||||
@@ -35,7 +33,7 @@ struct ExpandedFix<'a> {
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExpandedMessage<'a> {
|
||||
code: &'a CheckCode,
|
||||
code: &'a RuleCode,
|
||||
message: String,
|
||||
fix: Option<ExpandedFix<'a>>,
|
||||
location: Location,
|
||||
@@ -46,7 +44,7 @@ struct ExpandedMessage<'a> {
|
||||
pub struct Printer<'a> {
|
||||
format: &'a SerializationFormat,
|
||||
log_level: &'a LogLevel,
|
||||
autofix: &'a fixer::Mode,
|
||||
autofix: &'a fix::FixMode,
|
||||
violations: &'a Violations,
|
||||
}
|
||||
|
||||
@@ -54,7 +52,7 @@ impl<'a> Printer<'a> {
|
||||
pub fn new(
|
||||
format: &'a SerializationFormat,
|
||||
log_level: &'a LogLevel,
|
||||
autofix: &'a fixer::Mode,
|
||||
autofix: &'a fix::FixMode,
|
||||
violations: &'a Violations,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -67,7 +65,7 @@ impl<'a> Printer<'a> {
|
||||
|
||||
pub fn write_to_user(&self, message: &str) {
|
||||
if self.log_level >= &LogLevel::Default {
|
||||
tell_user!("{}", message);
|
||||
notify_user!("{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +82,7 @@ impl<'a> Printer<'a> {
|
||||
println!("Found {remaining} error(s).");
|
||||
}
|
||||
|
||||
if !matches!(self.autofix, fixer::Mode::Apply) {
|
||||
if !matches!(self.autofix, fix::FixMode::Apply) {
|
||||
let num_fixable = diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
@@ -98,9 +96,9 @@ impl<'a> Printer<'a> {
|
||||
Violations::Hide => {
|
||||
let fixed = diagnostics.fixed;
|
||||
if fixed > 0 {
|
||||
if matches!(self.autofix, fixer::Mode::Apply) {
|
||||
if matches!(self.autofix, fix::FixMode::Apply) {
|
||||
println!("Fixed {fixed} error(s).");
|
||||
} else if matches!(self.autofix, fixer::Mode::Diff) {
|
||||
} else if matches!(self.autofix, fix::FixMode::Diff) {
|
||||
println!("Would fix {fixed} error(s).");
|
||||
}
|
||||
}
|
||||
@@ -223,19 +221,30 @@ impl<'a> Printer<'a> {
|
||||
self.post_text(diagnostics);
|
||||
}
|
||||
SerializationFormat::Github => {
|
||||
// Generate error workflow command in GitHub Actions format
|
||||
// https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
||||
// Generate error workflow command in GitHub Actions format.
|
||||
// See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
||||
diagnostics.messages.iter().for_each(|message| {
|
||||
println!(
|
||||
"::notice title=Ruff,file={},line={},col={},endLine={},endColumn={}::({}) \
|
||||
{}",
|
||||
let label = format!(
|
||||
"{}{}{}{}{}{} {} {}",
|
||||
relativize_path(Path::new(&message.filename)),
|
||||
":",
|
||||
message.location.row(),
|
||||
":",
|
||||
message.location.column(),
|
||||
":",
|
||||
message.kind.code().as_ref(),
|
||||
message.kind.body(),
|
||||
);
|
||||
println!(
|
||||
"::error title=Ruff \
|
||||
({}),file={},line={},col={},endLine={},endColumn={}::{}",
|
||||
message.kind.code(),
|
||||
message.filename,
|
||||
message.location.row(),
|
||||
message.location.column(),
|
||||
message.end_location.row(),
|
||||
message.end_location.column(),
|
||||
message.kind.code(),
|
||||
message.kind.body(),
|
||||
label,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -254,7 +263,7 @@ impl<'a> Printer<'a> {
|
||||
"severity": "major",
|
||||
"fingerprint": message.kind.code(),
|
||||
"location": {
|
||||
"path": relativize_path(Path::new(&message.filename)),
|
||||
"path": message.filename,
|
||||
"lines": {
|
||||
"begin": message.location.row(),
|
||||
"end": message.end_location.row()
|
||||
@@ -272,13 +281,13 @@ impl<'a> Printer<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_continuously(&self, diagnostics: &Diagnostics) -> Result<()> {
|
||||
pub fn write_continuously(&self, diagnostics: &Diagnostics) {
|
||||
if matches!(self.log_level, LogLevel::Silent) {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if self.log_level >= &LogLevel::Default {
|
||||
tell_user!(
|
||||
notify_user!(
|
||||
"Found {} error(s). Watching for file changes.",
|
||||
diagnostics.messages.len()
|
||||
);
|
||||
@@ -292,10 +301,9 @@ impl<'a> Printer<'a> {
|
||||
print_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn clear_screen(&self) -> Result<()> {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
clearscreen::clear()?;
|
||||
@@ -303,7 +311,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn group_messages_by_filename(messages: &Vec<Message>) -> BTreeMap<&String, Vec<&Message>> {
|
||||
fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<&String, Vec<&Message>> {
|
||||
let mut grouped_messages = BTreeMap::default();
|
||||
for message in messages {
|
||||
grouped_messages
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user