Compare commits
6 Commits
v0.0.292
...
range-form
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de239ace74 | ||
|
|
53b5121f30 | ||
|
|
be93983e8e | ||
|
|
a1239b8f2d | ||
|
|
d1b12acb3c | ||
|
|
1aabf59f77 |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -9,5 +9,5 @@ updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "daily"
|
||||
labels: ["internal"]
|
||||
|
||||
5
.github/release.yml
vendored
5
.github/release.yml
vendored
@@ -4,6 +4,7 @@ changelog:
|
||||
labels:
|
||||
- internal
|
||||
- documentation
|
||||
- formatter
|
||||
categories:
|
||||
- title: Breaking Changes
|
||||
labels:
|
||||
@@ -11,6 +12,7 @@ changelog:
|
||||
- title: Rules
|
||||
labels:
|
||||
- rule
|
||||
- autofix
|
||||
- title: Settings
|
||||
labels:
|
||||
- configuration
|
||||
@@ -18,9 +20,6 @@ changelog:
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: Formatter
|
||||
labels:
|
||||
- formatter
|
||||
- title: Preview
|
||||
labels:
|
||||
- preview
|
||||
|
||||
5
.github/workflows/ci.yaml
vendored
5
.github/workflows/ci.yaml
vendored
@@ -96,6 +96,7 @@ jobs:
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- run: pip install black[d]==23.1.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests (Ubuntu)"
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
@@ -105,6 +106,10 @@ jobs:
|
||||
shell: bash
|
||||
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
|
||||
run: cargo insta test --all --all-features
|
||||
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
|
||||
# TODO: Skipped as it's currently broken. The resource were moved from the
|
||||
# ruff_cli to ruff crate, but this test was not updated.
|
||||
if: false
|
||||
# Check for broken links in the documentation.
|
||||
- run: cargo doc --all --no-deps
|
||||
env:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -208,9 +208,3 @@ cython_debug/
|
||||
# VIM
|
||||
.*.sw?
|
||||
.sw?
|
||||
|
||||
# Custom re-inclusions for the resolver test cases
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/lib
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so
|
||||
|
||||
@@ -204,7 +204,7 @@ As such, rule names should...
|
||||
For example, `AssertFalse` guards against `assert False` statements.
|
||||
|
||||
- _Not_ contain instructions on how to fix the violation, which instead belong in the rule
|
||||
documentation and the `fix_title`.
|
||||
documentation and the `autofix_title`.
|
||||
|
||||
- _Not_ contain a redundant prefix, like `Disallow` or `Banned`, which are already implied by the
|
||||
convention.
|
||||
|
||||
116
Cargo.lock
generated
116
Cargo.lock
generated
@@ -28,9 +28,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.1"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
||||
checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -313,9 +313,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.5"
|
||||
version = "4.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3"
|
||||
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -323,9 +323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.5"
|
||||
version = "4.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab"
|
||||
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1035,9 +1035,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.7"
|
||||
version = "0.17.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
|
||||
checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730"
|
||||
dependencies = [
|
||||
"console",
|
||||
"instant",
|
||||
@@ -1075,9 +1075,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.33.0"
|
||||
version = "1.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
|
||||
checksum = "a3e02c584f4595792d09509a94cdb92a3cef7592b1eb2d9877ee6f527062d0ea"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1260,7 +1260,8 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7773d520d4292e200ab1838f2daabe2feed7549f93b0a3c7582160a09e79ffde"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"libcst_derive",
|
||||
@@ -1274,7 +1275,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520197c50ba477f258cd7005ec5ed3a7393693ae6bec664990c7c8d9306a7c0d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
@@ -1335,9 +1337,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -1452,6 +1454,27 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
@@ -1582,9 +1605,9 @@ checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.3.12"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "887f66cc62717ea72caac4f1eb4e6f392224da3ffff3f40ec13ab427802746d6"
|
||||
checksum = "b05bf2c44c4cd12f03b2c3ca095f3aa21f44e43c16021c332e511884719705be"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
@@ -2028,7 +2051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.292"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2164,9 +2187,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.292"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
@@ -2184,6 +2206,8 @@ dependencies = [
|
||||
"log",
|
||||
"memchr",
|
||||
"natord",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
@@ -2266,6 +2290,8 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
@@ -2296,6 +2322,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"clap",
|
||||
"countme",
|
||||
"indoc",
|
||||
"insta",
|
||||
"itertools 0.11.0",
|
||||
"memchr",
|
||||
@@ -2310,7 +2337,6 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
@@ -2342,6 +2368,7 @@ dependencies = [
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
"lexical-parse-float",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
@@ -2351,12 +2378,13 @@ name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
@@ -2382,6 +2410,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"is-macro",
|
||||
"num-traits",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
@@ -2495,9 +2524,7 @@ dependencies = [
|
||||
"ruff_formatter",
|
||||
"ruff_linter",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_source_file",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2544,10 +2571,20 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.101.4",
|
||||
"sct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.100.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.4"
|
||||
@@ -2633,9 +2670,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.19"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@@ -2945,18 +2982,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.49"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.49"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3233,16 +3270,16 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.8.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
|
||||
checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-webpki",
|
||||
"rustls-webpki 0.100.2",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
@@ -3460,9 +3497,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
||||
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
|
||||
dependencies = [
|
||||
"rustls-webpki 0.100.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
@@ -3477,9 +3517,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wild"
|
||||
version = "2.2.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d01931a94d5a115a53f95292f51d316856b68a035618eb831bbba593a30b67"
|
||||
checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -15,17 +15,19 @@ license = "MIT"
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.5", features = ["derive"] }
|
||||
clap = { version = "4.4.4", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.10" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.33.0", feature = ["filters", "glob"] }
|
||||
insta = { version = "1.32.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.11.0" }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = "2.6.4"
|
||||
memchr = "2.6.3"
|
||||
num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.67" }
|
||||
@@ -43,7 +45,7 @@ strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.37" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.49" }
|
||||
thiserror = { version = "1.0.48" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
@@ -53,7 +55,8 @@ unicode-width = "0.1.11"
|
||||
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "03179b55ebe7e916f1722e18e8f0b87c01616d1f", default-features = false }
|
||||
# v1.0.1
|
||||
libcst = { version = "0.1.0", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
12
README.md
12
README.md
@@ -27,10 +27,10 @@ An extremely fast Python linter, written in Rust.
|
||||
- ⚡️ 10-100x faster than existing linters
|
||||
- 🐍 Installable via `pip`
|
||||
- 🛠️ `pyproject.toml` support
|
||||
- 🤝 Python 3.12 compatibility
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/)
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [600 built-in rules](https://docs.astral.sh/ruff/rules/)
|
||||
- ⚖️ [Near-parity](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.292
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -176,7 +176,7 @@ If left unspecified, the default configuration is equivalent to:
|
||||
select = ["E", "F"]
|
||||
ignore = []
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
|
||||
unfixable = []
|
||||
|
||||
@@ -233,7 +233,7 @@ linting command.
|
||||
|
||||
<!-- Begin section: Rules -->
|
||||
|
||||
**Ruff supports over 700 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
**Ruff supports over 600 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
Rust as a first-party feature.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.0.290"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
@@ -23,7 +23,7 @@ configparser = { version = "3.0.2" }
|
||||
itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
regex = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -16,8 +16,8 @@ use ruff_linter::settings::types::PythonVersion;
|
||||
use ruff_linter::warn_user;
|
||||
use ruff_workspace::options::{
|
||||
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
|
||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintOptions,
|
||||
McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, McCabeOptions,
|
||||
Options, Pep8NamingOptions, PydocstyleOptions,
|
||||
};
|
||||
use ruff_workspace::pyproject::Pyproject;
|
||||
|
||||
@@ -103,7 +103,6 @@ pub(crate) fn convert(
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options = Options::default();
|
||||
let mut lint_options = LintOptions::default();
|
||||
let mut flake8_annotations = Flake8AnnotationsOptions::default();
|
||||
let mut flake8_bugbear = Flake8BugbearOptions::default();
|
||||
let mut flake8_builtins = Flake8BuiltinsOptions::default();
|
||||
@@ -151,7 +150,7 @@ pub(crate) fn convert(
|
||||
"per-file-ignores" | "per_file_ignores" => {
|
||||
match parser::parse_files_to_codes_mapping(value.as_ref()) {
|
||||
Ok(per_file_ignores) => {
|
||||
lint_options.per_file_ignores =
|
||||
options.per_file_ignores =
|
||||
Some(parser::collect_per_file_ignores(per_file_ignores));
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -359,47 +358,47 @@ pub(crate) fn convert(
|
||||
}
|
||||
|
||||
// Deduplicate and sort.
|
||||
lint_options.select = Some(
|
||||
options.select = Some(
|
||||
select
|
||||
.into_iter()
|
||||
.sorted_by_key(RuleSelector::prefix_and_code)
|
||||
.collect(),
|
||||
);
|
||||
lint_options.ignore = Some(
|
||||
options.ignore = Some(
|
||||
ignore
|
||||
.into_iter()
|
||||
.sorted_by_key(RuleSelector::prefix_and_code)
|
||||
.collect(),
|
||||
);
|
||||
if flake8_annotations != Flake8AnnotationsOptions::default() {
|
||||
lint_options.flake8_annotations = Some(flake8_annotations);
|
||||
options.flake8_annotations = Some(flake8_annotations);
|
||||
}
|
||||
if flake8_bugbear != Flake8BugbearOptions::default() {
|
||||
lint_options.flake8_bugbear = Some(flake8_bugbear);
|
||||
options.flake8_bugbear = Some(flake8_bugbear);
|
||||
}
|
||||
if flake8_builtins != Flake8BuiltinsOptions::default() {
|
||||
lint_options.flake8_builtins = Some(flake8_builtins);
|
||||
options.flake8_builtins = Some(flake8_builtins);
|
||||
}
|
||||
if flake8_errmsg != Flake8ErrMsgOptions::default() {
|
||||
lint_options.flake8_errmsg = Some(flake8_errmsg);
|
||||
options.flake8_errmsg = Some(flake8_errmsg);
|
||||
}
|
||||
if flake8_pytest_style != Flake8PytestStyleOptions::default() {
|
||||
lint_options.flake8_pytest_style = Some(flake8_pytest_style);
|
||||
options.flake8_pytest_style = Some(flake8_pytest_style);
|
||||
}
|
||||
if flake8_quotes != Flake8QuotesOptions::default() {
|
||||
lint_options.flake8_quotes = Some(flake8_quotes);
|
||||
options.flake8_quotes = Some(flake8_quotes);
|
||||
}
|
||||
if flake8_tidy_imports != Flake8TidyImportsOptions::default() {
|
||||
lint_options.flake8_tidy_imports = Some(flake8_tidy_imports);
|
||||
options.flake8_tidy_imports = Some(flake8_tidy_imports);
|
||||
}
|
||||
if mccabe != McCabeOptions::default() {
|
||||
lint_options.mccabe = Some(mccabe);
|
||||
options.mccabe = Some(mccabe);
|
||||
}
|
||||
if pep8_naming != Pep8NamingOptions::default() {
|
||||
lint_options.pep8_naming = Some(pep8_naming);
|
||||
options.pep8_naming = Some(pep8_naming);
|
||||
}
|
||||
if pydocstyle != PydocstyleOptions::default() {
|
||||
lint_options.pydocstyle = Some(pydocstyle);
|
||||
options.pydocstyle = Some(pydocstyle);
|
||||
}
|
||||
|
||||
// Extract any settings from the existing `pyproject.toml`.
|
||||
@@ -437,10 +436,6 @@ pub(crate) fn convert(
|
||||
}
|
||||
}
|
||||
|
||||
if lint_options != LintOptions::default() {
|
||||
options.lint = Some(lint_options);
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
Pyproject::new(options)
|
||||
}
|
||||
@@ -469,7 +464,7 @@ mod tests {
|
||||
use ruff_linter::rules::flake8_quotes;
|
||||
use ruff_linter::rules::pydocstyle::settings::Convention;
|
||||
use ruff_linter::settings::types::PythonVersion;
|
||||
use ruff_workspace::options::{Flake8QuotesOptions, LintOptions, Options, PydocstyleOptions};
|
||||
use ruff_workspace::options::{Flake8QuotesOptions, Options, PydocstyleOptions};
|
||||
use ruff_workspace::pyproject::Pyproject;
|
||||
|
||||
use crate::converter::DEFAULT_SELECTORS;
|
||||
@@ -479,8 +474,8 @@ mod tests {
|
||||
use super::super::plugin::Plugin;
|
||||
use super::convert;
|
||||
|
||||
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintOptions {
|
||||
LintOptions {
|
||||
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
|
||||
Options {
|
||||
ignore: Some(vec![]),
|
||||
select: Some(
|
||||
DEFAULT_SELECTORS
|
||||
@@ -490,7 +485,7 @@ mod tests {
|
||||
.sorted_by_key(RuleSelector::prefix_and_code)
|
||||
.collect(),
|
||||
),
|
||||
..LintOptions::default()
|
||||
..Options::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,10 +496,7 @@ mod tests {
|
||||
&ExternalConfig::default(),
|
||||
None,
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(lint_default_options([])),
|
||||
..Options::default()
|
||||
});
|
||||
let expected = Pyproject::new(default_options([]));
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@@ -520,8 +512,7 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||
lint: Some(lint_default_options([])),
|
||||
..Options::default()
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -538,8 +529,7 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||
lint: Some(lint_default_options([])),
|
||||
..Options::default()
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -554,10 +544,7 @@ mod tests {
|
||||
&ExternalConfig::default(),
|
||||
Some(vec![]),
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(lint_default_options([])),
|
||||
..Options::default()
|
||||
});
|
||||
let expected = Pyproject::new(default_options([]));
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@@ -572,16 +559,13 @@ mod tests {
|
||||
Some(vec![]),
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(LintOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([])
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..Options::default()
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -600,15 +584,12 @@ mod tests {
|
||||
Some(vec![Plugin::Flake8Docstrings]),
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(LintOptions {
|
||||
pydocstyle: Some(PydocstyleOptions {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: None,
|
||||
property_decorators: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Pydocstyle.into()])
|
||||
pydocstyle: Some(PydocstyleOptions {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: None,
|
||||
property_decorators: None,
|
||||
}),
|
||||
..Options::default()
|
||||
..default_options([Linter::Pydocstyle.into()])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -624,16 +605,13 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(LintOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Flake8Quotes.into()])
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..Options::default()
|
||||
..default_options([Linter::Flake8Quotes.into()])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -652,8 +630,7 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
target_version: Some(PythonVersion::Py38),
|
||||
lint: Some(lint_default_options([])),
|
||||
..Options::default()
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ruff_linter::registry::Linter;
|
||||
use ruff_linter::rule_selector::PreviewOptions;
|
||||
use ruff_linter::settings::types::PreviewMode;
|
||||
use ruff_linter::RuleSelector;
|
||||
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
@@ -332,7 +332,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
|
||||
.filter(|plugin| {
|
||||
for selector in selectors {
|
||||
if selector
|
||||
.rules(&PreviewOptions::default())
|
||||
.rules(PreviewMode::Disabled)
|
||||
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -35,7 +35,7 @@ once_cell.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
url = "2.3.1"
|
||||
ureq = "2.8.0"
|
||||
ureq = "2.6.2"
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
codspeed-criterion-compat = { version="2.2.0", default-features = false, optional = true}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ fn benchmark_formatter(criterion: &mut Criterion) {
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
|
||||
// Parse the AST.
|
||||
let module = parse_tokens(tokens, case.code(), Mode::Module, "<filename>")
|
||||
let module = parse_tokens(tokens, Mode::Module, "<filename>")
|
||||
.expect("Input to be a valid python program");
|
||||
|
||||
b.iter(|| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.292"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -70,7 +70,7 @@ insta = { workspace = true, features = ["filters"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
tempfile = "3.6.0"
|
||||
test-case = { workspace = true }
|
||||
ureq = { version = "2.8.0", features = [] }
|
||||
ureq = { version = "2.6.2", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
3
crates/ruff_cli/resources/test/fixtures/cache_mutable/source.py
vendored
Normal file
3
crates/ruff_cli/resources/test/fixtures/cache_mutable/source.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
a = 1
|
||||
|
||||
__all__ = list(["a", "b"])
|
||||
@@ -11,6 +11,7 @@ use ruff_linter::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
|
||||
};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_python_formatter::LspRowColumn;
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
|
||||
@@ -88,7 +89,7 @@ pub struct CheckCommand {
|
||||
show_source: bool,
|
||||
#[clap(long, overrides_with("show_source"), hide = true)]
|
||||
no_show_source: bool,
|
||||
/// Show an enumeration of all fixed lint violations.
|
||||
/// Show an enumeration of all autofixed lint violations.
|
||||
/// Use `--no-show-fixes` to disable.
|
||||
#[arg(long, overrides_with("no_show_fixes"))]
|
||||
show_fixes: bool,
|
||||
@@ -202,7 +203,7 @@ pub struct CheckCommand {
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
pub extend_exclude: Option<Vec<FilePattern>>,
|
||||
/// List of rule codes to treat as eligible for fix. Only applicable when fix itself is enabled (e.g., via `--fix`).
|
||||
/// 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 = ',',
|
||||
@@ -212,7 +213,7 @@ pub struct CheckCommand {
|
||||
hide_possible_values = true
|
||||
)]
|
||||
pub fixable: Option<Vec<RuleSelector>>,
|
||||
/// List of rule codes to treat as ineligible for fix. Only applicable when fix itself is enabled (e.g., via `--fix`).
|
||||
/// 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 = ',',
|
||||
@@ -288,7 +289,7 @@ pub struct CheckCommand {
|
||||
conflicts_with = "exit_non_zero_on_fix"
|
||||
)]
|
||||
pub exit_zero: bool,
|
||||
/// Exit with a non-zero status code if any files were modified via fix, even if no lint violations remain.
|
||||
/// Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain.
|
||||
#[arg(long, help_heading = "Miscellaneous", conflicts_with = "exit_zero")]
|
||||
pub exit_non_zero_on_fix: bool,
|
||||
/// Show counts for every rule with at least one violation.
|
||||
@@ -395,6 +396,14 @@ pub struct FormatCommand {
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
/// Range formatting start: Zero-indexed row and zero-indexed char-based column separated by
|
||||
/// colon, e.g. `1:2`
|
||||
#[clap(long)]
|
||||
pub start: Option<LspRowColumn>,
|
||||
/// Range formatting end: Zero-indexed row and zero-indexed char-based column separated by
|
||||
/// colon, e.g. `3:4`
|
||||
#[clap(long)]
|
||||
pub end: Option<LspRowColumn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
@@ -516,6 +525,8 @@ impl FormatCommand {
|
||||
files: self.files,
|
||||
isolated: self.isolated,
|
||||
stdin_filename: self.stdin_filename,
|
||||
start: self.start,
|
||||
end: self.end,
|
||||
},
|
||||
CliOverrides {
|
||||
line_length: self.line_length,
|
||||
@@ -572,6 +583,8 @@ pub struct FormatArguments {
|
||||
pub files: Vec<PathBuf>,
|
||||
pub isolated: bool,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
pub start: Option<LspRowColumn>,
|
||||
pub end: Option<LspRowColumn>,
|
||||
}
|
||||
|
||||
/// CLI settings that function as configuration overrides.
|
||||
@@ -610,7 +623,7 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
config.cache_dir = Some(cache_dir.clone());
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = &self.dummy_variable_rgx {
|
||||
config.lint.dummy_variable_rgx = Some(dummy_variable_rgx.clone());
|
||||
config.dummy_variable_rgx = Some(dummy_variable_rgx.clone());
|
||||
}
|
||||
if let Some(exclude) = &self.exclude {
|
||||
config.exclude = Some(exclude.clone());
|
||||
@@ -624,7 +637,7 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
if let Some(fix_only) = &self.fix_only {
|
||||
config.fix_only = Some(*fix_only);
|
||||
}
|
||||
config.lint.rule_selections.push(RuleSelection {
|
||||
config.rule_selections.push(RuleSelection {
|
||||
select: self.select.clone(),
|
||||
ignore: self
|
||||
.ignore
|
||||
@@ -657,7 +670,7 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
config.preview = Some(*preview);
|
||||
}
|
||||
if let Some(per_file_ignores) = &self.per_file_ignores {
|
||||
config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
||||
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);
|
||||
|
||||
@@ -7,12 +7,12 @@ use log::{debug, error};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use ruff_linter::linter::add_noqa_to_path;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::diagnostics::LintSource;
|
||||
|
||||
/// Add `noqa` directives to a collection of files.
|
||||
pub(crate) fn add_noqa(
|
||||
@@ -57,8 +57,8 @@ pub(crate) fn add_noqa(
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
let LintSource(source_kind) = match LintSource::try_from_path(path, source_type) {
|
||||
Ok(Some(source)) => source,
|
||||
Ok(None) => return None,
|
||||
Err(e) => {
|
||||
error!("Failed to extract source from {}: {e}", path.display());
|
||||
|
||||
@@ -35,7 +35,7 @@ pub(crate) fn check(
|
||||
overrides: &CliOverrides,
|
||||
cache: flags::Cache,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Collect all the Python files to check.
|
||||
let start = Instant::now();
|
||||
@@ -119,7 +119,7 @@ pub(crate) fn check(
|
||||
}
|
||||
});
|
||||
|
||||
lint_path(path, package, &settings.linter, cache, noqa, fix_mode).map_err(|e| {
|
||||
lint_path(path, package, &settings.linter, cache, noqa, autofix).map_err(|e| {
|
||||
(Some(path.to_owned()), {
|
||||
let mut error = e.to_string();
|
||||
for cause in e.chain() {
|
||||
@@ -198,10 +198,10 @@ fn lint_path(
|
||||
settings: &LinterSettings,
|
||||
cache: Option<&Cache>,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
let result = catch_unwind(|| {
|
||||
crate::diagnostics::lint_path(path, package, settings, cache, noqa, fix_mode)
|
||||
crate::diagnostics::lint_path(path, package, settings, cache, noqa, autofix)
|
||||
});
|
||||
|
||||
match result {
|
||||
|
||||
@@ -16,7 +16,7 @@ pub(crate) fn check_stdin(
|
||||
pyproject_config: &PyprojectConfig,
|
||||
overrides: &CliOverrides,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
if let Some(filename) = filename {
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
@@ -33,7 +33,7 @@ pub(crate) fn check_stdin(
|
||||
stdin,
|
||||
&pyproject_config.settings,
|
||||
noqa,
|
||||
fix_mode,
|
||||
autofix,
|
||||
)?;
|
||||
diagnostics.messages.sort_unstable();
|
||||
Ok(diagnostics)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub(crate) fn config(key: Option<&str>) -> Result<()> {
|
||||
match key {
|
||||
None => print!("{}", Options::metadata()),
|
||||
Some(key) => match Options::metadata().find(key) {
|
||||
Some(key) => match Options::metadata().get(key) {
|
||||
None => {
|
||||
return Err(anyhow!("Unknown option: {key}"));
|
||||
}
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
use ruff_diagnostics::SourceMap;
|
||||
use ruff_linter::fs;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError, PyFormatOptions};
|
||||
use ruff_source_file::{find_newline, LineEnding};
|
||||
use ruff_workspace::resolver::python_files_in_path;
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::panic::{catch_unwind, PanicError};
|
||||
@@ -68,23 +64,24 @@ pub(crate) fn format(
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
|
||||
let SourceType::Python(source_type) = SourceType::from(path) else {
|
||||
let SourceType::Python(
|
||||
source_type @ (PySourceType::Python | PySourceType::Stub),
|
||||
) = SourceType::from(path)
|
||||
else {
|
||||
// Ignore any non-Python files.
|
||||
return None;
|
||||
};
|
||||
|
||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
||||
let options = resolved_settings.formatter.to_format_options(source_type);
|
||||
debug!("Formatting {} with {:?}", path.display(), options);
|
||||
|
||||
Some(
|
||||
match catch_unwind(|| {
|
||||
format_path(path, &resolved_settings.formatter, source_type, mode)
|
||||
}) {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
|
||||
}
|
||||
},
|
||||
)
|
||||
Some(match catch_unwind(|| format_path(path, options, mode)) {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
|
||||
}
|
||||
@@ -142,153 +139,32 @@ pub(crate) fn format(
|
||||
#[tracing::instrument(skip_all, fields(path = %path.display()))]
|
||||
fn format_path(
|
||||
path: &Path,
|
||||
settings: &FormatterSettings,
|
||||
source_type: PySourceType,
|
||||
options: PyFormatOptions,
|
||||
mode: FormatMode,
|
||||
) -> Result<FormatCommandResult, FormatCommandError> {
|
||||
// Extract the sources from the file.
|
||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
Ok(None) => return Ok(FormatCommandResult::Unchanged),
|
||||
Err(err) => {
|
||||
return Err(FormatCommandError::Read(Some(path.to_path_buf()), err));
|
||||
}
|
||||
let unformatted = std::fs::read_to_string(path)
|
||||
.map_err(|err| FormatCommandError::Read(Some(path.to_path_buf()), err))?;
|
||||
|
||||
let line_ending = match find_newline(&unformatted) {
|
||||
Some((_, LineEnding::Lf)) | None => ruff_formatter::printer::LineEnding::LineFeed,
|
||||
Some((_, LineEnding::Cr)) => ruff_formatter::printer::LineEnding::CarriageReturn,
|
||||
Some((_, LineEnding::CrLf)) => ruff_formatter::printer::LineEnding::CarriageReturnLineFeed,
|
||||
};
|
||||
|
||||
// Format the source.
|
||||
match format_source(source_kind, source_type, Some(path), settings)? {
|
||||
FormattedSource::Formatted(formatted) => {
|
||||
if mode.is_write() {
|
||||
let mut writer = File::create(path).map_err(|err| {
|
||||
FormatCommandError::Write(Some(path.to_path_buf()), err.into())
|
||||
})?;
|
||||
formatted
|
||||
.write(&mut writer)
|
||||
.map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
|
||||
}
|
||||
Ok(FormatCommandResult::Formatted)
|
||||
}
|
||||
FormattedSource::Unchanged(_) => Ok(FormatCommandResult::Unchanged),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum FormattedSource {
|
||||
/// The source was formatted, and the [`SourceKind`] contains the transformed source code.
|
||||
Formatted(SourceKind),
|
||||
/// The source was unchanged, and the [`SourceKind`] contains the original source code.
|
||||
Unchanged(SourceKind),
|
||||
}
|
||||
|
||||
impl From<FormattedSource> for FormatCommandResult {
|
||||
fn from(value: FormattedSource) -> Self {
|
||||
match value {
|
||||
FormattedSource::Formatted(_) => FormatCommandResult::Formatted,
|
||||
FormattedSource::Unchanged(_) => FormatCommandResult::Unchanged,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FormattedSource {
|
||||
pub(crate) fn source_kind(&self) -> &SourceKind {
|
||||
match self {
|
||||
FormattedSource::Formatted(source_kind) => source_kind,
|
||||
FormattedSource::Unchanged(source_kind) => source_kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a [`SourceKind`], returning the transformed [`SourceKind`], or `None` if the source was
|
||||
/// unchanged.
|
||||
pub(crate) fn format_source(
|
||||
source_kind: SourceKind,
|
||||
source_type: PySourceType,
|
||||
path: Option<&Path>,
|
||||
settings: &FormatterSettings,
|
||||
) -> Result<FormattedSource, FormatCommandError> {
|
||||
match source_kind {
|
||||
SourceKind::Python(unformatted) => {
|
||||
let options = settings.to_format_options(source_type, &unformatted);
|
||||
|
||||
let formatted = format_module_source(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::Format(path.map(Path::to_path_buf), err))?;
|
||||
|
||||
let formatted = formatted.into_code();
|
||||
if formatted.len() == unformatted.len() && formatted == *unformatted {
|
||||
Ok(FormattedSource::Unchanged(SourceKind::Python(unformatted)))
|
||||
} else {
|
||||
Ok(FormattedSource::Formatted(SourceKind::Python(formatted)))
|
||||
}
|
||||
}
|
||||
SourceKind::IpyNotebook(notebook) => {
|
||||
if !notebook.is_python_notebook() {
|
||||
return Ok(FormattedSource::Unchanged(SourceKind::IpyNotebook(
|
||||
notebook,
|
||||
)));
|
||||
}
|
||||
|
||||
let options = settings.to_format_options(source_type, notebook.source_code());
|
||||
|
||||
let mut output: Option<String> = None;
|
||||
let mut last: Option<TextSize> = None;
|
||||
let mut source_map = SourceMap::default();
|
||||
|
||||
// Format each cell individually.
|
||||
for (start, end) in notebook.cell_offsets().iter().tuple_windows::<(_, _)>() {
|
||||
let range = TextRange::new(*start, *end);
|
||||
let unformatted = ¬ebook.source_code()[range];
|
||||
|
||||
// Format the cell.
|
||||
let formatted = format_module_source(unformatted, options.clone())
|
||||
.map_err(|err| FormatCommandError::Format(path.map(Path::to_path_buf), err))?;
|
||||
|
||||
// If the cell is unchanged, skip it.
|
||||
let formatted = formatted.as_code();
|
||||
if formatted.len() == unformatted.len() && formatted == unformatted {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is the first newly-formatted cell, initialize the output.
|
||||
let output = output
|
||||
.get_or_insert_with(|| String::with_capacity(notebook.source_code().len()));
|
||||
|
||||
// Add all contents from `last` to the current cell.
|
||||
let slice = ¬ebook.source_code()
|
||||
[TextRange::new(last.unwrap_or_default(), range.start())];
|
||||
output.push_str(slice);
|
||||
|
||||
// Add the start source marker for the cell.
|
||||
source_map.push_marker(*start, output.text_len());
|
||||
|
||||
// Add the cell itself.
|
||||
output.push_str(formatted);
|
||||
|
||||
// Add the end source marker for the added cell.
|
||||
source_map.push_marker(*end, output.text_len());
|
||||
|
||||
// Track that the cell was formatted.
|
||||
last = Some(*end);
|
||||
}
|
||||
|
||||
// If the file was unchanged, return `None`.
|
||||
let (Some(mut output), Some(last)) = (output, last) else {
|
||||
return Ok(FormattedSource::Unchanged(SourceKind::IpyNotebook(
|
||||
notebook,
|
||||
)));
|
||||
};
|
||||
|
||||
// Add the remaining content.
|
||||
let slice = ¬ebook.source_code()[usize::from(last)..];
|
||||
output.push_str(slice);
|
||||
|
||||
// Update the notebook.
|
||||
let mut notebook = notebook.clone();
|
||||
notebook.update(&source_map, output);
|
||||
|
||||
Ok(FormattedSource::Formatted(SourceKind::IpyNotebook(
|
||||
notebook,
|
||||
)))
|
||||
let options = options.with_line_ending(line_ending);
|
||||
|
||||
let formatted = format_module_source(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::FormatModule(Some(path.to_path_buf()), err))?;
|
||||
|
||||
let formatted = formatted.as_code();
|
||||
if formatted.len() == unformatted.len() && formatted == unformatted {
|
||||
Ok(FormatCommandResult::Unchanged)
|
||||
} else {
|
||||
if mode.is_write() {
|
||||
std::fs::write(path, formatted.as_bytes())
|
||||
.map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
|
||||
}
|
||||
Ok(FormatCommandResult::Formatted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,10 +246,10 @@ impl Display for FormatResultSummary {
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum FormatCommandError {
|
||||
Ignore(#[from] ignore::Error),
|
||||
Read(Option<PathBuf>, io::Error),
|
||||
Write(Option<PathBuf>, io::Error),
|
||||
FormatModule(Option<PathBuf>, FormatModuleError),
|
||||
Panic(Option<PathBuf>, PanicError),
|
||||
Read(Option<PathBuf>, SourceError),
|
||||
Format(Option<PathBuf>, FormatModuleError),
|
||||
Write(Option<PathBuf>, SourceError),
|
||||
}
|
||||
|
||||
impl Display for FormatCommandError {
|
||||
@@ -426,7 +302,7 @@ impl Display for FormatCommandError {
|
||||
write!(f, "{}{} {err}", "Failed to write".bold(), ":".bold())
|
||||
}
|
||||
}
|
||||
Self::Format(path, err) => {
|
||||
Self::FormatModule(path, err) => {
|
||||
if let Some(path) = path {
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use std::io::stdout;
|
||||
use std::io::{stdout, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::warn;
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_formatter::{
|
||||
format_module_source, format_module_source_range, LspRowColumn, PyFormatOptions,
|
||||
};
|
||||
use ruff_workspace::resolver::python_file_at_path;
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::commands::format::{format_source, FormatCommandError, FormatCommandResult, FormatMode};
|
||||
use crate::commands::format::{FormatCommandError, FormatCommandResult, FormatMode};
|
||||
use crate::resolve::resolve;
|
||||
use crate::stdin::read_from_stdin;
|
||||
use crate::ExitStatus;
|
||||
@@ -35,19 +36,15 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
}
|
||||
}
|
||||
|
||||
// Format the file.
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
return Ok(ExitStatus::Success);
|
||||
};
|
||||
let options = pyproject_config
|
||||
.settings
|
||||
.formatter
|
||||
.to_format_options(path.map(PySourceType::from).unwrap_or_default());
|
||||
|
||||
// Format the file.
|
||||
match format_source_code(
|
||||
path,
|
||||
&pyproject_config.settings.formatter,
|
||||
source_type,
|
||||
mode,
|
||||
) {
|
||||
match format_source(path, options, mode, cli.start, cli.end) {
|
||||
Ok(result) => match mode {
|
||||
FormatMode::Write => Ok(ExitStatus::Success),
|
||||
FormatMode::Check => {
|
||||
@@ -66,35 +63,34 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
}
|
||||
|
||||
/// Format source code read from `stdin`.
|
||||
fn format_source_code(
|
||||
fn format_source(
|
||||
path: Option<&Path>,
|
||||
settings: &FormatterSettings,
|
||||
source_type: PySourceType,
|
||||
options: PyFormatOptions,
|
||||
mode: FormatMode,
|
||||
start: Option<LspRowColumn>,
|
||||
end: Option<LspRowColumn>,
|
||||
) -> Result<FormatCommandResult, FormatCommandError> {
|
||||
// Read the source from stdin.
|
||||
let source_code = read_from_stdin()
|
||||
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err.into()))?;
|
||||
|
||||
let source_kind = match SourceKind::from_source_code(source_code, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
Ok(None) => return Ok(FormatCommandResult::Unchanged),
|
||||
Err(err) => {
|
||||
return Err(FormatCommandError::Read(path.map(Path::to_path_buf), err));
|
||||
}
|
||||
};
|
||||
|
||||
// Format the source.
|
||||
let formatted = format_source(source_kind, source_type, path, settings)?;
|
||||
|
||||
// Write to stdout regardless of whether the source was formatted.
|
||||
if mode.is_write() {
|
||||
let mut writer = stdout().lock();
|
||||
let unformatted = read_from_stdin()
|
||||
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err))?;
|
||||
let formatted = if start.is_some() || end.is_some() {
|
||||
let formatted = format_module_source_range(&unformatted, options, start, end)
|
||||
.map_err(|err| FormatCommandError::FormatModule(path.map(Path::to_path_buf), err))?;
|
||||
formatted
|
||||
.source_kind()
|
||||
.write(&mut writer)
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
} else {
|
||||
let formatted = format_module_source(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::FormatModule(path.map(Path::to_path_buf), err))?;
|
||||
let formatted = formatted.as_code();
|
||||
formatted.to_string()
|
||||
};
|
||||
if formatted.len() == unformatted.len() && formatted == unformatted {
|
||||
Ok(FormatCommandResult::Unchanged)
|
||||
} else {
|
||||
if mode.is_write() {
|
||||
stdout()
|
||||
.lock()
|
||||
.write_all(formatted.as_bytes())
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
}
|
||||
Ok(FormatCommandResult::Formatted)
|
||||
}
|
||||
|
||||
Ok(FormatCommandResult::from(formatted))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_diagnostics::FixAvailability;
|
||||
use ruff_diagnostics::AutofixKind;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
|
||||
use crate::args::HelpFormat;
|
||||
@@ -17,7 +17,7 @@ struct Explanation<'a> {
|
||||
linter: &'a str,
|
||||
summary: &'a str,
|
||||
message_formats: &'a [&'a str],
|
||||
fix: String,
|
||||
autofix: String,
|
||||
explanation: Option<&'a str>,
|
||||
preview: bool,
|
||||
}
|
||||
@@ -26,14 +26,14 @@ impl<'a> Explanation<'a> {
|
||||
fn from_rule(rule: &'a Rule) -> Self {
|
||||
let code = rule.noqa_code().to_string();
|
||||
let (linter, _) = Linter::parse_code(&code).unwrap();
|
||||
let fix = rule.fixable().to_string();
|
||||
let autofix = rule.autofixable().to_string();
|
||||
Self {
|
||||
name: rule.as_ref(),
|
||||
code,
|
||||
linter: linter.name(),
|
||||
summary: rule.message_formats()[0],
|
||||
message_formats: rule.message_formats(),
|
||||
fix,
|
||||
autofix,
|
||||
explanation: rule.explanation(),
|
||||
preview: rule.is_preview(),
|
||||
}
|
||||
@@ -51,12 +51,9 @@ fn format_rule_text(rule: Rule) -> String {
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
let fix_availability = rule.fixable();
|
||||
if matches!(
|
||||
fix_availability,
|
||||
FixAvailability::Always | FixAvailability::Sometimes
|
||||
) {
|
||||
output.push_str(&fix_availability.to_string());
|
||||
let autofix = rule.autofixable();
|
||||
if matches!(autofix, AutofixKind::Always | AutofixKind::Sometimes) {
|
||||
output.push_str(&autofix.to_string());
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ use std::ops::AddAssign;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use colored::Colorize;
|
||||
use filetime::FileTime;
|
||||
use log::{debug, error, warn};
|
||||
use rustc_hash::FxHashMap;
|
||||
use similar::TextDiff;
|
||||
use thiserror::Error;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
||||
@@ -22,12 +23,12 @@ use ruff_linter::message::Message;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_notebook::{Cell, Notebook, NotebookError, NotebookIndex};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::{SourceType, TomlSourceType};
|
||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::Settings;
|
||||
@@ -81,38 +82,15 @@ impl Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate [`Diagnostics`] based on a [`SourceError`].
|
||||
/// Generate [`Diagnostics`] based on a [`SourceExtractionError`].
|
||||
pub(crate) fn from_source_error(
|
||||
err: &SourceError,
|
||||
err: &SourceExtractionError,
|
||||
path: Option<&Path>,
|
||||
settings: &LinterSettings,
|
||||
) -> Self {
|
||||
let diagnostic = match err {
|
||||
// IO errors.
|
||||
SourceError::Io(_)
|
||||
| SourceError::Notebook(NotebookError::Io(_) | NotebookError::Json(_)) => {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
}
|
||||
// Syntax errors.
|
||||
SourceError::Notebook(
|
||||
NotebookError::InvalidJson(_)
|
||||
| NotebookError::InvalidSchema(_)
|
||||
| NotebookError::InvalidFormat(_),
|
||||
) => Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
),
|
||||
};
|
||||
|
||||
let diagnostic = Diagnostic::from(err);
|
||||
if settings.rules.enabled(diagnostic.kind.rule()) {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let name = path.map_or_else(|| "-".into(), std::path::Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Message::from_diagnostic(
|
||||
@@ -169,7 +147,7 @@ pub(crate) fn lint_path(
|
||||
settings: &LinterSettings,
|
||||
cache: Option<&Cache>,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// Check the cache.
|
||||
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
|
||||
@@ -178,7 +156,7 @@ pub(crate) fn lint_path(
|
||||
// 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 caching = match cache {
|
||||
Some(cache) if noqa.into() && fix_mode.is_generate() => {
|
||||
Some(cache) if noqa.into() && autofix.is_generate() => {
|
||||
let relative_path = cache
|
||||
.relative_path(path)
|
||||
.expect("wrong package cache for file");
|
||||
@@ -205,12 +183,13 @@ pub(crate) fn lint_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
|
||||
{
|
||||
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
|
||||
Ok(contents) => contents,
|
||||
Err(err) => {
|
||||
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
||||
}
|
||||
};
|
||||
let contents =
|
||||
match std::fs::read_to_string(path).map_err(SourceExtractionError::Io) {
|
||||
Ok(contents) => contents,
|
||||
Err(err) => {
|
||||
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
||||
}
|
||||
};
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||
lint_pyproject_toml(source_file, settings)
|
||||
} else {
|
||||
@@ -226,8 +205,8 @@ pub(crate) fn lint_path(
|
||||
};
|
||||
|
||||
// Extract the sources from the file.
|
||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
let LintSource(source_kind) = match LintSource::try_from_path(path, source_type) {
|
||||
Ok(Some(sources)) => sources,
|
||||
Ok(None) => return Ok(Diagnostics::default()),
|
||||
Err(err) => {
|
||||
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
||||
@@ -241,7 +220,7 @@ pub(crate) fn lint_path(
|
||||
error: parse_error,
|
||||
},
|
||||
fixed,
|
||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
) = if matches!(autofix, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
if let Ok(FixerResult {
|
||||
result,
|
||||
transformed,
|
||||
@@ -249,7 +228,7 @@ pub(crate) fn lint_path(
|
||||
}) = lint_fix(path, package, noqa, settings, &source_kind, source_type)
|
||||
{
|
||||
if !fixed.is_empty() {
|
||||
match fix_mode {
|
||||
match autofix {
|
||||
flags::FixMode::Apply => match transformed.as_ref() {
|
||||
SourceKind::Python(transformed) => {
|
||||
write(path, transformed.as_bytes())?;
|
||||
@@ -275,9 +254,10 @@ pub(crate) fn lint_path(
|
||||
// mutated it.
|
||||
let src_notebook = source_kind.as_ipy_notebook().unwrap();
|
||||
let mut stdout = io::stdout().lock();
|
||||
// Cell indices are 1-based.
|
||||
for ((idx, src_cell), dest_cell) in (1u32..)
|
||||
.zip(src_notebook.cells().iter())
|
||||
for ((idx, src_cell), dest_cell) in src_notebook
|
||||
.cells()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(dest_notebook.cells().iter())
|
||||
{
|
||||
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
||||
@@ -322,7 +302,7 @@ pub(crate) fn lint_path(
|
||||
}
|
||||
(result, fixed)
|
||||
} else {
|
||||
// If we fail to fix, lint the original source code.
|
||||
// If we fail to autofix, lint the original source code.
|
||||
let result = lint_only(path, package, settings, noqa, &source_kind, source_type);
|
||||
let fixed = FxHashMap::default();
|
||||
(result, fixed)
|
||||
@@ -363,7 +343,13 @@ pub(crate) fn lint_path(
|
||||
}
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = source_kind {
|
||||
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook.into_index())])
|
||||
FxHashMap::from_iter([(
|
||||
path.to_str()
|
||||
.ok_or_else(|| anyhow!("Unable to parse filename: {:?}", path))?
|
||||
.to_string(),
|
||||
// Index needs to be computed always to store in cache.
|
||||
notebook.index().clone(),
|
||||
)])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
@@ -384,7 +370,7 @@ pub(crate) fn lint_stdin(
|
||||
contents: String,
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// TODO(charlie): Support `pyproject.toml`.
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
@@ -392,8 +378,8 @@ pub(crate) fn lint_stdin(
|
||||
};
|
||||
|
||||
// Extract the sources from the file.
|
||||
let source_kind = match SourceKind::from_source_code(contents, source_type) {
|
||||
Ok(Some(source_kind)) => source_kind,
|
||||
let LintSource(source_kind) = match LintSource::try_from_source_code(contents, source_type) {
|
||||
Ok(Some(sources)) => sources,
|
||||
Ok(None) => return Ok(Diagnostics::default()),
|
||||
Err(err) => {
|
||||
return Ok(Diagnostics::from_source_error(&err, path, &settings.linter));
|
||||
@@ -407,7 +393,7 @@ pub(crate) fn lint_stdin(
|
||||
error: parse_error,
|
||||
},
|
||||
fixed,
|
||||
) = if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
) = if matches!(autofix, flags::FixMode::Apply | flags::FixMode::Diff) {
|
||||
if let Ok(FixerResult {
|
||||
result,
|
||||
transformed,
|
||||
@@ -420,10 +406,10 @@ pub(crate) fn lint_stdin(
|
||||
&source_kind,
|
||||
source_type,
|
||||
) {
|
||||
match fix_mode {
|
||||
match autofix {
|
||||
flags::FixMode::Apply => {
|
||||
// Write the contents to stdout, regardless of whether any errors were fixed.
|
||||
transformed.write(&mut io::stdout().lock())?;
|
||||
io::stdout().write_all(transformed.source_code().as_bytes())?;
|
||||
}
|
||||
flags::FixMode::Diff => {
|
||||
// But only write a diff if it's non-empty.
|
||||
@@ -449,7 +435,7 @@ pub(crate) fn lint_stdin(
|
||||
|
||||
(result, fixed)
|
||||
} else {
|
||||
// If we fail to fix, lint the original source code.
|
||||
// If we fail to autofix, lint the original source code.
|
||||
let result = lint_only(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
@@ -461,8 +447,8 @@ pub(crate) fn lint_stdin(
|
||||
let fixed = FxHashMap::default();
|
||||
|
||||
// Write the contents to stdout anyway.
|
||||
if fix_mode.is_apply() {
|
||||
source_kind.write(&mut io::stdout().lock())?;
|
||||
if autofix.is_apply() {
|
||||
io::stdout().write_all(source_kind.source_code().as_bytes())?;
|
||||
}
|
||||
|
||||
(result, fixed)
|
||||
@@ -489,15 +475,6 @@ pub(crate) fn lint_stdin(
|
||||
);
|
||||
}
|
||||
|
||||
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = source_kind {
|
||||
FxHashMap::from_iter([(
|
||||
path.map_or_else(|| "-".into(), |path| path.to_string_lossy().to_string()),
|
||||
notebook.into_index(),
|
||||
)])
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
|
||||
Ok(Diagnostics {
|
||||
messages,
|
||||
fixed: FxHashMap::from_iter([(
|
||||
@@ -505,6 +482,83 @@ pub(crate) fn lint_stdin(
|
||||
fixed,
|
||||
)]),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
notebook_indexes: FxHashMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LintSource(pub(crate) SourceKind);
|
||||
|
||||
impl LintSource {
|
||||
/// Extract the lint [`LintSource`] from the given file path.
|
||||
pub(crate) fn try_from_path(
|
||||
path: &Path,
|
||||
source_type: PySourceType,
|
||||
) -> Result<Option<LintSource>, SourceExtractionError> {
|
||||
if source_type.is_ipynb() {
|
||||
let notebook = Notebook::from_path(path)?;
|
||||
Ok(notebook
|
||||
.is_python_notebook()
|
||||
.then_some(LintSource(SourceKind::IpyNotebook(notebook))))
|
||||
} else {
|
||||
// This is tested by ruff_cli integration test `unreadable_file`
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
Ok(Some(LintSource(SourceKind::Python(contents))))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the lint [`LintSource`] from the raw string contents, optionally accompanied by a
|
||||
/// file path indicating the path to the file from which the contents were read. If provided,
|
||||
/// the file path should be used for diagnostics, but not for reading the file from disk.
|
||||
pub(crate) fn try_from_source_code(
|
||||
source_code: String,
|
||||
source_type: PySourceType,
|
||||
) -> Result<Option<LintSource>, SourceExtractionError> {
|
||||
if source_type.is_ipynb() {
|
||||
let notebook = Notebook::from_source_code(&source_code)?;
|
||||
Ok(notebook
|
||||
.is_python_notebook()
|
||||
.then_some(LintSource(SourceKind::IpyNotebook(notebook))))
|
||||
} else {
|
||||
Ok(Some(LintSource(SourceKind::Python(source_code))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum SourceExtractionError {
|
||||
/// The extraction failed due to an [`io::Error`].
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
/// The extraction failed due to a [`NotebookError`].
|
||||
#[error(transparent)]
|
||||
Notebook(#[from] NotebookError),
|
||||
}
|
||||
|
||||
impl From<&SourceExtractionError> for Diagnostic {
|
||||
fn from(err: &SourceExtractionError) -> Self {
|
||||
match err {
|
||||
// IO errors.
|
||||
SourceExtractionError::Io(_)
|
||||
| SourceExtractionError::Notebook(NotebookError::Io(_) | NotebookError::Json(_)) => {
|
||||
Diagnostic::new(
|
||||
IOError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
)
|
||||
}
|
||||
// Syntax errors.
|
||||
SourceExtractionError::Notebook(
|
||||
NotebookError::InvalidJson(_)
|
||||
| NotebookError::InvalidSchema(_)
|
||||
| NotebookError::InvalidFormat(_),
|
||||
) => Diagnostic::new(
|
||||
SyntaxError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,13 +234,13 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
..
|
||||
} = pyproject_config.settings;
|
||||
|
||||
// Fix rules are as follows:
|
||||
// Autofix rules are as follows:
|
||||
// - By default, generate all fixes, but don't apply them to the filesystem.
|
||||
// - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or
|
||||
// print them to stdout, if we're reading from stdin).
|
||||
// - If `--diff` or `--fix-only` are set, don't print any violations (only
|
||||
// fixes).
|
||||
let fix_mode = if cli.diff {
|
||||
let autofix = if cli.diff {
|
||||
flags::FixMode::Diff
|
||||
} else if fix || fix_only {
|
||||
flags::FixMode::Apply
|
||||
@@ -275,7 +275,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
}
|
||||
|
||||
if cli.add_noqa {
|
||||
if !fix_mode.is_generate() {
|
||||
if !autofix.is_generate() {
|
||||
warn_user!("--fix is incompatible with --add-noqa.");
|
||||
}
|
||||
let modifications =
|
||||
@@ -290,7 +290,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let printer = Printer::new(output_format, log_level, fix_mode, printer_flags);
|
||||
let printer = Printer::new(output_format, log_level, autofix, printer_flags);
|
||||
|
||||
if cli.watch {
|
||||
if output_format != SerializationFormat::Text {
|
||||
@@ -317,7 +317,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
&overrides,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
autofix,
|
||||
)?;
|
||||
printer.write_continuously(&mut writer, &messages)?;
|
||||
|
||||
@@ -349,7 +349,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
&overrides,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
autofix,
|
||||
)?;
|
||||
printer.write_continuously(&mut writer, &messages)?;
|
||||
}
|
||||
@@ -366,7 +366,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
&pyproject_config,
|
||||
&overrides,
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
autofix,
|
||||
)?
|
||||
} else {
|
||||
commands::check::check(
|
||||
@@ -375,14 +375,14 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
&overrides,
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
autofix,
|
||||
)?
|
||||
};
|
||||
|
||||
// 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!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff)) {
|
||||
if !(is_stdin && matches!(autofix, flags::FixMode::Apply | flags::FixMode::Diff)) {
|
||||
if cli.statistics {
|
||||
printer.write_statistics(&diagnostics, &mut writer)?;
|
||||
} else {
|
||||
|
||||
@@ -72,7 +72,7 @@ impl From<Rule> for SerializeRuleAsCode {
|
||||
pub(crate) struct Printer {
|
||||
format: SerializationFormat,
|
||||
log_level: LogLevel,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix_level: flags::FixMode,
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
@@ -80,13 +80,13 @@ impl Printer {
|
||||
pub(crate) const fn new(
|
||||
format: SerializationFormat,
|
||||
log_level: LogLevel,
|
||||
fix_mode: flags::FixMode,
|
||||
autofix_level: flags::FixMode,
|
||||
flags: Flags,
|
||||
) -> Self {
|
||||
Self {
|
||||
format,
|
||||
log_level,
|
||||
fix_mode,
|
||||
autofix_level,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ impl Printer {
|
||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||
}
|
||||
|
||||
if show_fix_status(self.fix_mode) {
|
||||
if show_fix_status(self.autofix_level) {
|
||||
let num_fixable = diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
@@ -140,7 +140,7 @@ impl Printer {
|
||||
.sum::<usize>();
|
||||
if fixed > 0 {
|
||||
let s = if fixed == 1 { "" } else { "s" };
|
||||
if self.fix_mode.is_apply() {
|
||||
if self.autofix_level.is_apply() {
|
||||
writeln!(writer, "Fixed {fixed} error{s}.")?;
|
||||
} else {
|
||||
writeln!(writer, "Would fix {fixed} error{s}.")?;
|
||||
@@ -191,7 +191,7 @@ impl Printer {
|
||||
}
|
||||
SerializationFormat::Text => {
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
||||
.with_show_fix_status(show_fix_status(self.autofix_level))
|
||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
@@ -209,7 +209,7 @@ impl Printer {
|
||||
SerializationFormat::Grouped => {
|
||||
GroupedEmitter::default()
|
||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
||||
.with_show_fix_status(show_fix_status(self.autofix_level))
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
@@ -366,7 +366,7 @@ impl Printer {
|
||||
|
||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
||||
.with_show_fix_status(show_fix_status(self.autofix_level))
|
||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
@@ -390,13 +390,13 @@ fn num_digits(n: usize) -> usize {
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Printer`] should indicate that a rule is fixable.
|
||||
const fn show_fix_status(fix_mode: flags::FixMode) -> bool {
|
||||
const fn show_fix_status(autofix_level: flags::FixMode) -> bool {
|
||||
// If we're in application mode, avoid indicating that a rule is fixable.
|
||||
// If the specific violation were truly fixable, it would've been fixed in
|
||||
// this pass! (We're occasionally unable to determine whether a specific
|
||||
// violation is fixable without trying to fix it, so if fix is not
|
||||
// violation is fixable without trying to fix it, so if autofix is not
|
||||
// enabled, we may inadvertently indicate that a rule is fixable.)
|
||||
!fix_mode.is_apply()
|
||||
!autofix_level.is_apply()
|
||||
}
|
||||
|
||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
|
||||
|
||||
204
crates/ruff_cli/tests/black_compatibility_test.rs
Normal file
204
crates/ruff_cli/tests/black_compatibility_test.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::{fs, process, str};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use insta_cmd::get_cargo_bin;
|
||||
use log::info;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
|
||||
/// Handles `blackd` process and allows submitting code to it for formatting.
|
||||
struct Blackd {
|
||||
address: SocketAddr,
|
||||
server: process::Child,
|
||||
client: ureq::Agent,
|
||||
}
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
impl Blackd {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
// Get free TCP port to run on
|
||||
let address = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))?.local_addr()?;
|
||||
|
||||
let args = [
|
||||
"--bind-host",
|
||||
&address.ip().to_string(),
|
||||
"--bind-port",
|
||||
&address.port().to_string(),
|
||||
];
|
||||
let server = Command::new("blackd")
|
||||
.args(args)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.context("Starting blackd")?;
|
||||
|
||||
// Wait up to four seconds for `blackd` to be ready.
|
||||
for _ in 0..20 {
|
||||
match TcpStream::connect(address) {
|
||||
Err(e) if e.kind() == ErrorKind::ConnectionRefused => {
|
||||
info!("`blackd` not ready yet; retrying...");
|
||||
sleep(Duration::from_millis(200));
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(_) => {
|
||||
info!("`blackd` ready");
|
||||
return Ok(Self {
|
||||
address,
|
||||
server,
|
||||
client: ureq::agent(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("blackd {:?} failed to start", args)
|
||||
}
|
||||
|
||||
/// Format given code with blackd.
|
||||
pub(crate) fn check(&self, code: &[u8]) -> Result<Vec<u8>> {
|
||||
match self
|
||||
.client
|
||||
.post(&format!("http://{}/", self.address))
|
||||
.set("X-Line-Length", "88")
|
||||
.send_bytes(code)
|
||||
{
|
||||
// 204 indicates the input wasn't changed during formatting, so
|
||||
// we return the original.
|
||||
Ok(response) => {
|
||||
if response.status() == 204 {
|
||||
Ok(code.to_vec())
|
||||
} else {
|
||||
let mut buf = vec![];
|
||||
response
|
||||
.into_reader()
|
||||
.take((1024 * 1024) as u64)
|
||||
.read_to_end(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
Err(ureq::Error::Status(_, response)) => Err(anyhow::anyhow!(
|
||||
"Formatting with `black` failed: {}",
|
||||
response.into_string()?
|
||||
)),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Blackd {
|
||||
fn drop(&mut self) {
|
||||
self.server.kill().expect("Couldn't end `blackd` process");
|
||||
}
|
||||
}
|
||||
|
||||
fn run_test(path: &Path, blackd: &Blackd, ruff_args: &[&str]) -> Result<()> {
|
||||
let input = fs::read(path)?;
|
||||
|
||||
// Step 1: Run `ruff` on the input.
|
||||
let mut step_1 = Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(ruff_args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
if let Some(mut stdin) = step_1.stdin.take() {
|
||||
stdin.write_all(input.as_ref())?;
|
||||
}
|
||||
let step_1_output = step_1.wait_with_output()?;
|
||||
if !step_1_output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Running input through ruff failed:\n{}",
|
||||
str::from_utf8(&step_1_output.stderr)?
|
||||
));
|
||||
}
|
||||
|
||||
// Step 2: Run `blackd` on the input.
|
||||
let step_2_output = blackd.check(&step_1_output.stdout.clone())?;
|
||||
|
||||
// Step 3: Re-run `ruff` on the input.
|
||||
let mut step_3 = Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(ruff_args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
if let Some(mut stdin) = step_3.stdin.take() {
|
||||
stdin.write_all(step_2_output.as_ref())?;
|
||||
}
|
||||
let step_3_output = step_3.wait_with_output()?;
|
||||
if !step_3_output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Running input through ruff after black failed:\n{}",
|
||||
str::from_utf8(&step_3_output.stderr)?
|
||||
));
|
||||
}
|
||||
let step_3_output = step_3_output.stdout.clone();
|
||||
|
||||
assert_eq!(
|
||||
str::from_utf8(&step_2_output),
|
||||
str::from_utf8(&step_3_output),
|
||||
"Mismatch found for {}",
|
||||
path.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_ruff_black_compatibility() -> Result<()> {
|
||||
set_up_logging(&LogLevel::Default)?;
|
||||
|
||||
let blackd = Blackd::new()?;
|
||||
|
||||
let fixtures_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("resources")
|
||||
.join("test")
|
||||
.join("fixtures");
|
||||
|
||||
// Ignore some fixtures that currently trigger errors. `E999.py` especially, as
|
||||
// that is triggering a syntax error on purpose.
|
||||
let excludes = ["E999.py", "W605_1.py"];
|
||||
|
||||
let paths = WalkDir::new(fixtures_dir)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|entry| {
|
||||
entry
|
||||
.path()
|
||||
.extension()
|
||||
.is_some_and(|ext| ext == "py" || ext == "pyi")
|
||||
&& !excludes.contains(&entry.path().file_name().unwrap().to_str().unwrap())
|
||||
});
|
||||
|
||||
let ruff_args = [
|
||||
"-",
|
||||
"--silent",
|
||||
"--exit-zero",
|
||||
"--fix",
|
||||
"--line-length",
|
||||
"88",
|
||||
"--select",
|
||||
"ALL",
|
||||
// Exclude ruff codes, specifically RUF100, because it causes differences that are not a
|
||||
// problem. Ruff would add a `# noqa: W292` after the first run, black introduces a
|
||||
// newline, and ruff removes the `# noqa: W292` again.
|
||||
"--ignore",
|
||||
"RUF",
|
||||
];
|
||||
|
||||
for entry in paths {
|
||||
let path = entry.path();
|
||||
run_test(path, &blackd, &ruff_args).context(format!("Testing {}", path.display()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[test]
|
||||
fn default_options() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print('Should\'t change quotes')
|
||||
|
||||
|
||||
if condition:
|
||||
|
||||
print('Hy "Micha"') # Should not change quotes
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Should't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
print('Hy "Micha"') # Should not change quotes
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[format]
|
||||
indent-style = "tab"
|
||||
quote-style = "single"
|
||||
skip-magic-trailing-comma = true
|
||||
line-ending = "cr-lf"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
|
||||
print("Should change quotes")
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(arg1, arg2):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_option_inheritance() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
let base_toml = tempdir.path().join("base.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend = "base.toml"
|
||||
|
||||
[format]
|
||||
quote-style = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
base_toml,
|
||||
r#"
|
||||
[format]
|
||||
indent-style = "tab"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
|
||||
print("Should change quotes")
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that the legacy `format` option continues to work but emits a warning.
|
||||
#[test]
|
||||
fn legacy_format_option() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
format = "json"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--select", "F401", "--no-cache", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
[
|
||||
{
|
||||
"code": "F401",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "-",
|
||||
"fix": {
|
||||
"applicability": "Automatic",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 3
|
||||
},
|
||||
"location": {
|
||||
"column": 1,
|
||||
"row": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": "Remove unused import: `os`"
|
||||
},
|
||||
"location": {
|
||||
"column": 8,
|
||||
"row": 2
|
||||
},
|
||||
"message": "`os` imported but unused",
|
||||
"noqa_row": 2,
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
||||
}
|
||||
]
|
||||
----- stderr -----
|
||||
warning: The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -12,7 +12,8 @@ use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
#[cfg(unix)]
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
#[cfg(unix)]
|
||||
use clap::Parser;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
@@ -75,8 +76,8 @@ fn stdin_filename() {
|
||||
"###);
|
||||
}
|
||||
|
||||
/// Raise `TCH` errors in `.py` files ...
|
||||
#[test]
|
||||
/// Raise `TCH` errors in `.py` files ...
|
||||
fn stdin_source_type_py() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
@@ -136,7 +137,7 @@ fn stdin_json() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_fix_py() {
|
||||
fn stdin_autofix() {
|
||||
let args = ["--fix"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
@@ -154,174 +155,7 @@ fn stdin_fix_py() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_fix_jupyter() {
|
||||
let args = ["--fix", "--stdin-filename", "Jupyter.ipynb"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(args)
|
||||
.pass_stdin(r#"{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
||||
fn stdin_autofix_when_not_fixable_should_still_print_contents() {
|
||||
let args = ["--fix"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
@@ -340,7 +174,7 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_fix_when_no_issues_should_still_print_contents() {
|
||||
fn stdin_autofix_when_no_issues_should_still_print_contents() {
|
||||
let args = ["--fix"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
@@ -357,135 +191,6 @@ fn stdin_fix_when_no_issues_should_still_print_contents() {
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_format_jupyter() {
|
||||
let args = ["format", "--stdin-filename", "Jupyter.ipynb", "--isolated"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(args)
|
||||
.pass_stdin(r#"{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x=1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def func():\n",
|
||||
" pass\n",
|
||||
"print(1)\n",
|
||||
"import os"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def func():\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(1)\n",
|
||||
"import os"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_source() {
|
||||
let args = ["--show-source"];
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
//! Tests the interaction of the `lint` configuration section
|
||||
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
const STDIN_BASE_OPTIONS: &[&str] = &["--no-cache", "--output-format", "text"];
|
||||
|
||||
#[test]
|
||||
fn top_level_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 potentially fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lint_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 potentially fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that configurations from the top-level and `lint` section are merged together.
|
||||
#[test]
|
||||
fn mixed_levels() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 potentially fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that options in the `lint` section have higher precedence than top-level options (because they are more specific).
|
||||
#[test]
|
||||
fn precedence() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[flake8-quotes]
|
||||
inline-quotes = "double"
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 potentially fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -13,7 +13,7 @@ exit_code: 0
|
||||
|
||||
Derived from the **Pyflakes** linter.
|
||||
|
||||
Fix is sometimes available.
|
||||
Autofix is sometimes available.
|
||||
|
||||
## What it does
|
||||
Checks for unused imports.
|
||||
|
||||
@@ -6,7 +6,7 @@ info:
|
||||
- "-"
|
||||
- "--isolated"
|
||||
- "--no-cache"
|
||||
- "--output-format"
|
||||
- "--format"
|
||||
- json
|
||||
- "--stdin-filename"
|
||||
- F401.py
|
||||
|
||||
@@ -28,7 +28,7 @@ ruff_workspace = { path = "../ruff_workspace", features = ["schemars"]}
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
ignore = { workspace = true }
|
||||
indicatif = "0.17.7"
|
||||
indicatif = "0.17.5"
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
|
||||
@@ -549,6 +549,7 @@ fn format_dir_entry(
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
||||
// TODO(micha) use formatter settings instead
|
||||
if settings.formatter.line_width != LineWidth::default() {
|
||||
options = options.with_line_width(settings.formatter.line_width);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@ use anyhow::Result;
|
||||
use regex::{Captures, Regex};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_diagnostics::FixAvailability;
|
||||
use ruff_diagnostics::AutofixKind;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
@@ -37,19 +36,16 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let fix_availability = rule.fixable();
|
||||
if matches!(
|
||||
fix_availability,
|
||||
FixAvailability::Always | FixAvailability::Sometimes
|
||||
) {
|
||||
output.push_str(&fix_availability.to_string());
|
||||
let autofix = rule.autofixable();
|
||||
if matches!(autofix, AutofixKind::Always | AutofixKind::Sometimes) {
|
||||
output.push_str(&autofix.to_string());
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_preview() || rule.is_nursery() {
|
||||
if rule.is_preview() {
|
||||
output.push_str(
|
||||
r#"This rule is unstable and in [preview](../preview.md). The `--preview` flag is required for use."#,
|
||||
r#"This rule is in preview and is not stable. The `--preview` flag is required for use."#,
|
||||
);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
@@ -100,7 +96,10 @@ fn process_documentation(documentation: &str, out: &mut String) {
|
||||
if let Some(rest) = line.strip_prefix("- `") {
|
||||
let option = rest.trim_end().trim_end_matches('`');
|
||||
|
||||
assert!(Options::metadata().has(option), "unknown option {option}");
|
||||
assert!(
|
||||
Options::metadata().get(option).is_some(),
|
||||
"unknown option {option}"
|
||||
);
|
||||
|
||||
let anchor = option.replace('.', "-");
|
||||
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
||||
|
||||
@@ -1,104 +1,22 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
//!
|
||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
use std::fmt::Write;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ruff_workspace::options_base::{OptionEntry, OptionField};
|
||||
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
generate_set(&mut output, &Set::Toplevel(Options::metadata()));
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_set(output: &mut String, set: &Set) {
|
||||
if set.level() < 2 {
|
||||
writeln!(output, "### {title}\n", title = set.title()).unwrap();
|
||||
} else {
|
||||
writeln!(output, "#### {title}\n", title = set.title()).unwrap();
|
||||
}
|
||||
|
||||
if let Some(documentation) = set.metadata().documentation() {
|
||||
output.push_str(documentation);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let mut visitor = CollectOptionsVisitor::default();
|
||||
set.metadata().record(&mut visitor);
|
||||
|
||||
let (mut fields, mut sets) = (visitor.fields, visitor.groups);
|
||||
|
||||
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
|
||||
// Generate the fields.
|
||||
for (name, field) in &fields {
|
||||
emit_field(output, name, field, set);
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
// Generate all the sub-sets.
|
||||
for (set_name, sub_set) in &sets {
|
||||
generate_set(output, &Set::Named(set_name, *sub_set, set.level() + 1));
|
||||
}
|
||||
}
|
||||
|
||||
enum Set<'a> {
|
||||
Toplevel(OptionSet),
|
||||
Named(&'a str, OptionSet, u32),
|
||||
}
|
||||
|
||||
impl<'a> Set<'a> {
|
||||
fn name(&self) -> Option<&'a str> {
|
||||
match self {
|
||||
Set::Toplevel(_) => None,
|
||||
Set::Named(name, _, _) => Some(name),
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> &'a str {
|
||||
match self {
|
||||
Set::Toplevel(_) => "Top-level",
|
||||
Set::Named(name, _, _) => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&self) -> &OptionSet {
|
||||
match self {
|
||||
Set::Toplevel(set) => set,
|
||||
Set::Named(_, set, _) => set,
|
||||
}
|
||||
}
|
||||
|
||||
fn level(&self) -> u32 {
|
||||
match self {
|
||||
Set::Toplevel(_) => 0,
|
||||
Set::Named(_, _, level) => *level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set: &Set) {
|
||||
let header_level = if parent_set.level() < 2 {
|
||||
"####"
|
||||
} else {
|
||||
"#####"
|
||||
};
|
||||
|
||||
// if there's a set name, we need to add it to the anchor
|
||||
if let Some(set_name) = parent_set.name() {
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name: Option<&str>) {
|
||||
// if there's a group name, we need to add it to the anchor
|
||||
if let Some(group_name) = group_name {
|
||||
// the anchor used to just be the name, but now it's the group name
|
||||
// for backwards compatibility, we need to keep the old anchor
|
||||
output.push_str(&format!("<span id=\"{name}\"></span>\n"));
|
||||
|
||||
output.push_str(&format!(
|
||||
"{header_level} [`{name}`](#{set_name}-{name}) {{: #{set_name}-{name} }}\n"
|
||||
"#### [`{name}`](#{group_name}-{name}) {{: #{group_name}-{name} }}\n"
|
||||
));
|
||||
} else {
|
||||
output.push_str(&format!("{header_level} [`{name}`](#{name})\n"));
|
||||
output.push_str(&format!("#### [`{name}`](#{name})\n"));
|
||||
}
|
||||
output.push('\n');
|
||||
output.push_str(field.doc);
|
||||
@@ -109,8 +27,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push('\n');
|
||||
output.push_str(&format!(
|
||||
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
|
||||
if let Some(set_name) = parent_set.name() {
|
||||
format!(".{set_name}")
|
||||
if group_name.is_some() {
|
||||
format!(".{}", group_name.unwrap())
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
@@ -119,18 +37,38 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CollectOptionsVisitor {
|
||||
groups: Vec<(String, OptionSet)>,
|
||||
fields: Vec<(String, OptionField)>,
|
||||
}
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut output: String = "### Top-level\n\n".into();
|
||||
|
||||
impl Visit for CollectOptionsVisitor {
|
||||
fn record_set(&mut self, name: &str, group: OptionSet) {
|
||||
self.groups.push((name.to_owned(), group));
|
||||
let sorted_options: Vec<_> = Options::metadata()
|
||||
.into_iter()
|
||||
.sorted_by_key(|(name, _)| *name)
|
||||
.collect();
|
||||
|
||||
// Generate all the top-level fields.
|
||||
for (name, entry) in &sorted_options {
|
||||
let OptionEntry::Field(field) = entry else {
|
||||
continue;
|
||||
};
|
||||
emit_field(&mut output, name, field, None);
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||
self.fields.push((name.to_owned(), field));
|
||||
// Generate all the sub-groups.
|
||||
for (group_name, entry) in &sorted_options {
|
||||
let OptionEntry::Group(fields) = entry else {
|
||||
continue;
|
||||
};
|
||||
output.push_str(&format!("### {group_name}\n"));
|
||||
output.push('\n');
|
||||
for (name, entry) in fields.iter().sorted_by_key(|(name, _)| name) {
|
||||
let OptionEntry::Field(field) = entry else {
|
||||
continue;
|
||||
};
|
||||
emit_field(&mut output, name, field, Some(group_name));
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
use itertools::Itertools;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff_diagnostics::FixAvailability;
|
||||
use ruff_diagnostics::AutofixKind;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
const FIX_SYMBOL: &str = "🛠️";
|
||||
const PREVIEW_SYMBOL: &str = "🧪";
|
||||
@@ -20,11 +19,11 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
table_out.push_str("| ---- | ---- | ------- | ------: |");
|
||||
table_out.push('\n');
|
||||
for rule in rules {
|
||||
let fix_token = match rule.fixable() {
|
||||
FixAvailability::Always | FixAvailability::Sometimes => {
|
||||
let fix_token = match rule.autofixable() {
|
||||
AutofixKind::Always | AutofixKind::Sometimes => {
|
||||
format!("<span style='opacity: 1'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
FixAvailability::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
|
||||
AutofixKind::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
|
||||
};
|
||||
let preview_token = if rule.is_preview() || rule.is_nursery() {
|
||||
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
|
||||
@@ -105,7 +104,10 @@ pub(crate) fn generate() -> String {
|
||||
table_out.push('\n');
|
||||
}
|
||||
|
||||
if Options::metadata().has(linter.name()) {
|
||||
if Options::metadata()
|
||||
.iter()
|
||||
.any(|(name, _)| name == &linter.name())
|
||||
{
|
||||
table_out.push_str(&format!(
|
||||
"For related settings, see [{}](settings.md#{}).",
|
||||
linter.name(),
|
||||
|
||||
@@ -88,7 +88,7 @@ impl Fix {
|
||||
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from multiple [`Edit`] elements.
|
||||
pub fn automatic_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||
edits.sort_by_key(Ranged::start);
|
||||
Self {
|
||||
edits,
|
||||
applicability: Applicability::Automatic,
|
||||
@@ -108,7 +108,7 @@ impl Fix {
|
||||
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from multiple [`Edit`] elements.
|
||||
pub fn suggested_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||
edits.sort_by_key(Ranged::start);
|
||||
Self {
|
||||
edits,
|
||||
applicability: Applicability::Suggested,
|
||||
@@ -128,7 +128,7 @@ impl Fix {
|
||||
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from multiple [`Edit`] elements.
|
||||
pub fn manual_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||
edits.sort_by_key(Ranged::start);
|
||||
Self {
|
||||
edits,
|
||||
applicability: Applicability::Manual,
|
||||
|
||||
@@ -2,7 +2,7 @@ pub use diagnostic::{Diagnostic, DiagnosticKind};
|
||||
pub use edit::Edit;
|
||||
pub use fix::{Applicability, Fix, IsolationLevel};
|
||||
pub use source_map::{SourceMap, SourceMarker};
|
||||
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation};
|
||||
pub use violation::{AlwaysAutofixableViolation, AutofixKind, Violation};
|
||||
|
||||
mod diagnostic;
|
||||
mod edit;
|
||||
|
||||
@@ -46,7 +46,10 @@ impl SourceMap {
|
||||
/// The `output_length` is the length of the transformed string before the
|
||||
/// edit is applied.
|
||||
pub fn push_start_marker(&mut self, edit: &Edit, output_length: TextSize) {
|
||||
self.push_marker(edit.start(), output_length);
|
||||
self.0.push(SourceMarker {
|
||||
source: edit.start(),
|
||||
dest: output_length,
|
||||
});
|
||||
}
|
||||
|
||||
/// Push the end marker for an [`Edit`].
|
||||
@@ -55,18 +58,16 @@ impl SourceMap {
|
||||
/// edit has been applied.
|
||||
pub fn push_end_marker(&mut self, edit: &Edit, output_length: TextSize) {
|
||||
if edit.is_insertion() {
|
||||
self.push_marker(edit.start(), output_length);
|
||||
self.0.push(SourceMarker {
|
||||
source: edit.start(),
|
||||
dest: output_length,
|
||||
});
|
||||
} else {
|
||||
// Deletion or replacement
|
||||
self.push_marker(edit.end(), output_length);
|
||||
self.0.push(SourceMarker {
|
||||
source: edit.end(),
|
||||
dest: output_length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new marker to the sourcemap.
|
||||
pub fn push_marker(&mut self, offset: TextSize, output_length: TextSize) {
|
||||
self.0.push(SourceMarker {
|
||||
source: offset,
|
||||
dest: output_length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum FixAvailability {
|
||||
pub enum AutofixKind {
|
||||
Sometimes,
|
||||
Always,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Display for FixAvailability {
|
||||
impl Display for AutofixKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FixAvailability::Sometimes => write!(f, "Fix is sometimes available."),
|
||||
FixAvailability::Always => write!(f, "Fix is always available."),
|
||||
FixAvailability::None => write!(f, "Fix is not available."),
|
||||
AutofixKind::Sometimes => write!(f, "Autofix is sometimes available."),
|
||||
AutofixKind::Always => write!(f, "Autofix is always available."),
|
||||
AutofixKind::None => write!(f, "Autofix is not available."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Violation: Debug + PartialEq + Eq {
|
||||
/// `None` in the case an fix is never available or otherwise Some
|
||||
/// [`FixAvailability`] describing the available fix.
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
|
||||
/// `None` in the case an autofix is never available or otherwise Some
|
||||
/// [`AutofixKind`] describing the available autofix.
|
||||
const AUTOFIX: AutofixKind = AutofixKind::None;
|
||||
|
||||
/// The message used to describe the violation.
|
||||
fn message(&self) -> String;
|
||||
@@ -30,13 +30,13 @@ pub trait Violation: Debug + PartialEq + Eq {
|
||||
None
|
||||
}
|
||||
|
||||
// TODO(micha): Move `fix_title` to `Fix`, add new `advice` method that is shown as an advice.
|
||||
// TODO(micha): Move `autofix_title` to `Fix`, add new `advice` method that is shown as an advice.
|
||||
// Change the `Diagnostic` renderer to show the advice, and render the fix message after the `Suggested fix: <here>`
|
||||
|
||||
/// Returns the title for the fix. The message is also shown as an advice as part of the diagnostics.
|
||||
/// Returns the title for the autofix. The message is also shown as an advice as part of the diagnostics.
|
||||
///
|
||||
/// Required for rules that have fixes.
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
/// Required for rules that have autofixes.
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ pub trait Violation: Debug + PartialEq + Eq {
|
||||
}
|
||||
|
||||
/// This trait exists just to make implementing the [`Violation`] trait more
|
||||
/// convenient for violations that can always be fixed.
|
||||
pub trait AlwaysFixableViolation: Debug + PartialEq + Eq {
|
||||
/// convenient for violations that can always be autofixed.
|
||||
pub trait AlwaysAutofixableViolation: Debug + PartialEq + Eq {
|
||||
/// The message used to describe the violation.
|
||||
fn message(&self) -> String;
|
||||
|
||||
@@ -55,31 +55,31 @@ pub trait AlwaysFixableViolation: Debug + PartialEq + Eq {
|
||||
None
|
||||
}
|
||||
|
||||
/// The title displayed for the available fix.
|
||||
fn fix_title(&self) -> String;
|
||||
/// The title displayed for the available autofix.
|
||||
fn autofix_title(&self) -> String;
|
||||
|
||||
/// Returns the format strings used by
|
||||
/// [`message`](AlwaysFixableViolation::message).
|
||||
/// [`message`](AlwaysAutofixableViolation::message).
|
||||
fn message_formats() -> &'static [&'static str];
|
||||
}
|
||||
|
||||
/// A blanket implementation.
|
||||
impl<V: AlwaysFixableViolation> Violation for V {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always;
|
||||
impl<VA: AlwaysAutofixableViolation> Violation for VA {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Always;
|
||||
|
||||
fn message(&self) -> String {
|
||||
<Self as AlwaysFixableViolation>::message(self)
|
||||
<Self as AlwaysAutofixableViolation>::message(self)
|
||||
}
|
||||
|
||||
fn explanation() -> Option<&'static str> {
|
||||
<Self as AlwaysFixableViolation>::explanation()
|
||||
<Self as AlwaysAutofixableViolation>::explanation()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(<Self as AlwaysFixableViolation>::fix_title(self))
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(<Self as AlwaysAutofixableViolation>::autofix_title(self))
|
||||
}
|
||||
|
||||
fn message_formats() -> &'static [&'static str] {
|
||||
<Self as AlwaysFixableViolation>::message_formats()
|
||||
<Self as AlwaysAutofixableViolation>::message_formats()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::prelude::TagKind;
|
||||
use crate::GroupId;
|
||||
use ruff_text_size::TextRange;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
@@ -12,7 +11,7 @@ pub enum FormatError {
|
||||
SyntaxError { message: &'static str },
|
||||
/// In case range formatting failed because the provided range was larger
|
||||
/// than the formatted syntax tree
|
||||
RangeError { input: TextRange, tree: TextRange },
|
||||
RangeError { row: usize, col: usize },
|
||||
|
||||
/// In case printing the document failed because it has an invalid structure.
|
||||
InvalidDocument(InvalidDocumentError),
|
||||
@@ -32,9 +31,9 @@ impl std::fmt::Display for FormatError {
|
||||
FormatError::SyntaxError {message} => {
|
||||
std::write!(fmt, "syntax error: {message}")
|
||||
},
|
||||
FormatError::RangeError { input, tree } => std::write!(
|
||||
FormatError::RangeError { row, col } => std::write!(
|
||||
fmt,
|
||||
"formatting range {input:?} is larger than syntax tree {tree:?}"
|
||||
"formatting range {row}:{col} is not a valid index"
|
||||
),
|
||||
FormatError::InvalidDocument(error) => std::write!(fmt, "Invalid document: {error}\n\n This is an internal Rome error. Please report if necessary."),
|
||||
FormatError::PoorLayout => {
|
||||
|
||||
@@ -55,11 +55,7 @@ use ruff_macros::CacheKey;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(rename_all = "kebab-case")
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Default)]
|
||||
pub enum IndentStyle {
|
||||
|
||||
@@ -334,7 +334,7 @@ macro_rules! best_fitting {
|
||||
$crate::BestFitting::from_arguments_unchecked($crate::format_args!($least_expanded, $($tail),+))
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
/// Options that affect how the [`crate::Printer`] prints the format tokens
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
||||
@@ -120,7 +121,7 @@ impl SourceMapGeneration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum LineEnding {
|
||||
/// Line Feed only (\n), common on Linux and macOS as well as inside git repos
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.292"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -29,7 +29,6 @@ ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
aho-corasick = { version = "1.1.1" }
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
@@ -46,20 +45,22 @@ libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = [
|
||||
"once_cell_cache",
|
||||
"use_unix_paths_on_wasm",
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pep440_rs = { version = "0.3.1", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.7.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
semver = { version = "1.0.19" }
|
||||
semver = { version = "1.0.16" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
|
||||
@@ -158,9 +158,3 @@ class Foo:
|
||||
@decorator()
|
||||
def __init__(self: "Foo", foo: int):
|
||||
...
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7711
|
||||
class Class:
|
||||
def __init__(self):
|
||||
print(f"{self.attr=}")
|
||||
|
||||
@@ -20,4 +20,3 @@ os.chmod(keyfile, stat.S_IRWXO | stat.S_IRWXG | stat.S_IRWXU) # Error
|
||||
os.chmod("~/hidden_exec", stat.S_IXGRP) # Error
|
||||
os.chmod("~/hidden_exec", stat.S_IXOTH) # OK
|
||||
os.chmod("/etc/passwd", stat.S_IWOTH) # Error
|
||||
os.chmod("/etc/passwd", 0o100000000) # Error
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from Crypto.PublicKey import DSA as pycrypto_dsa
|
||||
from Crypto.PublicKey import RSA as pycrypto_rsa
|
||||
from Cryptodome.PublicKey import DSA as pycryptodomex_dsa
|
||||
from Cryptodome.PublicKey import RSA as pycryptodomex_rsa
|
||||
|
||||
# OK
|
||||
dsa.generate_private_key(key_size=2048, backend=backends.default_backend())
|
||||
ec.generate_private_key(curve=ec.SECP384R1, backend=backends.default_backend())
|
||||
rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=2048, backend=backends.default_backend()
|
||||
)
|
||||
pycrypto_dsa.generate(bits=2048)
|
||||
pycrypto_rsa.generate(bits=2048)
|
||||
pycryptodomex_dsa.generate(bits=2048)
|
||||
pycryptodomex_rsa.generate(bits=2048)
|
||||
dsa.generate_private_key(2048, backends.default_backend())
|
||||
ec.generate_private_key(ec.SECP256K1, backends.default_backend())
|
||||
rsa.generate_private_key(3, 2048, backends.default_backend())
|
||||
pycrypto_dsa.generate(2048)
|
||||
pycrypto_rsa.generate(2048)
|
||||
pycryptodomex_dsa.generate(2048)
|
||||
pycryptodomex_rsa.generate(2048)
|
||||
|
||||
# Errors
|
||||
dsa.generate_private_key(key_size=2047, backend=backends.default_backend())
|
||||
ec.generate_private_key(curve=ec.SECT163R2, backend=backends.default_backend())
|
||||
rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=2047, backend=backends.default_backend()
|
||||
)
|
||||
pycrypto_dsa.generate(bits=2047)
|
||||
pycrypto_rsa.generate(bits=2047)
|
||||
pycryptodomex_dsa.generate(bits=2047)
|
||||
pycryptodomex_rsa.generate(bits=2047)
|
||||
dsa.generate_private_key(2047, backends.default_backend())
|
||||
ec.generate_private_key(ec.SECT163R2, backends.default_backend())
|
||||
rsa.generate_private_key(3, 2047, backends.default_backend())
|
||||
pycrypto_dsa.generate(2047)
|
||||
pycrypto_rsa.generate(2047)
|
||||
pycryptodomex_dsa.generate(2047)
|
||||
pycryptodomex_rsa.generate(2047)
|
||||
|
||||
# Don't crash when the size is variable.
|
||||
rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=some_key_size, backend=backends.default_backend()
|
||||
)
|
||||
|
||||
# Can't reliably know which curve was passed, in some cases like below.
|
||||
ec.generate_private_key(
|
||||
curve=curves[self.curve]["create"](self.size), backend=backends.default_backend()
|
||||
)
|
||||
@@ -1,9 +1,7 @@
|
||||
import paramiko
|
||||
from paramiko import client
|
||||
from paramiko.client import AutoAddPolicy, WarningPolicy
|
||||
|
||||
ssh_client = client.SSHClient()
|
||||
ssh_client_from_paramiko = paramiko.SSHClient()
|
||||
|
||||
# OK
|
||||
ssh_client.set_missing_host_key_policy(policy=foo)
|
||||
@@ -14,12 +12,10 @@ ssh_client.set_missing_host_key_policy(foo)
|
||||
# Errors
|
||||
ssh_client.set_missing_host_key_policy(client.AutoAddPolicy)
|
||||
ssh_client.set_missing_host_key_policy(client.WarningPolicy)
|
||||
ssh_client.set_missing_host_key_policy(client.AutoAddPolicy())
|
||||
ssh_client.set_missing_host_key_policy(AutoAddPolicy)
|
||||
ssh_client.set_missing_host_key_policy(policy=client.AutoAddPolicy)
|
||||
ssh_client.set_missing_host_key_policy(policy=client.WarningPolicy)
|
||||
ssh_client.set_missing_host_key_policy(policy=WarningPolicy)
|
||||
ssh_client_from_paramiko.set_missing_host_key_policy(paramiko.AutoAddPolicy)
|
||||
|
||||
# Unrelated
|
||||
set_missing_host_key_policy(client.AutoAddPolicy)
|
||||
|
||||
@@ -92,35 +92,3 @@ try:
|
||||
pass
|
||||
except Exception:
|
||||
logging.error("...", exc_info=True)
|
||||
|
||||
|
||||
from logging import error, exception
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
error("...")
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
error("...", exc_info=False)
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
error("...", exc_info=None)
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
exception("...")
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
error("...", exc_info=True)
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Move mutable arguments below imports and docstrings
|
||||
# https://github.com/astral-sh/ruff/issues/7616
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
import os
|
||||
|
||||
|
||||
def import_module_with_values_wrong(value: dict[str, str] = {}):
|
||||
import os
|
||||
|
||||
return 2
|
||||
|
||||
|
||||
def import_modules_wrong(value: dict[str, str] = {}):
|
||||
import os
|
||||
import sys
|
||||
import itertools
|
||||
|
||||
|
||||
def from_import_module_wrong(value: dict[str, str] = {}):
|
||||
from os import path
|
||||
|
||||
|
||||
def from_imports_module_wrong(value: dict[str, str] = {}):
|
||||
from os import path
|
||||
from sys import version_info
|
||||
|
||||
|
||||
def import_and_from_imports_module_wrong(value: dict[str, str] = {}):
|
||||
import os
|
||||
from sys import version_info
|
||||
|
||||
|
||||
def import_docstring_module_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""
|
||||
import os
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""
|
||||
import os; import sys
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""
|
||||
import os; import sys; x = 1
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
"""Docstring"""
|
||||
import os; import sys
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
import os; import sys
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
import os; import sys; x = 1
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}):
|
||||
import os; import sys
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}): import os
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}): import os; import sys
|
||||
|
||||
|
||||
def import_module_wrong(value: dict[str, str] = {}): \
|
||||
import os
|
||||
@@ -1,5 +0,0 @@
|
||||
# Import followed by whitespace with no newline
|
||||
# Same as B006_2.py, but import instead of docstring
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
import os
|
||||
@@ -1,5 +0,0 @@
|
||||
# Import with no newline
|
||||
# Same as B006_3.py, but import instead of docstring
|
||||
|
||||
def foobar(foor, bar={}):
|
||||
import os
|
||||
@@ -56,11 +56,3 @@ setattr(foo.bar, r"baz", None)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1722458885
|
||||
assert getattr(func, '_rpc')is True
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1732387247
|
||||
getattr(*foo, "bar")
|
||||
setattr(*foo, "bar", None)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739800901
|
||||
getattr(self.
|
||||
registration.registry, '__name__')
|
||||
|
||||
@@ -76,15 +76,8 @@ except (ValueError, binascii.Error):
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/6412
|
||||
# https://github.com/astral-sh/ruff/issues/6412
|
||||
try:
|
||||
pass
|
||||
except (ValueError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7455#issuecomment-1739801758
|
||||
try:
|
||||
pas
|
||||
except(re.error, re.error):
|
||||
p
|
||||
|
||||
@@ -627,7 +627,7 @@ result = function(
|
||||
**{'ham': spam}
|
||||
)
|
||||
|
||||
# Make sure the COM812 and UP034 rules don't fix simultaneously and cause a syntax error.
|
||||
# Make sure the COM812 and UP034 rules don't autofix simultaneously and cause a syntax error.
|
||||
the_first_one = next(
|
||||
(i for i in range(10) if i // 2 == 0) # COM812 fix should include the final bracket
|
||||
)
|
||||
|
||||
@@ -59,23 +59,3 @@ _ = "abc" + "def" + foo
|
||||
_ = foo + bar + "abc"
|
||||
_ = "abc" + foo + bar
|
||||
_ = foo + "abc" + bar
|
||||
|
||||
# Multiple strings nested inside a f-string
|
||||
_ = f"a {'b' 'c' 'd'} e"
|
||||
_ = f"""abc {"def" "ghi"} jkl"""
|
||||
_ = f"""abc {
|
||||
"def"
|
||||
"ghi"
|
||||
} jkl"""
|
||||
|
||||
# Nested f-strings
|
||||
_ = "a" f"b {f"c" f"d"} e" "f"
|
||||
_ = f"b {f"c" f"d {f"e" f"f"} g"} h"
|
||||
_ = f"b {f"abc" \
|
||||
f"def"} g"
|
||||
|
||||
# Explicitly concatenated nested f-strings
|
||||
_ = f"a {f"first"
|
||||
+ f"second"} d"
|
||||
_ = f"a {f"first {f"middle"}"
|
||||
+ f"second"} d"
|
||||
|
||||
@@ -16,9 +16,3 @@ from logging import exception
|
||||
|
||||
exception("foo", exc_info=False) # LOG007
|
||||
exception("foo", exc_info=True) # OK
|
||||
|
||||
|
||||
exception = lambda *args, **kwargs: None
|
||||
|
||||
exception("foo", exc_info=False) # OK
|
||||
exception("foo", exc_info=True) # OK
|
||||
|
||||
@@ -16,12 +16,3 @@ from flask import current_app as app
|
||||
flask.current_app.logger.info("Hello {}".format("World!"))
|
||||
current_app.logger.info("Hello {}".format("World!"))
|
||||
app.logger.log(logging.INFO, "Hello {}".format("World!"))
|
||||
|
||||
from logging import info, log
|
||||
|
||||
info("Hello {}".format("World!"))
|
||||
log(logging.INFO, "Hello {}".format("World!"))
|
||||
info("Hello {}".format("World!"))
|
||||
log(logging.INFO, msg="Hello {}".format("World!"))
|
||||
log(level=logging.INFO, msg="Hello {}".format("World!"))
|
||||
log(msg="Hello {}".format("World!"), level=logging.INFO)
|
||||
|
||||
@@ -2,8 +2,3 @@ import logging
|
||||
|
||||
logging.info("Hello %s" % "World!")
|
||||
logging.log(logging.INFO, "Hello %s" % "World!")
|
||||
|
||||
from logging import info, log
|
||||
|
||||
info("Hello %s" % "World!")
|
||||
log(logging.INFO, "Hello %s" % "World!")
|
||||
|
||||
@@ -2,8 +2,3 @@ import logging
|
||||
|
||||
logging.info("Hello" + " " + "World!")
|
||||
logging.log(logging.INFO, "Hello" + " " + "World!")
|
||||
|
||||
from logging import info, log
|
||||
|
||||
info("Hello" + " " + "World!")
|
||||
log(logging.INFO, "Hello" + " " + "World!")
|
||||
|
||||
@@ -6,7 +6,3 @@ logging.log(logging.INFO, f"Hello {name}")
|
||||
|
||||
_LOGGER = logging.getLogger()
|
||||
_LOGGER.info(f"{__name__}")
|
||||
|
||||
from logging import info
|
||||
info(f"{name}")
|
||||
info(f"{__name__}")
|
||||
|
||||
@@ -8,8 +8,3 @@ log.warn("Hello world!") # This shouldn't be considered as a logger candidate
|
||||
logger.warn("Hello world!")
|
||||
|
||||
logging . warn("Hello World!")
|
||||
|
||||
from logging import warn, warning, exception
|
||||
warn("foo")
|
||||
warning("foo")
|
||||
exception("foo")
|
||||
|
||||
@@ -6,12 +6,3 @@ logging.info(
|
||||
"name": "foobar",
|
||||
},
|
||||
)
|
||||
|
||||
from logging import info
|
||||
|
||||
info(
|
||||
"Hello world!",
|
||||
extra={
|
||||
"name": "foobar",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -6,12 +6,3 @@ logging.info(
|
||||
name="foobar",
|
||||
),
|
||||
)
|
||||
|
||||
from logging import info
|
||||
|
||||
info(
|
||||
"Hello world!",
|
||||
extra=dict(
|
||||
name="foobar",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -19,24 +19,3 @@ except:
|
||||
logging.error("Hello World", exc_info=False)
|
||||
|
||||
logging.error("Hello World", exc_info=sys.exc_info())
|
||||
|
||||
# G201
|
||||
from logging import error
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
error("Hello World", exc_info=True)
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
error("Hello World", exc_info=sys.exc_info())
|
||||
|
||||
# OK
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
error("Hello World", exc_info=False)
|
||||
|
||||
error("Hello World", exc_info=sys.exc_info())
|
||||
|
||||
|
||||
@@ -19,23 +19,3 @@ except:
|
||||
logging.exception("Hello World", exc_info=False)
|
||||
|
||||
logging.exception("Hello World", exc_info=True)
|
||||
|
||||
# G202
|
||||
from logging import exception
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
exception("Hello World", exc_info=True)
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
exception("Hello World", exc_info=sys.exc_info())
|
||||
|
||||
# OK
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
exception("Hello World", exc_info=False)
|
||||
|
||||
exception("Hello World", exc_info=True)
|
||||
|
||||
@@ -123,28 +123,3 @@ except NetworkError:
|
||||
|
||||
def foo() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def foo():
|
||||
print("foo")
|
||||
pass
|
||||
|
||||
|
||||
def foo():
|
||||
"""A docstring."""
|
||||
print("foo")
|
||||
pass
|
||||
|
||||
|
||||
for i in range(10):
|
||||
pass
|
||||
pass
|
||||
|
||||
for i in range(10):
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
for i in range(10):
|
||||
pass # comment
|
||||
pass
|
||||
|
||||
@@ -33,10 +33,10 @@ message
|
||||
assert not (a or not (b or c))
|
||||
assert not (a or not (b and c))
|
||||
|
||||
# detected, but no fix for messages
|
||||
# detected, but no autofix for messages
|
||||
assert something and something_else, "error message"
|
||||
assert not (something or something_else and something_third), "with message"
|
||||
# detected, but no fix for mixed conditions (e.g. `a or b and c`)
|
||||
# detected, but no autofix for mixed conditions (e.g. `a or b and c`)
|
||||
assert not (something or something_else and something_third)
|
||||
|
||||
|
||||
|
||||
@@ -15,29 +15,3 @@ def ok_complex_logic():
|
||||
def error():
|
||||
resource = acquire_resource()
|
||||
yield resource
|
||||
|
||||
|
||||
import typing
|
||||
from typing import Generator
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def ok_complex_logic() -> typing.Generator[Resource, None, None]:
|
||||
if some_condition:
|
||||
resource = acquire_resource()
|
||||
yield resource
|
||||
resource.release()
|
||||
return
|
||||
yield None
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def error() -> typing.Generator[typing.Any, None, None]:
|
||||
resource = acquire_resource()
|
||||
yield resource
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def error() -> Generator[Resource, None, None]:
|
||||
resource = acquire_resource()
|
||||
yield resource
|
||||
|
||||
@@ -9,33 +9,3 @@ this_should_raise = (
|
||||
'This is a'
|
||||
'\'string\''
|
||||
)
|
||||
|
||||
# Same as above, but with f-strings
|
||||
f'This is a \'string\'' # Q003
|
||||
f'This is \\ a \\\'string\'' # Q003
|
||||
f'"This" is a \'string\''
|
||||
f"This is a 'string'"
|
||||
f"\"This\" is a 'string'"
|
||||
fr'This is a \'string\''
|
||||
fR'This is a \'string\''
|
||||
foo = (
|
||||
f'This is a'
|
||||
f'\'string\'' # Q003
|
||||
)
|
||||
|
||||
# Nested f-strings (Python 3.12+)
|
||||
#
|
||||
# The first one is interesting because the fix for it is valid pre 3.12:
|
||||
#
|
||||
# f"'foo' {'nested'}"
|
||||
#
|
||||
# but as the actual string itself is invalid pre 3.12, we don't catch it.
|
||||
f'\'foo\' {'nested'}' # Q003
|
||||
f'\'foo\' {f'nested'}' # Q003
|
||||
f'\'foo\' {f'\'nested\''} \'\'' # Q003
|
||||
|
||||
f'normal {f'nested'} normal'
|
||||
f'\'normal\' {f'nested'} normal' # Q003
|
||||
f'\'normal\' {f'nested'} "double quotes"'
|
||||
f'\'normal\' {f'\'nested\' {'other'} normal'} "double quotes"' # Q003
|
||||
f'\'normal\' {f'\'nested\' {'other'} "double quotes"'} normal' # Q00l
|
||||
|
||||
@@ -8,32 +8,3 @@ this_should_raise = (
|
||||
"This is a"
|
||||
"\"string\""
|
||||
)
|
||||
|
||||
# Same as above, but with f-strings
|
||||
f"This is a \"string\""
|
||||
f"'This' is a \"string\""
|
||||
f'This is a "string"'
|
||||
f'\'This\' is a "string"'
|
||||
fr"This is a \"string\""
|
||||
fR"This is a \"string\""
|
||||
foo = (
|
||||
f"This is a"
|
||||
f"\"string\""
|
||||
)
|
||||
|
||||
# Nested f-strings (Python 3.12+)
|
||||
#
|
||||
# The first one is interesting because the fix for it is valid pre 3.12:
|
||||
#
|
||||
# f'"foo" {"nested"}'
|
||||
#
|
||||
# but as the actual string itself is invalid pre 3.12, we don't catch it.
|
||||
f"\"foo\" {"foo"}" # Q003
|
||||
f"\"foo\" {f"foo"}" # Q003
|
||||
f"\"foo\" {f"\"foo\""} \"\"" # Q003
|
||||
|
||||
f"normal {f"nested"} normal"
|
||||
f"\"normal\" {f"nested"} normal" # Q003
|
||||
f"\"normal\" {f"nested"} 'single quotes'"
|
||||
f"\"normal\" {f"\"nested\" {"other"} normal"} 'single quotes'" # Q003
|
||||
f"\"normal\" {f"\"nested\" {"other"} 'single quotes'"} normal" # Q003
|
||||
|
||||
@@ -335,7 +335,7 @@ def foo():
|
||||
return x
|
||||
|
||||
|
||||
# Fix cases
|
||||
# Autofix cases
|
||||
def foo():
|
||||
a = 1
|
||||
b=a
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
try:
|
||||
from __builtin__ import bytes, str, open, super, range, zip, round, int, pow, object, input
|
||||
except ImportError: pass
|
||||
@@ -121,16 +121,3 @@ async with b as b2:
|
||||
with c as c2:
|
||||
async with d as d2:
|
||||
f(b2, c2, d2)
|
||||
|
||||
# SIM117
|
||||
with A() as a:
|
||||
with B() as b:
|
||||
type ListOrSet[T] = list[T] | set[T]
|
||||
|
||||
class ClassA[T: str]:
|
||||
def method1(self) -> T:
|
||||
...
|
||||
|
||||
f" something { my_dict["key"] } something else "
|
||||
|
||||
f"foo {f"bar {x}"} baz"
|
||||
|
||||
@@ -33,14 +33,3 @@ with open(p) as fp:
|
||||
fp.read()
|
||||
open(p).close()
|
||||
os.getcwdb(p)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7620
|
||||
def opener(path, flags):
|
||||
return os.open(path, flags, dir_fd=os.open('somedir', os.O_RDONLY))
|
||||
|
||||
|
||||
open(p, closefd=False)
|
||||
open(p, opener=opener)
|
||||
open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(p, 'r', - 1, None, None, None, True, None)
|
||||
open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
||||
@@ -84,8 +84,3 @@ spam[ ~ham]
|
||||
x = [ #
|
||||
'some value',
|
||||
]
|
||||
|
||||
# F-strings
|
||||
f"{ {'a': 1} }"
|
||||
f"{[ { {'a': 1} } ]}"
|
||||
f"normal { {f"{ { [1, 2] } }" } } normal"
|
||||
|
||||
@@ -29,16 +29,5 @@ mdtypes_template = {
|
||||
'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
|
||||
}
|
||||
|
||||
# E231
|
||||
f"{(a,b)}"
|
||||
|
||||
# Okay because it's hard to differentiate between the usages of a colon in a f-string
|
||||
f"{a:=1}"
|
||||
f"{ {'a':1} }"
|
||||
f"{a:.3f}"
|
||||
f"{(a:=1)}"
|
||||
f"{(lambda x:x)}"
|
||||
f"normal{f"{a:.3f}"}normal"
|
||||
|
||||
#: Okay
|
||||
a = (1,
|
||||
|
||||
@@ -48,9 +48,3 @@ def add(a: int=0, b: int =0, c: int= 0) -> int:
|
||||
#: Okay
|
||||
def add(a: int = _default(name='f')):
|
||||
return a
|
||||
|
||||
# F-strings
|
||||
f"{a=}"
|
||||
f"{a:=1}"
|
||||
f"{foo(a=1)}"
|
||||
f"normal {f"{a=}"} normal"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# TODO: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# TODO(charlie): comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# FIXME: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
# FIXME(charlie): comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep`
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# OK (88 characters)
|
||||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaa" # type: ignore
|
||||
|
||||
# OK (88 characters)
|
||||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaa"# type: ignore
|
||||
|
||||
# OK (88 characters)
|
||||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaa" # type: ignore
|
||||
|
||||
# Error (89 characters)
|
||||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaaa" # type: ignore
|
||||
@@ -152,11 +152,3 @@ x = [
|
||||
multiline string with tab in it, different lines
|
||||
'''
|
||||
" single line string with tab in it"
|
||||
|
||||
f"test{
|
||||
tab_indented_should_be_flagged
|
||||
} <- this tab is fine"
|
||||
|
||||
f"""test{
|
||||
tab_indented_should_be_flagged
|
||||
} <- this tab is fine"""
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Same as `W605_0.py` but using f-strings instead.
|
||||
|
||||
#: W605:1:10
|
||||
regex = f'\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = f'''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
f'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
f"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: W605:1:38
|
||||
value = f'new line\nand invalid escape \_ here'
|
||||
|
||||
|
||||
#: Okay
|
||||
regex = fr'\.png$'
|
||||
regex = f'\\.png$'
|
||||
regex = fr'''
|
||||
\.png$
|
||||
'''
|
||||
regex = fr'''
|
||||
\\.png$
|
||||
'''
|
||||
s = f'\\'
|
||||
regex = f'\w' # noqa
|
||||
regex = f'''
|
||||
\w
|
||||
''' # noqa
|
||||
|
||||
regex = f'\\\_'
|
||||
value = f'\{{1}}'
|
||||
value = f'\{1}'
|
||||
value = f'{1:\}'
|
||||
value = f"{f"\{1}"}"
|
||||
value = rf"{f"\{1}"}"
|
||||
|
||||
# Okay
|
||||
value = rf'\{{1}}'
|
||||
value = rf'\{1}'
|
||||
value = rf'{1:\}'
|
||||
value = f"{rf"\{1}"}"
|
||||
@@ -1,4 +0,0 @@
|
||||
"""
|
||||
TODO:
|
||||
-
|
||||
"""
|
||||
@@ -40,5 +40,7 @@ f"{{{{x}}}}"
|
||||
""f""
|
||||
''f""
|
||||
(""f""r"")
|
||||
f"{v:{f"0.2f"}}"
|
||||
f"\{{x}}"
|
||||
|
||||
# To be fixed
|
||||
# Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
# f"\{{x}}"
|
||||
|
||||
@@ -25,7 +25,3 @@ not ''}
|
||||
|
||||
{2 is
|
||||
not ''}
|
||||
|
||||
# Regression test for
|
||||
if values[1is not None ] is not '-':
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Test case for fixing F841 violations."""
|
||||
"""Test case for autofixing F841 violations."""
|
||||
|
||||
|
||||
def f():
|
||||
|
||||
@@ -5,6 +5,3 @@ from warnings import warn
|
||||
warnings.warn("this is ok")
|
||||
warn("by itself is also ok")
|
||||
logging.warning("this is fine")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning("this is fine")
|
||||
|
||||
@@ -3,6 +3,3 @@ from logging import warn
|
||||
|
||||
logging.warn("this is not ok")
|
||||
warn("not ok")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warn("this is not ok")
|
||||
|
||||
@@ -20,8 +20,6 @@ __all__ = foo.bar # [invalid-all-format]
|
||||
|
||||
__all__ = foo["bar"] # [invalid-all-format]
|
||||
|
||||
__all__ = (foo := bar) # [invalid-all-format]
|
||||
|
||||
__all__ = ["Hello"]
|
||||
|
||||
__all__ = ("Hello",)
|
||||
@@ -43,7 +41,3 @@ __all__ = __all__ + ["Hello"]
|
||||
__all__ = __all__ + multiprocessing.__all__
|
||||
|
||||
__all__ = list[str](["Hello", "world"])
|
||||
|
||||
__all__ = list[str](foo())
|
||||
|
||||
__all__ = (foo := ["Hello", "world"])
|
||||
|
||||
Binary file not shown.
@@ -26,29 +26,3 @@ logging.info(msg="Hello %s %s")
|
||||
import warning
|
||||
|
||||
warning.warning("Hello %s %s", "World!")
|
||||
|
||||
|
||||
from logging import error, info, warning
|
||||
|
||||
warning("Hello %s %s", "World!") # [logging-too-few-args]
|
||||
|
||||
# do not handle calls with kwargs (like pylint)
|
||||
warning("Hello %s", "World!", "again", something="else")
|
||||
|
||||
warning("Hello %s", "World!")
|
||||
|
||||
# do not handle calls without any args
|
||||
info("100% dynamic")
|
||||
|
||||
# do not handle calls with *args
|
||||
error("Example log %s, %s", "foo", "bar", "baz", *args)
|
||||
|
||||
# do not handle calls with **kwargs
|
||||
error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
|
||||
|
||||
# do not handle keyword arguments
|
||||
error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
|
||||
|
||||
info(msg="Hello %s")
|
||||
|
||||
info(msg="Hello %s %s")
|
||||
|
||||
@@ -22,25 +22,3 @@ logging.info(msg="Hello", something="else")
|
||||
import warning
|
||||
|
||||
warning.warning("Hello %s", "World!", "again")
|
||||
|
||||
|
||||
from logging import info, error, warning
|
||||
|
||||
warning("Hello %s", "World!", "again") # [logging-too-many-args]
|
||||
|
||||
warning("Hello %s", "World!", "again", something="else")
|
||||
|
||||
warning("Hello %s", "World!")
|
||||
|
||||
# do not handle calls with *args
|
||||
error("Example log %s, %s", "foo", "bar", "baz", *args)
|
||||
|
||||
# do not handle calls with **kwargs
|
||||
error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
|
||||
|
||||
# do not handle keyword arguments
|
||||
error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
|
||||
|
||||
info(msg="Hello")
|
||||
|
||||
info(msg="Hello", something="else")
|
||||
|
||||
@@ -59,15 +59,15 @@ with open(name="foo", mode="Ub") as f:
|
||||
with open(mode="Ub", name="foo") as f:
|
||||
pass
|
||||
|
||||
open(file="foo", mode='U', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U')
|
||||
open(file="foo", buffering=-1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None)
|
||||
open(mode='U', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", mode='U', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U')
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None)
|
||||
open(mode='U', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
|
||||
open(file="foo", mode='Ub', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
|
||||
open(file="foo", buffering=-1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
|
||||
open(mode='Ub', file="foo", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", mode='Ub', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
|
||||
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
|
||||
open(mode='Ub', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
|
||||
open = 1
|
||||
open("foo", "U")
|
||||
|
||||
@@ -104,12 +104,3 @@ def get_owner_id_from_mac_address():
|
||||
mac_address = get_primary_mac_address()
|
||||
except(IOError, OSError) as ex:
|
||||
msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7580
|
||||
import os
|
||||
|
||||
try:
|
||||
pass
|
||||
except os.error:
|
||||
pass
|
||||
|
||||
@@ -184,15 +184,3 @@ if sys.version_info < (3,12):
|
||||
|
||||
if sys.version_info <= (3,12):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info <= (3,12):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info == 10000000:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info < (3,10000000):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info <= (3,10000000):
|
||||
print("py3")
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# Errors.
|
||||
|
||||
print("")
|
||||
print("", sep=",")
|
||||
print("", end="bar")
|
||||
print("", sep=",", end="bar")
|
||||
print(sep="")
|
||||
print("", sep="")
|
||||
print("", "", sep="")
|
||||
print("", "", sep="", end="")
|
||||
print("", "", sep="", end="bar")
|
||||
print("", sep="", end="bar")
|
||||
print(sep="", end="bar")
|
||||
print("", "foo", sep="")
|
||||
print("foo", "", sep="")
|
||||
print("foo", "", "bar", sep="")
|
||||
print("", *args)
|
||||
print("", *args, sep="")
|
||||
print("", **kwargs)
|
||||
print(sep="\t")
|
||||
|
||||
# OK.
|
||||
|
||||
print()
|
||||
print("foo")
|
||||
print("", "")
|
||||
print("", "foo")
|
||||
print("foo", "")
|
||||
print("", "", sep=",")
|
||||
print("", "foo", sep=",")
|
||||
print("foo", "", sep=",")
|
||||
print("foo", "", "bar", "", sep=",")
|
||||
print("", "", **kwargs)
|
||||
@@ -1,31 +0,0 @@
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
|
||||
# Errors
|
||||
_ = Path().resolve()
|
||||
_ = pathlib.Path().resolve()
|
||||
|
||||
_ = Path("").resolve()
|
||||
_ = pathlib.Path("").resolve()
|
||||
|
||||
_ = Path(".").resolve()
|
||||
_ = pathlib.Path(".").resolve()
|
||||
|
||||
_ = Path("", **kwargs).resolve()
|
||||
_ = pathlib.Path("", **kwargs).resolve()
|
||||
|
||||
_ = Path(".", **kwargs).resolve()
|
||||
_ = pathlib.Path(".", **kwargs).resolve()
|
||||
|
||||
# OK
|
||||
_ = Path.cwd()
|
||||
_ = pathlib.Path.cwd()
|
||||
|
||||
_ = Path("foo").resolve()
|
||||
_ = pathlib.Path("foo").resolve()
|
||||
|
||||
_ = Path(".", "foo").resolve()
|
||||
_ = pathlib.Path(".", "foo").resolve()
|
||||
|
||||
_ = Path(*args).resolve()
|
||||
_ = pathlib.Path(*args).resolve()
|
||||
@@ -19,8 +19,3 @@ def func():
|
||||
import functools, operator
|
||||
|
||||
sum([x, y], [])
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7718
|
||||
def func():
|
||||
sum((factor.dims for factor in bases), [])
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user