Compare commits
119 Commits
zanie/app-
...
options-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ca7407868 | ||
|
|
f1b00cafd4 | ||
|
|
dda4ceda71 | ||
|
|
195c000f5a | ||
|
|
a62c735f9e | ||
|
|
94b4bb0f57 | ||
|
|
fe485d791c | ||
|
|
d685107638 | ||
|
|
d85950ce5a | ||
|
|
88c0106421 | ||
|
|
f60aa85471 | ||
|
|
d942a777d7 | ||
|
|
8a529925b3 | ||
|
|
dc6b4ad2b4 | ||
|
|
21ea290d6a | ||
|
|
5da0f9111e | ||
|
|
cb6d74c27b | ||
|
|
73049df3ed | ||
|
|
bf0e5788ef | ||
|
|
4113d65836 | ||
|
|
4c2c9bf7e0 | ||
|
|
172ac2c9a2 | ||
|
|
cac9754455 | ||
|
|
134def0119 | ||
|
|
1fabaca5de | ||
|
|
523f542dbd | ||
|
|
ee7575eb5a | ||
|
|
84f7391cc5 | ||
|
|
7da4e28a98 | ||
|
|
5718df638f | ||
|
|
4bb4cd3b37 | ||
|
|
620426de7a | ||
|
|
84ec66a22c | ||
|
|
e58ffa9a7a | ||
|
|
aa6846c78c | ||
|
|
3d03e75a9d | ||
|
|
b6e75e58c9 | ||
|
|
8061894af6 | ||
|
|
e261eb7461 | ||
|
|
bd06cbe0c5 | ||
|
|
ddffadb4b0 | ||
|
|
8255e4ed6c | ||
|
|
60ca6885b1 | ||
|
|
889117ea87 | ||
|
|
c03a693ebc | ||
|
|
6f9c317aa5 | ||
|
|
66179af4f1 | ||
|
|
1e184e69f3 | ||
|
|
f08a5f67eb | ||
|
|
cd564c4200 | ||
|
|
c1fdb9c46d | ||
|
|
48b256bd94 | ||
|
|
3944c42d4c | ||
|
|
cb06b7956c | ||
|
|
4454fbf7e5 | ||
|
|
b243840e4b | ||
|
|
23bbe7336a | ||
|
|
a71c4dfabb | ||
|
|
81275d12e9 | ||
|
|
40cad44f4a | ||
|
|
c38617fa27 | ||
|
|
1835d7bb45 | ||
|
|
f670f9b22c | ||
|
|
7a072cc2ea | ||
|
|
8c4b5d3c90 | ||
|
|
ec9d5cddd6 | ||
|
|
0f759af3cf | ||
|
|
644011fb14 | ||
|
|
a1ee6d28ce | ||
|
|
826868da5b | ||
|
|
5986ff748a | ||
|
|
739a8aa10e | ||
|
|
090c1a4a19 | ||
|
|
2b95d3832b | ||
|
|
d412e8ef74 | ||
|
|
46e45bdf19 | ||
|
|
a3e8e77172 | ||
|
|
ec7395ba69 | ||
|
|
d8c0360fc7 | ||
|
|
097b654ba7 | ||
|
|
d54cabd276 | ||
|
|
7faa43108f | ||
|
|
74b00c9b91 | ||
|
|
97e944003b | ||
|
|
016e16254a | ||
|
|
61a41334a3 | ||
|
|
74971617a1 | ||
|
|
5c68c89566 | ||
|
|
8923eb19e0 | ||
|
|
dad70fff99 | ||
|
|
b72c94b3d1 | ||
|
|
b4b296dca3 | ||
|
|
43883b7a15 | ||
|
|
38f512d588 | ||
|
|
2d6557a51b | ||
|
|
2ba5677700 | ||
|
|
62f1ee08e7 | ||
|
|
bdd925c0f2 | ||
|
|
dd36a2516e | ||
|
|
b6c9cf1c5b | ||
|
|
805fd1bc93 | ||
|
|
0fc76ba276 | ||
|
|
4b537d1297 | ||
|
|
3c25d261fe | ||
|
|
4f95df1b6d | ||
|
|
22e18741bd | ||
|
|
e8d2cbc3f6 | ||
|
|
1dd5deb53d | ||
|
|
b64f403dc2 | ||
|
|
7dc9887ab9 | ||
|
|
709abd534a | ||
|
|
27def479bd | ||
|
|
1eac457c1b | ||
|
|
609a78b13e | ||
|
|
17fba99ed4 | ||
|
|
adb6580270 | ||
|
|
76fcf63052 | ||
|
|
90de108bfa | ||
|
|
ad265fa6bc |
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.1.1
|
||||
uses: cloudflare/wrangler-action@v3.3.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.1.1
|
||||
uses: cloudflare/wrangler-action@v3.3.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -23,6 +23,7 @@ repos:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
- mdformat-mkdocs
|
||||
- mdformat-admon
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.33.0
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### The deprecated `format` setting has been removed
|
||||
|
||||
Ruff previously used the `format` setting, `--format` CLI option, and `RUFF_FORMAT` environment variable to
|
||||
configure the output format of the CLI. This usage was deprecated in `v0.0.291` — the `format` setting is now used
|
||||
to control Ruff's code formatting. As of this release:
|
||||
|
||||
- The `format` setting cannot be used to configure the output format, use `output-format` instead
|
||||
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
|
||||
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
|
||||
|
||||
### Unsafe fixes are not applied by default ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||
|
||||
Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe
|
||||
fixes, but the meaning could be changed when applying unsafe fixes. Previously, unsafe fixes were always displayed
|
||||
and applied when fixing was enabled. Now, unsafe fixes are hidden by default and not applied. The `--unsafe-fixes`
|
||||
flag or `unsafe-fixes` configuration option can be used to enable unsafe fixes.
|
||||
|
||||
See the [docs](https://docs.astral.sh/ruff/configuration/#fix-safety) for details.
|
||||
|
||||
### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
|
||||
|
||||
Previously, Ruff enabled all implemented rules in Pycodestyle (`E`) by default. Ruff now only includes the
|
||||
Pycodestyle prefixes `E4`, `E7`, and `E9` to exclude rules that conflict with automatic formatters. Consequently,
|
||||
the stable rule set no longer includes `line-too-long` (`E501`) and `mixed-spaces-and-tabs` (`E101`). Other
|
||||
excluded Pycodestyle rules include whitespace enforcement in `E1` and `E2`; these rules are currently in preview, and are already omitted by default.
|
||||
|
||||
This change only affects those using Ruff under its default rule set. Users that include `E` in their `select` will experience no change in behavior.
|
||||
|
||||
## 0.0.288
|
||||
|
||||
### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
|
||||
|
||||
115
CHANGELOG.md
Normal file
115
CHANGELOG.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Changelog
|
||||
|
||||
This is the first release which uses the `CHANGELOG` file. See [GitHub Releases](https://github.com/astral-sh/ruff/releases) for prior changelog entries.
|
||||
|
||||
Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Unsafe fixes are no longer displayed or applied without opt-in ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||
- Drop formatting specific rules from the default set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
|
||||
- The deprecated `format` setting has been removed ([#7984](https://github.com/astral-sh/ruff/pull/7984))
|
||||
- The `format` setting cannot be used to configure the output format, use `output-format` instead
|
||||
- The `RUFF_FORMAT` environment variable is ignored, use `RUFF_OUTPUT_FORMAT` instead
|
||||
- The `--format` option has been removed from `ruff check`, use `--output-format` instead
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Extend `reimplemented-starmap` (`FURB140`) to catch calls with a single and starred argument ([#7768](https://github.com/astral-sh/ruff/pull/7768))
|
||||
- Improve cases covered by `RUF015` ([#7848](https://github.com/astral-sh/ruff/pull/7848))
|
||||
- Update `SIM15` to allow `open` followed by `close` ([#7916](https://github.com/astral-sh/ruff/pull/7916))
|
||||
- Respect `msgspec.Struct` default-copy semantics in `RUF012` ([#7786](https://github.com/astral-sh/ruff/pull/7786))
|
||||
- Add `sqlalchemy` methods to \`flake8-boolean-trap\`\` exclusion list ([#7874](https://github.com/astral-sh/ruff/pull/7874))
|
||||
- Add fix for `PLR1714` ([#7910](https://github.com/astral-sh/ruff/pull/7910))
|
||||
- Add fix for `PIE804` ([#7884](https://github.com/astral-sh/ruff/pull/7884))
|
||||
- Add fix for `PLC0208` ([#7887](https://github.com/astral-sh/ruff/pull/7887))
|
||||
- Add fix for `PYI055` ([#7886](https://github.com/astral-sh/ruff/pull/7886))
|
||||
- Update `non-pep695-type-alias` to require `--unsafe-fixes` outside of stub files ([#7836](https://github.com/astral-sh/ruff/pull/7836))
|
||||
- Improve fix message for `UP018` ([#7913](https://github.com/astral-sh/ruff/pull/7913))
|
||||
- Update `PLW3201` to support `Enum` [sunder names](https://docs.python.org/3/library/enum.html#supported-sunder-names) ([#7987](https://github.com/astral-sh/ruff/pull/7987))
|
||||
|
||||
### Preview features
|
||||
|
||||
- Only show warnings for empty preview selectors when enabling rules ([#7842](https://github.com/astral-sh/ruff/pull/7842))
|
||||
- Add `unnecessary-key-check` to simplify `key in dct and dct[key]` to `dct.get(key)` ([#7895](https://github.com/astral-sh/ruff/pull/7895))
|
||||
- Add `assignment-in-assert` to prevent walrus expressions in assert statements ([#7856](https://github.com/astral-sh/ruff/pull/7856))
|
||||
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
|
||||
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
|
||||
|
||||
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
|
||||
|
||||
### Configuration
|
||||
|
||||
- Add `unsafe-fixes` setting ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||
- Add `extend-safe-fixes` and `extend-unsafe-fixes` for promoting and demoting fixes ([#7841](https://github.com/astral-sh/ruff/pull/7841))
|
||||
|
||||
### CLI
|
||||
|
||||
- Added `--unsafe-fixes` option for opt-in to display and apply unsafe fixes ([#7769](https://github.com/astral-sh/ruff/pull/7769))
|
||||
- Fix use of deprecated `--format` option in warning ([#7837](https://github.com/astral-sh/ruff/pull/7837))
|
||||
- Show changed files when running under `--check` ([#7788](https://github.com/astral-sh/ruff/pull/7788))
|
||||
- Write summary messages to stderr when fixing via stdin instead of omitting them ([#7838](https://github.com/astral-sh/ruff/pull/7838))
|
||||
- Update fix summary message in `check --diff` to include unsafe fix hints ([#7790](https://github.com/astral-sh/ruff/pull/7790))
|
||||
- Add notebook `cell` field to JSON output format ([#7664](https://github.com/astral-sh/ruff/pull/7664))
|
||||
- Rename applicability levels to `Safe`, `Unsafe`, and `Display` ([#7843](https://github.com/astral-sh/ruff/pull/7843))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix bug where f-strings were allowed in match pattern literal ([#7857](https://github.com/astral-sh/ruff/pull/7857))
|
||||
- Fix `SIM110` with a yield in the condition ([#7801](https://github.com/astral-sh/ruff/pull/7801))
|
||||
- Preserve trailing comments in `C414` fixes ([#7775](https://github.com/astral-sh/ruff/pull/7775))
|
||||
- Check sequence type before triggering `unnecessary-enumerate` `len` suggestion ([#7781](https://github.com/astral-sh/ruff/pull/7781))
|
||||
- Use correct start location for class/function clause header ([#7802](https://github.com/astral-sh/ruff/pull/7802))
|
||||
- Fix incorrect fixes for `SIM101` ([#7798](https://github.com/astral-sh/ruff/pull/7798))
|
||||
- Format comment before parameter default correctly ([#7870](https://github.com/astral-sh/ruff/pull/7870))
|
||||
- Fix `E251` false positive inside f-strings ([#7894](https://github.com/astral-sh/ruff/pull/7894))
|
||||
- Allow bindings to be created and referenced within annotations ([#7885](https://github.com/astral-sh/ruff/pull/7885))
|
||||
- Show per-cell diffs when analyzing notebooks over `stdin` ([#7789](https://github.com/astral-sh/ruff/pull/7789))
|
||||
- Avoid curly brace escape in f-string format spec ([#7780](https://github.com/astral-sh/ruff/pull/7780))
|
||||
- Fix lexing single-quoted f-string with multi-line format spec ([#7787](https://github.com/astral-sh/ruff/pull/7787))
|
||||
- Consider nursery rules to be in-preview for `ruff rule` ([#7812](https://github.com/astral-sh/ruff/pull/7812))
|
||||
- Report precise location for invalid conversion flag ([#7809](https://github.com/astral-sh/ruff/pull/7809))
|
||||
- Visit pattern match guard as a boolean test ([#7911](https://github.com/astral-sh/ruff/pull/7911))
|
||||
- Respect `--unfixable` in `ISC` rules ([#7917](https://github.com/astral-sh/ruff/pull/7917))
|
||||
- Fix edge case with `PIE804` ([#7922](https://github.com/astral-sh/ruff/pull/7922))
|
||||
- Show custom message in `PTH118` for `Path.joinpath` with starred arguments ([#7852](https://github.com/astral-sh/ruff/pull/7852))
|
||||
- Fix false negative in `outdated-version-block` when using greater than comparisons ([#7920](https://github.com/astral-sh/ruff/pull/7920))
|
||||
- Avoid converting f-strings within Django `gettext` calls ([#7898](https://github.com/astral-sh/ruff/pull/7898))
|
||||
- Fix false positive in `PLR6301` ([#7933](https://github.com/astral-sh/ruff/pull/7933))
|
||||
- Treat type aliases as typing-only expressions e.g. resolves false positive in `TCH004` ([#7968](https://github.com/astral-sh/ruff/pull/7968))
|
||||
- Resolve `cache-dir` relative to project root ([#7962](https://github.com/astral-sh/ruff/pull/7962))
|
||||
- Respect subscripted base classes in type-checking rules e.g. resolves false positive in `TCH003` ([#7954](https://github.com/astral-sh/ruff/pull/7954))
|
||||
- Fix JSON schema limit for `line-length` ([#7883](https://github.com/astral-sh/ruff/pull/7883))
|
||||
- Fix commented-out `coalesce` keyword ([#7876](https://github.com/astral-sh/ruff/pull/7876))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document `reimplemented-starmap` performance effects ([#7846](https://github.com/astral-sh/ruff/pull/7846))
|
||||
- Default to following the system dark/light mode ([#7888](https://github.com/astral-sh/ruff/pull/7888))
|
||||
- Add documentation for fixes ([#7901](https://github.com/astral-sh/ruff/pull/7901))
|
||||
- Fix typo in docs of `PLR6301` ([#7831](https://github.com/astral-sh/ruff/pull/7831))
|
||||
- Update `UP038` docs to note that it results in slower code ([#7872](https://github.com/astral-sh/ruff/pull/7872))
|
||||
- crlf -> cr-lf ([#7766](https://github.com/astral-sh/ruff/pull/7766))
|
||||
- Add an example of an unsafe fix ([#7924](https://github.com/astral-sh/ruff/pull/7924))
|
||||
- Fix documented examples for `unnecessary-subscript-reversal` ([#7774](https://github.com/astral-sh/ruff/pull/7774))
|
||||
- Correct error in tuple example in ruff formatter docs ([#7822](https://github.com/astral-sh/ruff/pull/7822))
|
||||
- Add versioning policy to documentation ([#7923](https://github.com/astral-sh/ruff/pull/7923))
|
||||
- Fix invalid code in `FURB177` example ([#7832](https://github.com/astral-sh/ruff/pull/7832))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Less scary `ruff format` message ([#7867](https://github.com/astral-sh/ruff/pull/7867))
|
||||
- Remove spaces from import statements ([#7859](https://github.com/astral-sh/ruff/pull/7859))
|
||||
- Formatter quoting for f-strings with triple quotes ([#7826](https://github.com/astral-sh/ruff/pull/7826))
|
||||
- Update `ruff_python_formatter` generate.py comment ([#7850](https://github.com/astral-sh/ruff/pull/7850))
|
||||
- Document one-call chaining deviation ([#7767](https://github.com/astral-sh/ruff/pull/7767))
|
||||
- Allow f-string modifications in line-shrinking cases ([#7818](https://github.com/astral-sh/ruff/pull/7818))
|
||||
- Add trailing comment deviation to README ([#7827](https://github.com/astral-sh/ruff/pull/7827))
|
||||
- Add trailing zero between dot and exponential ([#7956](https://github.com/astral-sh/ruff/pull/7956))
|
||||
- Force parentheses for power operations in unary expressions ([#7955](https://github.com/astral-sh/ruff/pull/7955))
|
||||
|
||||
### Playground
|
||||
|
||||
- Fix playground `Quick Fix` action ([#7824](https://github.com/astral-sh/ruff/pull/7824))
|
||||
@@ -170,7 +170,8 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
|
||||
expressions, like function calls).
|
||||
|
||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`).
|
||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`). New rules
|
||||
should be added in `RuleGroup::Preview`.
|
||||
|
||||
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
||||
|
||||
|
||||
161
Cargo.lock
generated
161
Cargo.lock
generated
@@ -28,9 +28,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -74,9 +74,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.5.0"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
||||
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -112,9 +112,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "2.1.0"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.48.0",
|
||||
@@ -221,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.3.8",
|
||||
"regex-automata 0.3.9",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -313,9 +313,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.5"
|
||||
version = "4.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3"
|
||||
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -323,9 +323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.5"
|
||||
version = "4.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab"
|
||||
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -383,7 +383,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -608,7 +608,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1084,9 +1084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.33.0"
|
||||
version = "1.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686"
|
||||
checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1129,7 +1129,7 @@ dependencies = [
|
||||
"pmutil 0.6.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1268,8 +1268,9 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd5c2ff400caac657bf794181d885491bb97cc37c376f8cb4fa3a0cc2176a053"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"libcst_derive",
|
||||
@@ -1282,8 +1283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Instagram/LibCST.git?rev=03179b55ebe7e916f1722e18e8f0b87c01616d1f#03179b55ebe7e916f1722e18e8f0b87c01616d1f"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7f252282b20bfec6fae65d351ab1df7e4552a6270dd7bb779ca9d6135aabe9"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
@@ -1705,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1790,9 +1792,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.67"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1923,14 +1925,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.5"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.8",
|
||||
"regex-syntax 0.7.5",
|
||||
"regex-automata 0.4.3",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1944,13 +1946,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
||||
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.5",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1965,6 +1973,12 @@ version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "result-like"
|
||||
version = "0.4.6"
|
||||
@@ -2037,7 +2051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2141,6 +2155,7 @@ name = "ruff_diagnostics"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"is-macro",
|
||||
"log",
|
||||
"ruff_text_size",
|
||||
"serde",
|
||||
@@ -2173,7 +2188,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
@@ -2244,7 +2259,8 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.37",
|
||||
"serde_derive_internals 0.29.0",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2494,6 +2510,7 @@ dependencies = [
|
||||
"glob",
|
||||
"globset",
|
||||
"ignore",
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -2608,7 +2625,7 @@ checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"serde_derive_internals 0.26.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
@@ -2642,15 +2659,15 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2668,13 +2685,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2688,6 +2705,17 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
@@ -2736,7 +2764,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2765,9 +2793,9 @@ checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.2.1"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
|
||||
checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@@ -2831,7 +2859,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2847,9 +2875,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.37"
|
||||
version = "2.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2936,7 +2964,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2948,7 +2976,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2969,7 +2997,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3088,11 +3116,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -3101,20 +3128,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -3244,9 +3271,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b2c0942619ae1797f999a0ce7efc6c09592ad30e68e16cdbfdcd48a98c3579"
|
||||
checksum = "5d5506ae2c3c1ccbdf468e52fc5ef536c2ccd981f01273a4cb81aa61021f3a5f"
|
||||
dependencies = [
|
||||
"phf",
|
||||
"unicode_names2_generator",
|
||||
@@ -3254,9 +3281,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2_generator"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d0d66ab60be9799a70f8eb227ea43da7dcc47561dd9102cbadacfe0930113f7"
|
||||
checksum = "b6dfc680313e95bc6637fa278cd7a22390c3c2cd7b8b2bd28755bc6c0fc811e7"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"log",
|
||||
@@ -3325,7 +3352,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3419,7 +3446,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3453,7 +3480,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
21
Cargo.toml
21
Cargo.toml
@@ -15,47 +15,46 @@ 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.6", 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.34.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.11.0" }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
memchr = { version = "2.6.4" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.67" }
|
||||
proc-macro2 = { version = "1.0.69" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.9.5" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde = { version = "1.0.189", features = ["derive"] }
|
||||
serde_json = { version = "1.0.107" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1", features = ["inline"] }
|
||||
similar = { version = "2.3.0", features = ["inline"] }
|
||||
smallvec = { version = "1.11.1" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.37" }
|
||||
syn = { version = "2.0.38" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.49" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = { version = "0.1.37" }
|
||||
tracing = { version = "0.1.39" }
|
||||
tracing-indicatif = { version = "0.3.4" }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode_names2 = { version = "1.1.0" }
|
||||
unicode_names2 = { version = "1.2.0" }
|
||||
unicode-width = { version = "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 }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
@@ -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.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -172,8 +172,8 @@ If left unspecified, the default configuration is equivalent to:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
||||
select = ["E", "F"]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
@@ -239,7 +239,7 @@ Rust as a first-party feature.
|
||||
|
||||
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
|
||||
and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those
|
||||
stylistic rules made obsolete by the use of an autoformatter, like
|
||||
stylistic rules made obsolete by the use of a formatter, like
|
||||
[Black](https://github.com/psf/black).
|
||||
|
||||
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -13,11 +13,12 @@ use ruff_linter::rules::flake8_quotes::settings::Quote;
|
||||
use ruff_linter::rules::flake8_tidy_imports::settings::Strictness;
|
||||
use ruff_linter::rules::pydocstyle::settings::Convention;
|
||||
use ruff_linter::settings::types::PythonVersion;
|
||||
use ruff_linter::settings::DEFAULT_SELECTORS;
|
||||
use ruff_linter::warn_user;
|
||||
use ruff_workspace::options::{
|
||||
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
|
||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintOptions,
|
||||
McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintCommonOptions,
|
||||
LintOptions, McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
||||
};
|
||||
use ruff_workspace::pyproject::Pyproject;
|
||||
|
||||
@@ -25,11 +26,6 @@ use super::external_config::ExternalConfig;
|
||||
use super::plugin::Plugin;
|
||||
use super::{parser, plugin};
|
||||
|
||||
const DEFAULT_SELECTORS: &[RuleSelector] = &[
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
RuleSelector::Linter(Linter::Pycodestyle),
|
||||
];
|
||||
|
||||
pub(crate) fn convert(
|
||||
config: &HashMap<String, HashMap<String, Option<String>>>,
|
||||
external_config: &ExternalConfig,
|
||||
@@ -103,7 +99,7 @@ pub(crate) fn convert(
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options = Options::default();
|
||||
let mut lint_options = LintOptions::default();
|
||||
let mut lint_options = LintCommonOptions::default();
|
||||
let mut flake8_annotations = Flake8AnnotationsOptions::default();
|
||||
let mut flake8_bugbear = Flake8BugbearOptions::default();
|
||||
let mut flake8_builtins = Flake8BuiltinsOptions::default();
|
||||
@@ -437,8 +433,11 @@ pub(crate) fn convert(
|
||||
}
|
||||
}
|
||||
|
||||
if lint_options != LintOptions::default() {
|
||||
options.lint = Some(lint_options);
|
||||
if lint_options != LintCommonOptions::default() {
|
||||
options.lint = Some(LintOptions {
|
||||
common: lint_options,
|
||||
..LintOptions::default()
|
||||
});
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
@@ -469,7 +468,9 @@ 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, LintCommonOptions, LintOptions, Options, PydocstyleOptions,
|
||||
};
|
||||
use ruff_workspace::pyproject::Pyproject;
|
||||
|
||||
use crate::converter::DEFAULT_SELECTORS;
|
||||
@@ -479,8 +480,8 @@ mod tests {
|
||||
use super::super::plugin::Plugin;
|
||||
use super::convert;
|
||||
|
||||
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintOptions {
|
||||
LintOptions {
|
||||
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintCommonOptions {
|
||||
LintCommonOptions {
|
||||
ignore: Some(vec![]),
|
||||
select: Some(
|
||||
DEFAULT_SELECTORS
|
||||
@@ -490,7 +491,7 @@ mod tests {
|
||||
.sorted_by_key(RuleSelector::prefix_and_code)
|
||||
.collect(),
|
||||
),
|
||||
..LintOptions::default()
|
||||
..LintCommonOptions::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +503,10 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -520,7 +524,10 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -538,7 +545,10 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -555,7 +565,10 @@ mod tests {
|
||||
Some(vec![]),
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -573,13 +586,16 @@ mod tests {
|
||||
);
|
||||
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([])
|
||||
common: LintCommonOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([])
|
||||
},
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
@@ -601,12 +617,15 @@ mod tests {
|
||||
);
|
||||
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()])
|
||||
common: LintCommonOptions {
|
||||
pydocstyle: Some(PydocstyleOptions {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: None,
|
||||
property_decorators: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Pydocstyle.into()])
|
||||
},
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
@@ -625,13 +644,16 @@ mod tests {
|
||||
);
|
||||
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()])
|
||||
common: LintCommonOptions {
|
||||
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()])
|
||||
},
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
@@ -652,7 +674,10 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
target_version: Some(PythonVersion::Py38),
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -9,6 +9,7 @@ use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
|
||||
UnsafeFixes,
|
||||
};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
@@ -76,12 +77,18 @@ pub enum Command {
|
||||
pub struct CheckCommand {
|
||||
/// List of files or directories to check.
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Attempt to automatically fix lint violations.
|
||||
/// Use `--no-fix` to disable.
|
||||
/// Apply fixes to resolve lint violations.
|
||||
/// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||
#[arg(long, overrides_with("no_fix"))]
|
||||
fix: bool,
|
||||
#[clap(long, overrides_with("fix"), hide = true)]
|
||||
no_fix: bool,
|
||||
/// Include fixes that may not retain the original intent of the code.
|
||||
/// Use `--no-unsafe-fixes` to disable.
|
||||
#[arg(long, overrides_with("no_unsafe_fixes"))]
|
||||
unsafe_fixes: bool,
|
||||
#[arg(long, overrides_with("unsafe_fixes"), hide = true)]
|
||||
no_unsafe_fixes: bool,
|
||||
/// Show violations with source code.
|
||||
/// Use `--no-show-source` to disable.
|
||||
#[arg(long, overrides_with("no_show_source"))]
|
||||
@@ -100,8 +107,8 @@ pub struct CheckCommand {
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
pub watch: bool,
|
||||
/// Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`.
|
||||
/// Use `--no-fix-only` to disable.
|
||||
/// Apply fixes to resolve lint violations, but don't report on leftover violations. Implies `--fix`.
|
||||
/// Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes.
|
||||
#[arg(long, overrides_with("no_fix_only"))]
|
||||
fix_only: bool,
|
||||
#[clap(long, overrides_with("fix_only"), hide = true)]
|
||||
@@ -110,16 +117,6 @@ pub struct CheckCommand {
|
||||
#[arg(long)]
|
||||
ignore_noqa: bool,
|
||||
|
||||
/// Output serialization format for violations. (Deprecated: Use `--output-format` instead).
|
||||
#[arg(
|
||||
long,
|
||||
value_enum,
|
||||
env = "RUFF_FORMAT",
|
||||
conflicts_with = "output_format",
|
||||
hide = true
|
||||
)]
|
||||
pub format: Option<SerializationFormat>,
|
||||
|
||||
/// Output serialization format for violations.
|
||||
#[arg(long, value_enum, env = "RUFF_OUTPUT_FORMAT")]
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
@@ -369,6 +366,15 @@ pub struct FormatCommand {
|
||||
respect_gitignore: bool,
|
||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||
no_respect_gitignore: bool,
|
||||
/// List of paths, used to omit files and/or directories from analysis.
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "FILE_PATTERN",
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
|
||||
/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
|
||||
/// Use `--no-force-exclude` to disable.
|
||||
#[arg(
|
||||
@@ -389,9 +395,9 @@ pub struct FormatCommand {
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
|
||||
/// Enable preview mode; checks will include unstable rules and fixes.
|
||||
/// Enable preview mode; enables unstable formatting.
|
||||
/// Use `--no-preview` to disable.
|
||||
#[arg(long, overrides_with("no_preview"), hide = true)]
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
@@ -497,8 +503,10 @@ impl CheckCommand {
|
||||
cache_dir: self.cache_dir,
|
||||
fix: resolve_bool_arg(self.fix, self.no_fix),
|
||||
fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only),
|
||||
unsafe_fixes: resolve_bool_arg(self.unsafe_fixes, self.no_unsafe_fixes)
|
||||
.map(UnsafeFixes::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
output_format: self.output_format.or(self.format),
|
||||
output_format: self.output_format,
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
},
|
||||
)
|
||||
@@ -523,6 +531,7 @@ impl FormatCommand {
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
exclude: self.exclude,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||
@@ -599,6 +608,7 @@ pub struct CliOverrides {
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub fix: Option<bool>,
|
||||
pub fix_only: Option<bool>,
|
||||
pub unsafe_fixes: Option<UnsafeFixes>,
|
||||
pub force_exclude: Option<bool>,
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub show_fixes: Option<bool>,
|
||||
@@ -624,6 +634,9 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
if let Some(fix_only) = &self.fix_only {
|
||||
config.fix_only = Some(*fix_only);
|
||||
}
|
||||
if self.unsafe_fixes.is_some() {
|
||||
config.unsafe_fixes = self.unsafe_fixes;
|
||||
}
|
||||
config.lint.rule_selections.push(RuleSelection {
|
||||
select: self.select.clone(),
|
||||
ignore: self
|
||||
@@ -655,6 +668,8 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
}
|
||||
if let Some(preview) = &self.preview {
|
||||
config.preview = Some(*preview);
|
||||
config.lint.preview = Some(*preview);
|
||||
config.format.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()));
|
||||
|
||||
@@ -338,6 +338,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use filetime::{set_file_mtime, FileTime};
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use std::env::temp_dir;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
@@ -410,6 +411,7 @@ mod tests {
|
||||
Some(&cache),
|
||||
flags::Noqa::Enabled,
|
||||
flags::FixMode::Generate,
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
if diagnostics
|
||||
@@ -455,6 +457,7 @@ mod tests {
|
||||
Some(&cache),
|
||||
flags::Noqa::Enabled,
|
||||
flags::FixMode::Generate,
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -712,6 +715,7 @@ mod tests {
|
||||
Some(cache),
|
||||
flags::Noqa::Enabled,
|
||||
flags::FixMode::Generate,
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(crate) fn add_noqa(
|
||||
&paths
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(ignore::DirEntry::path)
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
@@ -45,14 +45,15 @@ pub(crate) fn add_noqa(
|
||||
let modifications: usize = paths
|
||||
.par_iter()
|
||||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
.filter_map(|resolved_file| {
|
||||
let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
|
||||
SourceType::from(path)
|
||||
SourceType::from(resolved_file.path())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let package = path
|
||||
let path = resolved_file.path();
|
||||
let package = resolved_file
|
||||
.path()
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
|
||||
@@ -11,6 +11,7 @@ use itertools::Itertools;
|
||||
use log::{debug, error, warn};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -21,7 +22,10 @@ use ruff_linter::{fs, warn_user_once, IOError};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy,
|
||||
ResolvedFile,
|
||||
};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::cache::{self, Cache};
|
||||
@@ -36,12 +40,12 @@ pub(crate) fn check(
|
||||
cache: flags::Cache,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
) -> Result<Diagnostics> {
|
||||
// Collect all the Python files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
debug!("Identified files to lint in: {:?}", start.elapsed());
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
@@ -75,7 +79,7 @@ pub(crate) fn check(
|
||||
&paths
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(ignore::DirEntry::path)
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
@@ -96,86 +100,114 @@ pub(crate) fn check(
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
let mut diagnostics: Diagnostics = paths
|
||||
.par_iter()
|
||||
.map(|entry| {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
let package = path
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let diagnostics_per_file = paths.par_iter().filter_map(|resolved_file| {
|
||||
let result = match resolved_file {
|
||||
Ok(resolved_file) => {
|
||||
let path = resolved_file.path();
|
||||
let package = path
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
|
||||
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
||||
let cache = caches.as_ref().and_then(|caches| {
|
||||
if let Some(cache) = caches.get(&cache_root) {
|
||||
Some(cache)
|
||||
} else {
|
||||
debug!("No cache found for {}", cache_root.display());
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
lint_path(path, package, &settings.linter, cache, noqa, fix_mode).map_err(|e| {
|
||||
(Some(path.to_owned()), {
|
||||
let mut error = e.to_string();
|
||||
for cause in e.chain() {
|
||||
write!(&mut error, "\n Cause: {cause}").unwrap();
|
||||
}
|
||||
error
|
||||
})
|
||||
})
|
||||
if !resolved_file.is_root()
|
||||
&& match_exclusion(
|
||||
resolved_file.path(),
|
||||
resolved_file.file_name(),
|
||||
&settings.linter.exclude,
|
||||
)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Err(e) => Err((
|
||||
if let Error::WithPath { path, .. } = e {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
e.io_error()
|
||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||
)),
|
||||
}
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.linter.rules.enabled(Rule::IOError) {
|
||||
let dummy =
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
Diagnostics::new(
|
||||
vec![Message::from_diagnostic(
|
||||
Diagnostic::new(IOError { message }, TextRange::default()),
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
ImportMap::default(),
|
||||
FxHashMap::default(),
|
||||
)
|
||||
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
||||
let cache = caches.as_ref().and_then(|caches| {
|
||||
if let Some(cache) = caches.get(&cache_root) {
|
||||
Some(cache)
|
||||
} else {
|
||||
warn!(
|
||||
"{}{}{} {message}",
|
||||
"Failed to lint ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
Diagnostics::default()
|
||||
debug!("No cache found for {}", cache_root.display());
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
lint_path(
|
||||
path,
|
||||
package,
|
||||
&settings.linter,
|
||||
cache,
|
||||
noqa,
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)
|
||||
.map_err(|e| {
|
||||
(Some(path.to_path_buf()), {
|
||||
let mut error = e.to_string();
|
||||
for cause in e.chain() {
|
||||
write!(&mut error, "\n Cause: {cause}").unwrap();
|
||||
}
|
||||
error
|
||||
})
|
||||
})
|
||||
}
|
||||
Err(e) => Err((
|
||||
if let Error::WithPath { path, .. } = e {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
warn!("{} {message}", "Encountered error:".bold());
|
||||
None
|
||||
},
|
||||
e.io_error()
|
||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||
)),
|
||||
};
|
||||
|
||||
Some(result.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.linter.rules.enabled(Rule::IOError) {
|
||||
let dummy =
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
Diagnostics::new(
|
||||
vec![Message::from_diagnostic(
|
||||
Diagnostic::new(IOError { message }, TextRange::default()),
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
ImportMap::default(),
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
warn!(
|
||||
"{}{}{} {message}",
|
||||
"Failed to lint ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
Diagnostics::default()
|
||||
}
|
||||
})
|
||||
})
|
||||
.reduce(Diagnostics::default, |mut acc, item| {
|
||||
acc += item;
|
||||
acc
|
||||
});
|
||||
} else {
|
||||
warn!("{} {message}", "Encountered error:".bold());
|
||||
Diagnostics::default()
|
||||
}
|
||||
}))
|
||||
});
|
||||
|
||||
diagnostics.messages.sort();
|
||||
// Aggregate the diagnostics of all checked files and count the checked files.
|
||||
// This can't be a regular for loop because we use `par_iter`.
|
||||
let (mut all_diagnostics, checked_files) = diagnostics_per_file
|
||||
.fold(
|
||||
|| (Diagnostics::default(), 0u64),
|
||||
|(all_diagnostics, checked_files), file_diagnostics| {
|
||||
(all_diagnostics + file_diagnostics, checked_files + 1)
|
||||
},
|
||||
)
|
||||
.reduce(
|
||||
|| (Diagnostics::default(), 0u64),
|
||||
|a, b| (a.0 + b.0, a.1 + b.1),
|
||||
);
|
||||
|
||||
all_diagnostics.messages.sort();
|
||||
|
||||
// Store the caches.
|
||||
if let Some(caches) = caches {
|
||||
@@ -185,9 +217,9 @@ pub(crate) fn check(
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked {:?} files in: {:?}", paths.len(), duration);
|
||||
debug!("Checked {:?} files in: {:?}", checked_files, duration);
|
||||
|
||||
Ok(diagnostics)
|
||||
Ok(all_diagnostics)
|
||||
}
|
||||
|
||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||
@@ -199,9 +231,10 @@ fn lint_path(
|
||||
cache: Option<&Cache>,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
) -> 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, fix_mode, unsafe_fixes)
|
||||
});
|
||||
|
||||
match result {
|
||||
@@ -233,6 +266,8 @@ mod test {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -285,6 +320,7 @@ mod test {
|
||||
flags::Cache::Disabled,
|
||||
flags::Noqa::Disabled,
|
||||
flags::FixMode::Generate,
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
let mut output = Vec::new();
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
|
||||
use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_workspace::resolver::{python_file_at_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||
@@ -22,6 +22,14 @@ pub(crate) fn check_stdin(
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
let lint_settings = &pyproject_config.settings.linter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||
{
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
}
|
||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)
|
||||
|
||||
@@ -20,7 +20,7 @@ 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_workspace::resolver::python_files_in_path;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
@@ -61,26 +61,42 @@ pub(crate) fn format(
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let (results, errors): (Vec<_>, Vec<_>) = paths
|
||||
let (mut results, errors): (Vec<_>, Vec<_>) = paths
|
||||
.into_par_iter()
|
||||
.filter_map(|entry| {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.into_path();
|
||||
|
||||
Ok(resolved_file) => {
|
||||
let path = resolved_file.path();
|
||||
let SourceType::Python(source_type) = SourceType::from(&path) else {
|
||||
// Ignore any non-Python files.
|
||||
return None;
|
||||
};
|
||||
|
||||
let resolved_settings = resolver.resolve(&path, &pyproject_config);
|
||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
||||
|
||||
// Ignore files that are excluded from formatting
|
||||
if !resolved_file.is_root()
|
||||
&& match_exclusion(
|
||||
path,
|
||||
resolved_file.file_name(),
|
||||
&resolved_settings.formatter.exclude,
|
||||
)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
match catch_unwind(|| {
|
||||
format_path(&path, &resolved_settings.formatter, source_type, mode)
|
||||
format_path(path, &resolved_settings.formatter, source_type, mode)
|
||||
}) {
|
||||
Ok(inner) => inner.map(|result| FormatPathResult { path, result }),
|
||||
Err(error) => Err(FormatCommandError::Panic(Some(path), error)),
|
||||
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||
path: resolved_file.into_path(),
|
||||
result,
|
||||
}),
|
||||
Err(error) => Err(FormatCommandError::Panic(
|
||||
Some(resolved_file.into_path()),
|
||||
error,
|
||||
)),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -104,6 +120,8 @@ pub(crate) fn format(
|
||||
error!("{error}");
|
||||
}
|
||||
|
||||
results.sort_unstable_by(|a, b| a.path.cmp(&b.path));
|
||||
|
||||
let summary = FormatSummary::new(results.as_slice(), mode);
|
||||
|
||||
// Report on the formatting changes.
|
||||
@@ -137,7 +155,7 @@ pub(crate) fn format(
|
||||
}
|
||||
|
||||
/// Format the file at the given [`Path`].
|
||||
#[tracing::instrument(skip_all, fields(path = %path.display()))]
|
||||
#[tracing::instrument(level="debug", skip_all, fields(path = %path.display()))]
|
||||
fn format_path(
|
||||
path: &Path,
|
||||
settings: &FormatterSettings,
|
||||
|
||||
@@ -6,7 +6,7 @@ use log::error;
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::python_file_at_path;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
@@ -33,6 +33,14 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let format_settings = &pyproject_config.settings.formatter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||
{
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
|
||||
@@ -25,12 +25,13 @@ pub(crate) fn show_files(
|
||||
}
|
||||
|
||||
// Print the list of files.
|
||||
for entry in paths
|
||||
.iter()
|
||||
for path in paths
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.sorted_by(|a, b| a.path().cmp(b.path()))
|
||||
.map(ResolvedFile::into_path)
|
||||
.sorted_unstable()
|
||||
{
|
||||
writeln!(writer, "{}", entry.path().to_string_lossy())?;
|
||||
writeln!(writer, "{}", path.to_string_lossy())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
|
||||
@@ -19,16 +19,17 @@ pub(crate) fn show_settings(
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
|
||||
// Print the list of files.
|
||||
let Some(entry) = paths
|
||||
.iter()
|
||||
let Some(path) = paths
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.sorted_by(|a, b| a.path().cmp(b.path()))
|
||||
.map(ResolvedFile::into_path)
|
||||
.sorted_unstable()
|
||||
.next()
|
||||
else {
|
||||
bail!("No files found under the given path");
|
||||
};
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
|
||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::ops::AddAssign;
|
||||
use std::ops::{Add, AddAssign};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
@@ -19,6 +19,7 @@ use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
@@ -60,7 +61,7 @@ impl FileCacheKey {
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub(crate) struct Diagnostics {
|
||||
pub(crate) messages: Vec<Message>,
|
||||
pub(crate) fixed: FxHashMap<String, FixTable>,
|
||||
pub(crate) fixed: FixMap,
|
||||
pub(crate) imports: ImportMap,
|
||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
@@ -73,7 +74,7 @@ impl Diagnostics {
|
||||
) -> Self {
|
||||
Self {
|
||||
messages,
|
||||
fixed: FxHashMap::default(),
|
||||
fixed: FixMap::default(),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
}
|
||||
@@ -141,22 +142,68 @@ impl Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Diagnostics {
|
||||
type Output = Diagnostics;
|
||||
|
||||
fn add(mut self, other: Self) -> Self::Output {
|
||||
self += other;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Diagnostics {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.messages.extend(other.messages);
|
||||
self.imports.extend(other.imports);
|
||||
for (filename, fixed) in other.fixed {
|
||||
self.fixed += other.fixed;
|
||||
self.notebook_indexes.extend(other.notebook_indexes);
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of fixes indexed by file path.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub(crate) struct FixMap(FxHashMap<String, FixTable>);
|
||||
|
||||
impl FixMap {
|
||||
/// Returns `true` if there are no fixes in the map.
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the fixes in the map, along with the file path.
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (&String, &FixTable)> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the fixes in the map.
|
||||
pub(crate) fn values(&self) -> impl Iterator<Item = &FixTable> {
|
||||
self.0.values()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, FixTable)> for FixMap {
|
||||
fn from_iter<T: IntoIterator<Item = (String, FixTable)>>(iter: T) -> Self {
|
||||
Self(
|
||||
iter.into_iter()
|
||||
.filter(|(_, fixes)| !fixes.is_empty())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for FixMap {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
for (filename, fixed) in rhs.0 {
|
||||
if fixed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let fixed_in_file = self.fixed.entry(filename).or_default();
|
||||
let fixed_in_file = self.0.entry(filename).or_default();
|
||||
for (rule, count) in fixed {
|
||||
if count > 0 {
|
||||
*fixed_in_file.entry(rule).or_default() += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.notebook_indexes.extend(other.notebook_indexes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +215,7 @@ pub(crate) fn lint_path(
|
||||
cache: Option<&Cache>,
|
||||
noqa: flags::Noqa,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
) -> Result<Diagnostics> {
|
||||
// Check the cache.
|
||||
// TODO(charlie): `fixer::Mode::Apply` and `fixer::Mode::Diff` both have
|
||||
@@ -244,8 +292,15 @@ pub(crate) fn lint_path(
|
||||
result,
|
||||
transformed,
|
||||
fixed,
|
||||
}) = lint_fix(path, package, noqa, settings, &source_kind, source_type)
|
||||
{
|
||||
}) = lint_fix(
|
||||
path,
|
||||
package,
|
||||
noqa,
|
||||
unsafe_fixes,
|
||||
settings,
|
||||
&source_kind,
|
||||
source_type,
|
||||
) {
|
||||
if !fixed.is_empty() {
|
||||
match fix_mode {
|
||||
flags::FixMode::Apply => transformed.write(&mut File::create(path)?)?,
|
||||
@@ -309,7 +364,7 @@ pub(crate) fn lint_path(
|
||||
|
||||
Ok(Diagnostics {
|
||||
messages,
|
||||
fixed: FxHashMap::from_iter([(fs::relativize_path(path), fixed)]),
|
||||
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
})
|
||||
@@ -355,6 +410,7 @@ pub(crate) fn lint_stdin(
|
||||
path.unwrap_or_else(|| Path::new("-")),
|
||||
package,
|
||||
noqa,
|
||||
settings.unsafe_fixes,
|
||||
&settings.linter,
|
||||
&source_kind,
|
||||
source_type,
|
||||
@@ -426,7 +482,7 @@ pub(crate) fn lint_stdin(
|
||||
|
||||
Ok(Diagnostics {
|
||||
messages,
|
||||
fixed: FxHashMap::from_iter([(
|
||||
fixed: FixMap::from_iter([(
|
||||
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
|
||||
fixed,
|
||||
)]),
|
||||
|
||||
@@ -6,11 +6,12 @@ use std::sync::mpsc::channel;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::CommandFactory;
|
||||
use colored::Colorize;
|
||||
use log::warn;
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
|
||||
use ruff_linter::logging::{set_up_logging, LogLevel};
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_linter::settings::flags::FixMode;
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
use ruff_linter::{fs, warn_user, warn_user_once};
|
||||
use ruff_workspace::Settings;
|
||||
@@ -104,8 +105,6 @@ pub fn run(
|
||||
}: Args,
|
||||
) -> Result<ExitStatus> {
|
||||
{
|
||||
use colored::Colorize;
|
||||
|
||||
let default_panic_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
#[allow(clippy::print_stderr)]
|
||||
@@ -166,10 +165,7 @@ pub fn run(
|
||||
}
|
||||
|
||||
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
warn_user_once!(
|
||||
"`ruff format` is a work-in-progress, subject to change at any time, and intended only for \
|
||||
experimentation."
|
||||
);
|
||||
warn_user_once!("`ruff format` is not yet stable, and subject to change in future versions.");
|
||||
|
||||
let (cli, overrides) = args.partition();
|
||||
|
||||
@@ -181,14 +177,6 @@ fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
if args.format.is_some() {
|
||||
if std::env::var("RUFF_FORMAT").is_ok() {
|
||||
warn_user!("The environment variable `RUFF_FORMAT` is deprecated. Use `RUFF_OUTPUT_FORMAT` instead.");
|
||||
} else {
|
||||
warn_user!("The argument `--format=<FORMAT>` is deprecated. Use `--output-format=<FORMAT>` instead.");
|
||||
}
|
||||
}
|
||||
|
||||
let (cli, overrides) = args.partition();
|
||||
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
@@ -208,6 +196,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
}
|
||||
_ => Box::new(BufWriter::new(io::stdout())),
|
||||
};
|
||||
let stderr_writer = Box::new(BufWriter::new(io::stderr()));
|
||||
|
||||
if cli.show_settings {
|
||||
commands::show_settings::show_settings(
|
||||
@@ -228,6 +217,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let Settings {
|
||||
fix,
|
||||
fix_only,
|
||||
unsafe_fixes,
|
||||
output_format,
|
||||
show_fixes,
|
||||
show_source,
|
||||
@@ -236,17 +226,20 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
|
||||
// Fix 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
|
||||
// - If `--fix` or `--fix-only` is set, apply applicable 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).
|
||||
// - If `--diff` or `--fix-only` are set, don't print any violations (only applicable fixes)
|
||||
// - By default, applicable fixes only include [`Applicablility::Automatic`], but if
|
||||
// `--unsafe-fixes` is set, then [`Applicablility::Suggested`] fixes are included.
|
||||
|
||||
let fix_mode = if cli.diff {
|
||||
flags::FixMode::Diff
|
||||
FixMode::Diff
|
||||
} else if fix || fix_only {
|
||||
flags::FixMode::Apply
|
||||
FixMode::Apply
|
||||
} else {
|
||||
flags::FixMode::Generate
|
||||
FixMode::Generate
|
||||
};
|
||||
|
||||
let cache = !cli.no_cache;
|
||||
let noqa = !cli.ignore_noqa;
|
||||
let mut printer_flags = PrinterFlags::empty();
|
||||
@@ -290,11 +283,17 @@ 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,
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
printer_flags,
|
||||
);
|
||||
|
||||
if cli.watch {
|
||||
if output_format != SerializationFormat::Text {
|
||||
warn_user!("--format 'text' is used in watch mode.");
|
||||
warn_user!("`--output-format text` is always used in watch mode.");
|
||||
}
|
||||
|
||||
// Configure the file watcher.
|
||||
@@ -318,6 +317,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)?;
|
||||
printer.write_continuously(&mut writer, &messages)?;
|
||||
|
||||
@@ -350,6 +350,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)?;
|
||||
printer.write_continuously(&mut writer, &messages)?;
|
||||
}
|
||||
@@ -376,18 +377,22 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
cache.into(),
|
||||
noqa.into(),
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)?
|
||||
};
|
||||
|
||||
// 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 cli.statistics {
|
||||
printer.write_statistics(&diagnostics, &mut writer)?;
|
||||
} else {
|
||||
printer.write_once(&diagnostics, &mut writer)?;
|
||||
}
|
||||
// Always try to print violations (though the printer itself may suppress output)
|
||||
// If we're writing fixes via stdin, the transformed source code goes to the writer
|
||||
// so send the summary to stderr instead
|
||||
let mut summary_writer = if is_stdin && matches!(fix_mode, FixMode::Apply | FixMode::Diff) {
|
||||
stderr_writer
|
||||
} else {
|
||||
writer
|
||||
};
|
||||
if cli.statistics {
|
||||
printer.write_statistics(&diagnostics, &mut summary_writer)?;
|
||||
} else {
|
||||
printer.write_once(&diagnostics, &mut summary_writer)?;
|
||||
}
|
||||
|
||||
if !cli.exit_zero {
|
||||
|
||||
@@ -7,11 +7,9 @@ use anyhow::Result;
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use itertools::{iterate, Itertools};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::linter::FixTable;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||
@@ -19,10 +17,10 @@ use ruff_linter::message::{
|
||||
};
|
||||
use ruff_linter::notify_user;
|
||||
use ruff_linter::registry::{AsRule, Rule};
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_linter::settings::types::SerializationFormat;
|
||||
use ruff_linter::settings::flags::{self};
|
||||
use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes};
|
||||
|
||||
use crate::diagnostics::Diagnostics;
|
||||
use crate::diagnostics::{Diagnostics, FixMap};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
@@ -73,6 +71,7 @@ pub(crate) struct Printer {
|
||||
format: SerializationFormat,
|
||||
log_level: LogLevel,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
@@ -81,12 +80,14 @@ impl Printer {
|
||||
format: SerializationFormat,
|
||||
log_level: LogLevel,
|
||||
fix_mode: flags::FixMode,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
flags: Flags,
|
||||
) -> Self {
|
||||
Self {
|
||||
format,
|
||||
log_level,
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
@@ -99,12 +100,15 @@ impl Printer {
|
||||
|
||||
fn write_summary_text(&self, writer: &mut dyn Write, diagnostics: &Diagnostics) -> Result<()> {
|
||||
if self.log_level >= LogLevel::Default {
|
||||
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||
|
||||
let fixed = diagnostics
|
||||
.fixed
|
||||
.values()
|
||||
.flat_map(std::collections::HashMap::values)
|
||||
.sum::<usize>();
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_VIOLATIONS) {
|
||||
let fixed = diagnostics
|
||||
.fixed
|
||||
.values()
|
||||
.flat_map(std::collections::HashMap::values)
|
||||
.sum::<usize>();
|
||||
let remaining = diagnostics.messages.len();
|
||||
let total = fixed + remaining;
|
||||
if fixed > 0 {
|
||||
@@ -118,32 +122,83 @@ impl Printer {
|
||||
writeln!(writer, "Found {remaining} error{s}.")?;
|
||||
}
|
||||
|
||||
if show_fix_status(self.fix_mode) {
|
||||
let num_fixable = diagnostics
|
||||
.messages
|
||||
.iter()
|
||||
.filter(|message| message.fix.is_some())
|
||||
.count();
|
||||
if num_fixable > 0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"[{}] {num_fixable} potentially fixable with the --fix option.",
|
||||
"*".cyan(),
|
||||
)?;
|
||||
if let Some(fixables) = fixables {
|
||||
let fix_prefix = format!("[{}]", "*".cyan());
|
||||
|
||||
if self.unsafe_fixes.is_enabled() {
|
||||
if fixables.applicable > 0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"{fix_prefix} {} fixable with the --fix option.",
|
||||
fixables.applicable
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
if fixables.applicable > 0 && fixables.unapplicable_unsafe > 0 {
|
||||
let es = if fixables.unapplicable_unsafe == 1 {
|
||||
""
|
||||
} else {
|
||||
"es"
|
||||
};
|
||||
writeln!(writer,
|
||||
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||
fixables.applicable, fixables.unapplicable_unsafe
|
||||
)?;
|
||||
} else if fixables.applicable > 0 {
|
||||
// Only applicable fixes
|
||||
writeln!(
|
||||
writer,
|
||||
"{fix_prefix} {} fixable with the `--fix` option.",
|
||||
fixables.applicable,
|
||||
)?;
|
||||
} else {
|
||||
// Only unapplicable fixes
|
||||
let es = if fixables.unapplicable_unsafe == 1 {
|
||||
""
|
||||
} else {
|
||||
"es"
|
||||
};
|
||||
writeln!(writer,
|
||||
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||
fixables.unapplicable_unsafe
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let fixed = diagnostics
|
||||
.fixed
|
||||
.values()
|
||||
.flat_map(std::collections::HashMap::values)
|
||||
.sum::<usize>();
|
||||
if fixed > 0 {
|
||||
let s = if fixed == 1 { "" } else { "s" };
|
||||
if self.fix_mode.is_apply() {
|
||||
writeln!(writer, "Fixed {fixed} error{s}.")?;
|
||||
// Check if there are unapplied fixes
|
||||
let unapplied = {
|
||||
if let Some(fixables) = fixables {
|
||||
fixables.unapplicable_unsafe
|
||||
} else {
|
||||
writeln!(writer, "Would fix {fixed} error{s}.")?;
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
if unapplied > 0 {
|
||||
let es = if unapplied == 1 { "" } else { "es" };
|
||||
if fixed > 0 {
|
||||
let s = if fixed == 1 { "" } else { "s" };
|
||||
if self.fix_mode.is_apply() {
|
||||
writeln!(writer, "Fixed {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
|
||||
} else {
|
||||
writeln!(writer, "Would fix {fixed} error{s} ({unapplied} additional fix{es} available with `--unsafe-fixes`).")?;
|
||||
}
|
||||
} else {
|
||||
if self.fix_mode.is_apply() {
|
||||
writeln!(writer, "No errors fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
|
||||
} else {
|
||||
writeln!(writer, "No errors would be fixed ({unapplied} fix{es} available with `--unsafe-fixes`).")?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if fixed > 0 {
|
||||
let s = if fixed == 1 { "" } else { "s" };
|
||||
if self.fix_mode.is_apply() {
|
||||
writeln!(writer, "Fixed {fixed} error{s}.")?;
|
||||
} else {
|
||||
writeln!(writer, "Would fix {fixed} error{s}.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +233,7 @@ impl Printer {
|
||||
}
|
||||
|
||||
let context = EmitterContext::new(&diagnostics.notebook_indexes);
|
||||
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||
|
||||
match self.format {
|
||||
SerializationFormat::Json => {
|
||||
@@ -191,9 +247,10 @@ impl Printer {
|
||||
}
|
||||
SerializationFormat::Text => {
|
||||
TextEmitter::default()
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode))
|
||||
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
|
||||
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
|
||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
@@ -209,7 +266,8 @@ 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.fix_mode, fixables.as_ref()))
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
|
||||
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
|
||||
@@ -359,6 +417,8 @@ impl Printer {
|
||||
);
|
||||
}
|
||||
|
||||
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
|
||||
|
||||
if !diagnostics.messages.is_empty() {
|
||||
if self.log_level >= LogLevel::Default {
|
||||
writeln!(writer)?;
|
||||
@@ -366,8 +426,9 @@ 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.fix_mode, fixables.as_ref()))
|
||||
.with_show_source(self.flags.intersects(Flags::SHOW_SOURCE))
|
||||
.with_unsafe_fixes(self.unsafe_fixes)
|
||||
.emit(writer, &diagnostics.messages, &context)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
@@ -390,16 +451,16 @@ 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 {
|
||||
fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics>) -> 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
|
||||
// enabled, we may inadvertently indicate that a rule is fixable.)
|
||||
!fix_mode.is_apply()
|
||||
(!fix_mode.is_apply()) && fixables.is_some_and(FixableStatistics::any_applicable_fixes)
|
||||
}
|
||||
|
||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
|
||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> {
|
||||
let total = fixed
|
||||
.values()
|
||||
.map(|table| table.values().sum::<usize>())
|
||||
@@ -439,3 +500,43 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Statistics for [applicable][ruff_diagnostics::Applicability] fixes.
|
||||
#[derive(Debug)]
|
||||
struct FixableStatistics {
|
||||
applicable: u32,
|
||||
unapplicable_unsafe: u32,
|
||||
}
|
||||
|
||||
impl FixableStatistics {
|
||||
fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option<Self> {
|
||||
let mut applicable = 0;
|
||||
let mut unapplicable_unsafe = 0;
|
||||
|
||||
for message in &diagnostics.messages {
|
||||
if let Some(fix) = &message.fix {
|
||||
if fix.applies(unsafe_fixes.required_applicability()) {
|
||||
applicable += 1;
|
||||
} else {
|
||||
// Do not include unapplicable fixes at other levels that do not provide an opt-in
|
||||
if fix.applicability().is_unsafe() {
|
||||
unapplicable_unsafe += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if applicable == 0 && unapplicable_unsafe == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Self {
|
||||
applicable,
|
||||
unapplicable_unsafe,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn any_applicable_fixes(&self) -> bool {
|
||||
self.applicable > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const BIN_NAME: &str = "ruff";
|
||||
#[test]
|
||||
fn default_options() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated"])
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
@@ -39,7 +39,7 @@ 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.
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ fn format_options() -> Result<()> {
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
tab-size = 8
|
||||
line-length = 84
|
||||
|
||||
[format]
|
||||
indent-style = "tab"
|
||||
quote-style = "single"
|
||||
@@ -64,7 +67,7 @@ line-ending = "cr-lf"
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't change quotes")
|
||||
print("Shouldn't change quotes. It exceeds the line width with the tab size 8")
|
||||
|
||||
|
||||
if condition:
|
||||
@@ -76,14 +79,118 @@ if condition:
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(arg1, arg2):
|
||||
print("Shouldn't change quotes")
|
||||
print(
|
||||
"Shouldn't change quotes. It exceeds the line width with the tab size 8"
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-exclude = ["out"]
|
||||
|
||||
[format]
|
||||
exclude = ["test.py", "generated.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.py"),
|
||||
r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Excluded file but passed to the CLI directly, should be formatted
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("generated.py"),
|
||||
r#"NUMBERS = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||
]
|
||||
OTHER = "OTHER"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let out_dir = tempdir.path().join("out");
|
||||
fs::create_dir(&out_dir)?;
|
||||
|
||||
fs::write(out_dir.join("a.py"), "a = a")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(["format", "--check", "--config"])
|
||||
.arg(ruff_toml.file_name().unwrap())
|
||||
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
|
||||
.arg(test_path.file_name().unwrap())
|
||||
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Would reformat: main.py
|
||||
Would reformat: test.py
|
||||
2 files would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[format]
|
||||
exclude = ["generated.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "-"])
|
||||
.pass_stdin(r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -139,12 +246,12 @@ 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.
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that the legacy `format` option continues to work but emits a warning.
|
||||
/// Since 0.1.0 the legacy format option is no longer supported
|
||||
#[test]
|
||||
fn legacy_format_option() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
@@ -156,52 +263,29 @@ 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.
|
||||
"###);
|
||||
insta::with_settings!({filters => vec![
|
||||
(&*regex::escape(ruff_toml.to_str().unwrap()), "[RUFF-TOML-PATH]"),
|
||||
]}, {
|
||||
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: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Failed to parse `[RUFF-TOML-PATH]`: TOML parse error at line 2, column 10
|
||||
|
|
||||
2 | format = "json"
|
||||
| ^^^^^^
|
||||
invalid type: string "json", expected struct FormatOptions
|
||||
|
||||
"###);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::fs;
|
||||
#[cfg(unix)]
|
||||
use std::fs::Permissions;
|
||||
@@ -12,13 +11,13 @@ 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};
|
||||
#[cfg(unix)]
|
||||
use path_absolutize::path_dedot;
|
||||
#[cfg(unix)]
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -46,16 +45,16 @@ fn stdin_success() {
|
||||
fn stdin_error() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.pass_stdin("import os\n"), @r#"
|
||||
.pass_stdin("import os\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 potentially fixable with the --fix option.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -69,7 +68,7 @@ fn stdin_filename() {
|
||||
----- stdout -----
|
||||
F401.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 potentially fixable with the --fix option.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -87,7 +86,7 @@ fn stdin_source_type_py() {
|
||||
----- stdout -----
|
||||
TCH.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 potentially fixable with the --fix option.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -150,6 +149,7 @@ fn stdin_fix_py() {
|
||||
print(sys.version)
|
||||
|
||||
----- stderr -----
|
||||
Found 1 error (1 fixed, 0 remaining).
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -317,6 +317,7 @@ fn stdin_fix_jupyter() {
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
----- stderr -----
|
||||
Found 2 errors (2 fixed, 0 remaining).
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -336,6 +337,8 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
||||
print(sys.version)
|
||||
|
||||
----- stderr -----
|
||||
-:3:4: F634 If test is a tuple, which is always `True`
|
||||
Found 2 errors (1 fixed, 1 remaining).
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -482,7 +485,7 @@ fn stdin_format_jupyter() {
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -733,6 +736,42 @@ fn preview_disabled_prefix_empty() {
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_disabled_does_not_warn_for_empty_ignore_selections() {
|
||||
// Does not warn that the selection is empty since the user is not trying to enable the rule
|
||||
let args = ["--ignore", "CPY"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(args)
|
||||
.pass_stdin("I=42\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `I`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_disabled_does_not_warn_for_empty_fixable_selections() {
|
||||
// Does not warn that the selection is empty since the user is not trying to enable the rule
|
||||
let args = ["--fixable", "CPY"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(args)
|
||||
.pass_stdin("I=42\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `I`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_group_selector() {
|
||||
// `--select PREVIEW` should error (selector was removed)
|
||||
@@ -861,7 +900,7 @@ fn check_input_from_argfile() -> Result<()> {
|
||||
----- stdout -----
|
||||
/path/to/a.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 potentially fixable with the --fix option.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -869,3 +908,499 @@ fn check_input_from_argfile() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_hints_hidden_unsafe_fixes() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format=text",
|
||||
"--isolated",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--no-cache",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
-:2:7: UP034 [*] Avoid extraneous parentheses
|
||||
Found 2 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["-", "--output-format", "text", "--no-cache", "--isolated", "--select", "F601"])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
Found 1 error.
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_shows_unsafe_fixes_with_opt_in() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format=text",
|
||||
"--isolated",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--no-cache",
|
||||
"--unsafe-fixes",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 [*] Dictionary key literal `'a'` repeated
|
||||
-:2:7: UP034 [*] Avoid extraneous parentheses
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the --fix option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_applies_safe_fixes_by_default() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--fix",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
x = {'a': 1, 'a': 1}
|
||||
print('foo')
|
||||
|
||||
----- stderr -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
Found 2 errors (1 fixed, 1 remaining).
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_applies_unsafe_fixes_with_opt_in() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--fix",
|
||||
"--unsafe-fixes",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
x = {'a': 1}
|
||||
print('foo')
|
||||
|
||||
----- stderr -----
|
||||
Found 2 errors (2 fixed, 0 remaining).
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_does_not_apply_display_only_fixes() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"B006",
|
||||
"--fix",
|
||||
])
|
||||
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
def add_to_list(item, some_list=[]): ...
|
||||
----- stderr -----
|
||||
-:1:33: B006 Do not use mutable data structures for argument defaults
|
||||
Found 1 error.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"B006",
|
||||
"--fix",
|
||||
"--unsafe-fixes",
|
||||
])
|
||||
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
def add_to_list(item, some_list=[]): ...
|
||||
----- stderr -----
|
||||
-:1:33: B006 Do not use mutable data structures for argument defaults
|
||||
Found 1 error.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_only_unsafe_fixes_available() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601",
|
||||
"--fix",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
x = {'a': 1, 'a': 1}
|
||||
print(('foo'))
|
||||
|
||||
----- stderr -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
Found 1 error.
|
||||
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_only_flag_applies_safe_fixes_by_default() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--fix-only",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
x = {'a': 1, 'a': 1}
|
||||
print('foo')
|
||||
|
||||
----- stderr -----
|
||||
Fixed 1 error (1 additional fix available with `--unsafe-fixes`).
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_only_flag_applies_unsafe_fixes_with_opt_in() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--fix-only",
|
||||
"--unsafe-fixes",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
x = {'a': 1}
|
||||
print('foo')
|
||||
|
||||
----- stderr -----
|
||||
Fixed 2 errors.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_shows_safe_fixes_by_default() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--diff",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -1,2 +1,2 @@
|
||||
x = {'a': 1, 'a': 1}
|
||||
-print(('foo'))
|
||||
+print('foo')
|
||||
|
||||
|
||||
----- stderr -----
|
||||
Would fix 1 error (1 additional fix available with `--unsafe-fixes`).
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_shows_unsafe_fixes_with_opt_in() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
"--diff",
|
||||
"--unsafe-fixes",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -1,2 +1,2 @@
|
||||
-x = {'a': 1, 'a': 1}
|
||||
-print(('foo'))
|
||||
+x = {'a': 1}
|
||||
+print('foo')
|
||||
|
||||
|
||||
----- stderr -----
|
||||
Would fix 2 errors.
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"B006",
|
||||
"--diff",
|
||||
"--unsafe-fixes",
|
||||
])
|
||||
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_only_unsafe_fixes_available() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args([
|
||||
"-",
|
||||
"--output-format",
|
||||
"text",
|
||||
"--isolated",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601",
|
||||
"--diff",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
No errors would be fixed (1 fix available with `--unsafe-fixes`).
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_extend_unsafe_fixes() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-unsafe-fixes = ["UP034"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.args([
|
||||
"--output-format",
|
||||
"text",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
-:2:7: UP034 Avoid extraneous parentheses
|
||||
Found 2 errors.
|
||||
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_extend_safe_fixes() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-safe-fixes = ["F601"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.args([
|
||||
"--output-format",
|
||||
"text",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 [*] Dictionary key literal `'a'` repeated
|
||||
-:2:7: UP034 [*] Avoid extraneous parentheses
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_extend_unsafe_fixes_conflict_with_extend_safe_fixes() -> Result<()> {
|
||||
// Adding a rule to both options should result in it being treated as unsafe
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
extend-unsafe-fixes = ["UP034"]
|
||||
extend-safe-fixes = ["UP034"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.args([
|
||||
"--output-format",
|
||||
"text",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP034",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
-:2:7: UP034 Avoid extraneous parentheses
|
||||
Found 2 errors.
|
||||
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,16 +31,17 @@ inline-quotes = "single"
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--stdin-filename", "test.py"])
|
||||
.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
|
||||
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 potentially fixable with the --fix option.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -75,7 +76,7 @@ inline-quotes = "single"
|
||||
-: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.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -110,7 +111,7 @@ inline-quotes = "single"
|
||||
-: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.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
@@ -149,7 +150,121 @@ inline-quotes = "single"
|
||||
-: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.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
extend-exclude = ["out"]
|
||||
|
||||
[lint]
|
||||
exclude = ["test.py", "generated.py"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.py"),
|
||||
r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Excluded file but passed to the CLI directly, should be linted
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("generated.py"),
|
||||
r#"NUMBERS = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||
]
|
||||
OTHER = "OTHER"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let out_dir = tempdir.path().join("out");
|
||||
fs::create_dir(&out_dir)?;
|
||||
|
||||
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
|
||||
.arg(test_path.file_name().unwrap())
|
||||
// Lint all other files in the directory, should respect the `exclude` and `lint.exclude` options
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:4:16: Q000 [*] Double quotes found but single quotes preferred
|
||||
main.py:5:12: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:3:15: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 3 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[lint]
|
||||
exclude = ["generated.py"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
@@ -17,6 +17,7 @@ exit_code: 1
|
||||
----- stdout -----
|
||||
[
|
||||
{
|
||||
"cell": null,
|
||||
"code": "F401",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
@@ -24,7 +25,7 @@ exit_code: 1
|
||||
},
|
||||
"filename": "/path/to/F401.py",
|
||||
"fix": {
|
||||
"applicability": "Automatic",
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::{fmt, fs, io, iter};
|
||||
|
||||
use anyhow::{bail, format_err, Context, Error};
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use ignore::DirEntry;
|
||||
use imara_diff::intern::InternedInput;
|
||||
use imara_diff::sink::Counter;
|
||||
use imara_diff::{diff, Algorithm};
|
||||
@@ -36,14 +35,14 @@ use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||
use ruff_python_formatter::{
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||
};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, Resolver};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||
|
||||
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths(
|
||||
dirs: &[PathBuf],
|
||||
) -> anyhow::Result<(
|
||||
Vec<Result<DirEntry, ignore::Error>>,
|
||||
Vec<Result<ResolvedFile, ignore::Error>>,
|
||||
Resolver,
|
||||
PyprojectConfig,
|
||||
)> {
|
||||
@@ -467,9 +466,9 @@ fn format_dev_project(
|
||||
let iter = { paths.into_par_iter() };
|
||||
#[cfg(feature = "singlethreaded")]
|
||||
let iter = { paths.into_iter() };
|
||||
iter.map(|dir_entry| {
|
||||
iter.map(|path| {
|
||||
let result = format_dir_entry(
|
||||
dir_entry,
|
||||
path,
|
||||
stability_check,
|
||||
write,
|
||||
&black_options,
|
||||
@@ -527,24 +526,20 @@ fn format_dev_project(
|
||||
|
||||
/// Error handling in between walkdir and `format_dev_file`
|
||||
fn format_dir_entry(
|
||||
dir_entry: Result<DirEntry, ignore::Error>,
|
||||
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||
stability_check: bool,
|
||||
write: bool,
|
||||
options: &BlackOptions,
|
||||
resolver: &Resolver,
|
||||
pyproject_config: &PyprojectConfig,
|
||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||
let dir_entry = match dir_entry.context("Iterating the files in the repository failed") {
|
||||
Ok(dir_entry) => dir_entry,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let file = dir_entry.path().to_path_buf();
|
||||
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
||||
// For some reason it does not filter in the beginning
|
||||
if dir_entry.file_name() == "pyproject.toml" {
|
||||
return Ok((Ok(Statistics::default()), file));
|
||||
if resolved_file.file_name() == "pyproject.toml" {
|
||||
return Ok((Ok(Statistics::default()), resolved_file.into_path()));
|
||||
}
|
||||
|
||||
let path = dir_entry.path().to_path_buf();
|
||||
let path = resolved_file.into_path();
|
||||
let mut options = options.to_py_format_options(&path);
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
//!
|
||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruff_workspace::options::Options;
|
||||
@@ -107,6 +108,24 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push('\n');
|
||||
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
|
||||
output.push('\n');
|
||||
|
||||
if !field.aliases.is_empty() {
|
||||
let title = if field.aliases.len() == 1 {
|
||||
"Alias"
|
||||
} else {
|
||||
"Aliases"
|
||||
};
|
||||
output.push_str(&format!(
|
||||
"**{title}**: {}\n",
|
||||
field
|
||||
.aliases
|
||||
.iter()
|
||||
.map(|alias| format!("`{alias}`"))
|
||||
.join(", ")
|
||||
));
|
||||
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() {
|
||||
|
||||
@@ -17,4 +17,5 @@ ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
serde = { workspace = true, optional = true, features = [] }
|
||||
|
||||
@@ -5,27 +5,22 @@ use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::edit::Edit;
|
||||
|
||||
/// Indicates confidence in the correctness of a suggested fix.
|
||||
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Indicates if a fix can be applied.
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
|
||||
pub enum Applicability {
|
||||
/// The fix is definitely what the user intended, or maintains the exact meaning of the code.
|
||||
/// This fix should be automatically applied.
|
||||
Automatic,
|
||||
/// The fix is unsafe and should only be displayed for manual application by the user.
|
||||
/// The fix is likely to be incorrect or the resulting code may have invalid syntax.
|
||||
Display,
|
||||
|
||||
/// The fix may be what the user intended, but it is uncertain.
|
||||
/// The fix should result in valid code if it is applied.
|
||||
/// The fix can be applied with user opt-in.
|
||||
Suggested,
|
||||
/// The fix is unsafe and should only be applied with user opt-in.
|
||||
/// The fix may be what the user intended, but it is uncertain; the resulting code will have valid syntax.
|
||||
Unsafe,
|
||||
|
||||
/// The fix has a good chance of being incorrect or the code be incomplete.
|
||||
/// The fix may result in invalid code if it is applied.
|
||||
/// The fix should only be manually applied by the user.
|
||||
Manual,
|
||||
|
||||
/// The applicability of the fix is unknown.
|
||||
#[default]
|
||||
Unspecified,
|
||||
/// The fix is safe and can always be applied.
|
||||
/// The fix is definitely what the user intended, or it maintains the exact meaning of the code.
|
||||
Safe,
|
||||
}
|
||||
|
||||
/// Indicates the level of isolation required to apply a fix.
|
||||
@@ -52,86 +47,62 @@ pub struct Fix {
|
||||
}
|
||||
|
||||
impl Fix {
|
||||
/// Create a new [`Fix`] with an unspecified applicability from an [`Edit`] element.
|
||||
#[deprecated(
|
||||
note = "Use `Fix::automatic`, `Fix::suggested`, or `Fix::manual` instead to specify an applicability."
|
||||
)]
|
||||
pub fn unspecified(edit: Edit) -> Self {
|
||||
/// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from an [`Edit`] element.
|
||||
pub fn safe_edit(edit: Edit) -> Self {
|
||||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Unspecified,
|
||||
applicability: Applicability::Safe,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with an unspecified applicability from multiple [`Edit`] elements.
|
||||
#[deprecated(
|
||||
note = "Use `Fix::automatic_edits`, `Fix::suggested_edits`, or `Fix::manual_edits` instead to specify an applicability."
|
||||
)]
|
||||
pub fn unspecified_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
Self {
|
||||
edits: std::iter::once(edit).chain(rest).collect(),
|
||||
applicability: Applicability::Unspecified,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from an [`Edit`] element.
|
||||
pub fn automatic(edit: Edit) -> Self {
|
||||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Automatic,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from multiple [`Edit`] elements.
|
||||
pub fn safe_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()));
|
||||
Self {
|
||||
edits,
|
||||
applicability: Applicability::Automatic,
|
||||
applicability: Applicability::Safe,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from an [`Edit`] element.
|
||||
pub fn suggested(edit: Edit) -> Self {
|
||||
/// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from an [`Edit`] element.
|
||||
pub fn unsafe_edit(edit: Edit) -> Self {
|
||||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Suggested,
|
||||
applicability: Applicability::Unsafe,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from multiple [`Edit`] elements.
|
||||
pub fn unsafe_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()));
|
||||
Self {
|
||||
edits,
|
||||
applicability: Applicability::Suggested,
|
||||
applicability: Applicability::Unsafe,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from an [`Edit`] element.
|
||||
pub fn manual(edit: Edit) -> Self {
|
||||
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from an [`Edit`] element .
|
||||
pub fn display_edit(edit: Edit) -> Self {
|
||||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Manual,
|
||||
applicability: Applicability::Display,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from multiple [`Edit`] elements.
|
||||
pub fn display_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()));
|
||||
Self {
|
||||
edits,
|
||||
applicability: Applicability::Manual,
|
||||
applicability: Applicability::Display,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
@@ -162,4 +133,16 @@ impl Fix {
|
||||
self.isolation_level = isolation;
|
||||
self
|
||||
}
|
||||
|
||||
/// Return [`true`] if this [`Fix`] should be applied with at a given [`Applicability`].
|
||||
pub fn applies(&self, applicability: Applicability) -> bool {
|
||||
self.applicability >= applicability
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with the given [`Applicability`].
|
||||
#[must_use]
|
||||
pub fn with_applicability(mut self, applicability: Applicability) -> Self {
|
||||
self.applicability = applicability;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
static_assertions = { workspace = true }
|
||||
tracing = { version = "0.1.37", default-features = false, features = ["std"] }
|
||||
tracing = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -95,7 +95,7 @@ impl std::fmt::Display for IndentStyle {
|
||||
///
|
||||
/// Determines the visual width of a tab character (`\t`) and the number of
|
||||
/// spaces per indent when using [`IndentStyle::Space`].
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct IndentWidth(NonZeroU8);
|
||||
|
||||
@@ -54,7 +54,7 @@ impl<'a> Printer<'a> {
|
||||
|
||||
/// Prints the passed in element as well as all its content,
|
||||
/// starting at the specified indentation level
|
||||
#[tracing::instrument(name = "Printer::print", skip_all)]
|
||||
#[tracing::instrument(level = "debug", name = "Printer::print", skip_all)]
|
||||
pub fn print_with_indent(
|
||||
mut self,
|
||||
document: &'a Document,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.292"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -29,7 +29,7 @@ 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" }
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
@@ -59,7 +59,7 @@ 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.20" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
|
||||
@@ -70,6 +70,8 @@ settings.set_enable_developer_extras(True)
|
||||
foo.is_(True)
|
||||
bar.is_not(False)
|
||||
next(iter([]), False)
|
||||
sa.func.coalesce(tbl.c.valid, False)
|
||||
|
||||
|
||||
class Registry:
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -8,6 +8,8 @@ Foo.objects.create(**{"_id": some_id}) # PIE804
|
||||
|
||||
Foo.objects.create(**{**bar}) # PIE804
|
||||
|
||||
foo(**{})
|
||||
|
||||
|
||||
foo(**{**data, "foo": "buzz"})
|
||||
foo(**buzz)
|
||||
|
||||
@@ -28,4 +28,12 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
|
||||
def func():
|
||||
# PYI055
|
||||
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
|
||||
y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
||||
|
||||
|
||||
def func():
|
||||
from typing import Union as U
|
||||
|
||||
# PYI055
|
||||
x: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
||||
|
||||
@@ -21,4 +21,5 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
|
||||
def func():
|
||||
# PYI055
|
||||
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
|
||||
item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
|
||||
item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
||||
|
||||
@@ -42,3 +42,7 @@ with contextlib.ExitStack():
|
||||
with contextlib.ExitStack() as exit_stack:
|
||||
exit_stack_ = exit_stack
|
||||
f = exit_stack_.enter_context(open("filename"))
|
||||
|
||||
# OK (quick one-liner to clear file contents)
|
||||
open("filename", "w").close()
|
||||
pathlib.Path("filename").open("w").close()
|
||||
|
||||
19
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .foo import Record
|
||||
|
||||
type RecordOrThings = Record | int | str
|
||||
type RecordCallback[R: Record] = Callable[[R], None]
|
||||
|
||||
|
||||
def process_record[R: Record](record: R) -> None:
|
||||
...
|
||||
|
||||
|
||||
class RecordContainer[R: Record]:
|
||||
def add_record(self, record: R) -> None:
|
||||
...
|
||||
@@ -22,3 +22,10 @@ class C:
|
||||
|
||||
class D(C):
|
||||
x: UUID
|
||||
|
||||
|
||||
import collections
|
||||
|
||||
|
||||
class E(BaseModel[int]):
|
||||
x: collections.Awaitable
|
||||
|
||||
@@ -33,6 +33,8 @@ with open(p) as fp:
|
||||
fp.read()
|
||||
open(p).close()
|
||||
os.getcwdb(p)
|
||||
os.path.join(p, *q)
|
||||
os.sep.join(p, *q)
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/7620
|
||||
def opener(path, flags):
|
||||
|
||||
@@ -54,3 +54,8 @@ f"{a=}"
|
||||
f"{a:=1}"
|
||||
f"{foo(a=1)}"
|
||||
f"normal {f"{a=}"} normal"
|
||||
|
||||
# Okay as the `=` is used inside a f-string...
|
||||
print(f"{foo = }")
|
||||
# ...but then it creates false negatives for now
|
||||
print(f"{foo(a = 1)}")
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def with_backslash():
|
||||
"""Sum\\mary."""
|
||||
|
||||
|
||||
def ends_in_quote():
|
||||
'Sum\\mary."'
|
||||
|
||||
|
||||
def contains_quote():
|
||||
'Sum"\\mary.'
|
||||
@@ -10,6 +10,10 @@ def double_quotes_backslash_uppercase():
|
||||
R"""Sum\\mary."""
|
||||
|
||||
|
||||
def shouldnt_add_raw_here():
|
||||
"Ruff \U000026a1"
|
||||
|
||||
|
||||
def make_unique_pod_id(pod_id: str) -> str | None:
|
||||
r"""
|
||||
Generate a unique Pod name.
|
||||
|
||||
17
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py
vendored
Normal file
17
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Test that type parameters are considered used."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from .foo import Record as Record1
|
||||
from .bar import Record as Record2
|
||||
|
||||
type RecordCallback[R: Record1] = Callable[[R], None]
|
||||
|
||||
|
||||
def process_record[R: Record2](record: R) -> None:
|
||||
...
|
||||
19
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Test bindings created within annotations."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
foo = [1, 2, 3, 4, 5]
|
||||
|
||||
|
||||
class Bar:
|
||||
# OK: Allow list comprehensions in annotations (i.e., treat `qux` as a valid
|
||||
# load in the scope of the annotation).
|
||||
baz: Annotated[
|
||||
str,
|
||||
[qux for qux in foo],
|
||||
]
|
||||
|
||||
|
||||
# OK: Allow named expressions in annotations.
|
||||
x: (y := 1)
|
||||
print(y)
|
||||
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Test bindings created within annotations under `__future__` annotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
foo = [1, 2, 3, 4, 5]
|
||||
|
||||
|
||||
class Bar:
|
||||
# OK: Allow list comprehensions in annotations (i.e., treat `qux` as a valid
|
||||
# load in the scope of the annotation).
|
||||
baz: Annotated[
|
||||
str,
|
||||
[qux for qux in foo],
|
||||
]
|
||||
|
||||
|
||||
# Error: `y` is not defined.
|
||||
x: (y := 1)
|
||||
print(y)
|
||||
5
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Test lazy evaluation of type alias values."""
|
||||
|
||||
type RecordCallback[R: Record] = Callable[[R], None]
|
||||
|
||||
from collections.abc import Callable
|
||||
73
crates/ruff_linter/resources/test/fixtures/pylint/and_or_ternary.py
vendored
Normal file
73
crates/ruff_linter/resources/test/fixtures/pylint/and_or_ternary.py
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# OK
|
||||
|
||||
1<2 and 'b' and 'c'
|
||||
|
||||
1<2 or 'a' and 'b'
|
||||
|
||||
1<2 and 'a'
|
||||
|
||||
1<2 or 'a'
|
||||
|
||||
2>1
|
||||
|
||||
1<2 and 'a' or 'b' and 'c'
|
||||
|
||||
1<2 and 'a' or 'b' or 'c'
|
||||
|
||||
1<2 and 'a' or 'b' or 'c' or (lambda x: x+1)
|
||||
|
||||
1<2 and 'a' or 'b' or (lambda x: x+1) or 'c'
|
||||
|
||||
default = 'default'
|
||||
if (not isinstance(default, bool) and isinstance(default, int)) \
|
||||
or (isinstance(default, str) and default):
|
||||
pass
|
||||
|
||||
docid, token = None, None
|
||||
(docid is None and token is None) or (docid is not None and token is not None)
|
||||
|
||||
vendor, os_version = 'darwin', '14'
|
||||
vendor == "debian" and os_version in ["12"] or vendor == "ubuntu" and os_version in []
|
||||
|
||||
# Don't emit if the parent is an `if` statement.
|
||||
if (task_id in task_dict and task_dict[task_id] is not task) \
|
||||
or task_id in used_group_ids:
|
||||
pass
|
||||
|
||||
no_target, is_x64, target = True, False, 'target'
|
||||
if (no_target and not is_x64) or target == 'ARM_APPL_RUST_TARGET':
|
||||
pass
|
||||
|
||||
# Don't emit if the parent is a `bool_op` expression.
|
||||
isinstance(val, str) and ((len(val) == 7 and val[0] == "#") or val in enums.NamedColor)
|
||||
|
||||
# Errors
|
||||
|
||||
1<2 and 'a' or 'b'
|
||||
|
||||
(lambda x: x+1) and 'a' or 'b'
|
||||
|
||||
'a' and (lambda x: x+1) or 'orange'
|
||||
|
||||
val = '#0000FF'
|
||||
(len(val) == 7 and val[0] == "#") or val in {'green'}
|
||||
|
||||
marker = 'marker'
|
||||
isinstance(marker, dict) and 'field' in marker or marker in {}
|
||||
|
||||
def has_oranges(oranges, apples=None) -> bool:
|
||||
return apples and False or oranges
|
||||
|
||||
[x for x in l if a and b or c]
|
||||
|
||||
{x: y for x in l if a and b or c}
|
||||
|
||||
{x for x in l if a and b or c}
|
||||
|
||||
new_list = [
|
||||
x
|
||||
for sublist in all_lists
|
||||
if a and b or c
|
||||
for x in sublist
|
||||
if (isinstance(operator, list) and x in operator) or x != operator
|
||||
]
|
||||
@@ -63,6 +63,15 @@ class Apples:
|
||||
def __html__(self):
|
||||
pass
|
||||
|
||||
# Allow _missing_, used by enum.Enum.
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
pass
|
||||
|
||||
# Allow anonymous functions.
|
||||
def _(self):
|
||||
pass
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
# Errors
|
||||
|
||||
for item in {1}:
|
||||
print(f"I can count to {item}!")
|
||||
|
||||
for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
for item in {1,}:
|
||||
print(f"I can count to {item}!")
|
||||
|
||||
for item in {
|
||||
"apples", "lemons", "water"
|
||||
}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Errors
|
||||
1 in [1, 2, 3]
|
||||
1 in (1, 2, 3)
|
||||
1 in (
|
||||
1, 2, 3
|
||||
)
|
||||
|
||||
# OK
|
||||
fruits = ["cherry", "grapes"]
|
||||
"cherry" in fruits
|
||||
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# bare raise is an except block
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
if True:
|
||||
raise
|
||||
|
||||
# bare raise is in a finally block which is in an except block
|
||||
try:
|
||||
raise Exception
|
||||
except Exception:
|
||||
try:
|
||||
raise Exception
|
||||
finally:
|
||||
raise
|
||||
|
||||
# bare raise is in an __exit__ method
|
||||
class ContextManager:
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *args):
|
||||
raise
|
||||
|
||||
try:
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def f():
|
||||
try:
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def g():
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
def h():
|
||||
try:
|
||||
if True:
|
||||
def i():
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
def i():
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
class C:
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
raise # [misplaced-bare-raise]
|
||||
@@ -1,5 +1,7 @@
|
||||
import abc
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
class Person:
|
||||
def developer_greeting(self, name): # [no-self-use]
|
||||
@@ -60,3 +62,24 @@ class Prop:
|
||||
@property
|
||||
def count(self):
|
||||
return 24
|
||||
|
||||
|
||||
class A:
|
||||
def foo(self):
|
||||
...
|
||||
|
||||
|
||||
class B(A):
|
||||
@override
|
||||
def foo(self):
|
||||
...
|
||||
|
||||
def foobar(self):
|
||||
super()
|
||||
|
||||
def bar(self):
|
||||
super().foo()
|
||||
|
||||
def baz(self):
|
||||
if super().foo():
|
||||
...
|
||||
|
||||
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
if a:
|
||||
...
|
||||
elif (a and b):
|
||||
...
|
||||
elif (a and b) and c:
|
||||
...
|
||||
elif (a and b) and c and d:
|
||||
...
|
||||
elif (a and b) and c and d and e:
|
||||
...
|
||||
elif (a and b) and c and d and e and f:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
...
|
||||
else:
|
||||
...
|
||||
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import io
|
||||
import sys
|
||||
import tempfile
|
||||
import io as hugo
|
||||
import codecs
|
||||
|
||||
# Errors.
|
||||
open("test.txt")
|
||||
io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
tempfile.NamedTemporaryFile("w")
|
||||
tempfile.TemporaryFile("w")
|
||||
codecs.open("test.txt")
|
||||
tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
||||
# Non-errors.
|
||||
open("test.txt", encoding="utf-8")
|
||||
open("test.bin", "wb")
|
||||
open("test.bin", mode="wb")
|
||||
open("test.txt", "r", -1, "utf-8")
|
||||
open("test.txt", mode=sys.argv[1])
|
||||
|
||||
def func(*args, **kwargs):
|
||||
open(*args)
|
||||
open("text.txt", **kwargs)
|
||||
|
||||
io.TextIOWrapper(io.FileIO("test.txt"), encoding="utf-8")
|
||||
io.TextIOWrapper(io.FileIO("test.txt"), "utf-8")
|
||||
tempfile.TemporaryFile("w", encoding="utf-8")
|
||||
tempfile.TemporaryFile("w", -1, "utf-8")
|
||||
tempfile.TemporaryFile("wb")
|
||||
tempfile.TemporaryFile()
|
||||
tempfile.NamedTemporaryFile("w", encoding="utf-8")
|
||||
tempfile.NamedTemporaryFile("w", -1, "utf-8")
|
||||
tempfile.NamedTemporaryFile("wb")
|
||||
tempfile.NamedTemporaryFile()
|
||||
codecs.open("test.txt", encoding="utf-8")
|
||||
codecs.open("test.bin", "wb")
|
||||
codecs.open("test.bin", mode="wb")
|
||||
codecs.open("test.txt", "r", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", encoding="utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "wb")
|
||||
tempfile.SpooledTemporaryFile(0, )
|
||||
@@ -202,3 +202,8 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
"{}".format(
|
||||
1 # comment
|
||||
)
|
||||
|
||||
|
||||
# The fixed string will exceed the line length, but it's still smaller than the
|
||||
# existing line length, so it's fine.
|
||||
"<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
|
||||
9
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_3.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_3.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.utils.translation import gettext
|
||||
|
||||
long = 'long'
|
||||
split_to = 'split_to'
|
||||
gettext(
|
||||
'some super {} and complicated string so that the error code '
|
||||
'E501 Triggers when this is not {} multi-line'.format(
|
||||
long, split_to)
|
||||
)
|
||||
@@ -196,3 +196,9 @@ if sys.version_info < (3,10000000):
|
||||
|
||||
if sys.version_info <= (3,10000000):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info > (3,12):
|
||||
print("py3")
|
||||
|
||||
if sys.version_info >= (3,12):
|
||||
print("py3")
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import typing
|
||||
from typing import TypeAlias
|
||||
|
||||
# UP040
|
||||
# Fixes in type stub files should be safe to apply unlike in regular code where runtime behavior could change
|
||||
x: typing.TypeAlias = int
|
||||
x: TypeAlias = int
|
||||
45
crates/ruff_linter/resources/test/fixtures/refurb/FURB171.py
vendored
Normal file
45
crates/ruff_linter/resources/test/fixtures/refurb/FURB171.py
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Errors.
|
||||
|
||||
if 1 in (1,):
|
||||
print("Single-element tuple")
|
||||
|
||||
if 1 in [1]:
|
||||
print("Single-element list")
|
||||
|
||||
if 1 in {1}:
|
||||
print("Single-element set")
|
||||
|
||||
if "a" in "a":
|
||||
print("Single-element string")
|
||||
|
||||
if 1 not in (1,):
|
||||
print("Check `not in` membership test")
|
||||
|
||||
if not 1 in (1,):
|
||||
print("Check the negated membership test")
|
||||
|
||||
# Non-errors.
|
||||
|
||||
if 1 in (1, 2):
|
||||
pass
|
||||
|
||||
if 1 in [1, 2]:
|
||||
pass
|
||||
|
||||
if 1 in {1, 2}:
|
||||
pass
|
||||
|
||||
if "a" in "ab":
|
||||
pass
|
||||
|
||||
if 1 == (1,):
|
||||
pass
|
||||
|
||||
if 1 > [1]:
|
||||
pass
|
||||
|
||||
if 1 is {1}:
|
||||
pass
|
||||
|
||||
if "a" == "a":
|
||||
pass
|
||||
@@ -38,6 +38,13 @@ y = range(10)
|
||||
list(range(10))[0]
|
||||
list(x.y)[0]
|
||||
list(x["y"])[0]
|
||||
[*range(10)][0]
|
||||
[*x["y"]][0]
|
||||
[*x.y][0]
|
||||
[* x.y][0]
|
||||
[
|
||||
*x.y
|
||||
][0]
|
||||
|
||||
# RUF015 (multi-line)
|
||||
revision_heads_map_ast = [
|
||||
@@ -45,3 +52,12 @@ revision_heads_map_ast = [
|
||||
for a in revision_heads_map_ast_obj.body
|
||||
if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP"
|
||||
][0]
|
||||
|
||||
# RUF015 (zip)
|
||||
list(zip(x, y))[0]
|
||||
[*zip(x, y)][0]
|
||||
|
||||
|
||||
def test():
|
||||
zip = list # Overwrite the builtin zip
|
||||
list(zip(x, y))[0]
|
||||
|
||||
7
crates/ruff_linter/resources/test/fixtures/ruff/RUF018.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/ruff/RUF018.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# RUF018
|
||||
assert (x := 0) == 0
|
||||
assert x, (y := "error")
|
||||
|
||||
# OK
|
||||
if z := 0:
|
||||
pass
|
||||
20
crates/ruff_linter/resources/test/fixtures/ruff/RUF019.py
vendored
Normal file
20
crates/ruff_linter/resources/test/fixtures/ruff/RUF019.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
d = {}
|
||||
# RUF019
|
||||
if "k" in d and d["k"]:
|
||||
pass
|
||||
|
||||
k = "k"
|
||||
if k in d and d[k]:
|
||||
pass
|
||||
|
||||
if (k) in d and d[k]:
|
||||
pass
|
||||
|
||||
if k in d and d[(k)]:
|
||||
pass
|
||||
|
||||
# OK
|
||||
v = "k" in d and d["k"]
|
||||
|
||||
if f() in d and d[f()]:
|
||||
pass
|
||||
@@ -32,15 +32,10 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
},
|
||||
binding.range(),
|
||||
);
|
||||
if checker.patch(Rule::UnusedVariable) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
pyflakes::fixes::remove_exception_handler_assignment(
|
||||
binding,
|
||||
checker.locator,
|
||||
)
|
||||
.map(Fix::automatic)
|
||||
});
|
||||
}
|
||||
diagnostic.try_set_fix(|| {
|
||||
pyflakes::fixes::remove_exception_handler_assignment(binding, checker.locator)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
|
||||
if scope.kind.is_function() {
|
||||
if checker.enabled(Rule::NoSelfUse) {
|
||||
pylint::rules::no_self_use(checker, scope, &mut diagnostics);
|
||||
pylint::rules::no_self_use(checker, scope_id, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,13 +414,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyupgrade::rules::format_literals(checker, call, &summary);
|
||||
}
|
||||
if checker.enabled(Rule::FString) {
|
||||
pyupgrade::rules::f_strings(
|
||||
checker,
|
||||
call,
|
||||
&summary,
|
||||
value,
|
||||
checker.settings.line_length,
|
||||
);
|
||||
pyupgrade::rules::f_strings(checker, call, &summary, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -792,6 +786,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
|
||||
pylint::rules::subprocess_run_without_check(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnspecifiedEncoding) {
|
||||
pylint::rules::unspecified_encoding(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::PytestRaisesWithoutException,
|
||||
Rule::PytestRaisesTooBroad,
|
||||
@@ -1200,6 +1197,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ComparisonWithItself) {
|
||||
pylint::rules::comparison_with_itself(checker, left, ops, comparators);
|
||||
}
|
||||
if checker.enabled(Rule::LiteralMembership) {
|
||||
pylint::rules::literal_membership(checker, compare);
|
||||
}
|
||||
if checker.enabled(Rule::ComparisonOfConstant) {
|
||||
pylint::rules::comparison_of_constant(checker, left, ops, comparators);
|
||||
}
|
||||
@@ -1224,6 +1224,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::SingleItemMembershipTest) {
|
||||
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
||||
@@ -1415,6 +1418,17 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::RepeatedEqualityComparison) {
|
||||
pylint::rules::repeated_equality_comparison(checker, bool_op);
|
||||
}
|
||||
if checker.enabled(Rule::AndOrTernary) {
|
||||
pylint::rules::and_or_ternary(checker, bool_op);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryKeyCheck) {
|
||||
ruff::rules::unnecessary_key_check(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::NamedExpr(..) => {
|
||||
if checker.enabled(Rule::AssignmentInAssert) {
|
||||
ruff::rules::assignment_in_assert(checker, expr);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
@@ -964,7 +964,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Raise(ast::StmtRaise { exc, .. }) => {
|
||||
Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => {
|
||||
if checker.enabled(Rule::RaiseNotImplemented) {
|
||||
if let Some(expr) = exc {
|
||||
pyflakes::rules::raise_not_implemented(checker, expr);
|
||||
@@ -1004,6 +1004,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_raise::rules::unnecessary_paren_on_raise_exception(checker, expr);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MisplacedBareRaise) {
|
||||
pylint::rules::misplaced_bare_raise(checker, raise);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
@@ -1067,6 +1070,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CheckAndRemoveFromSet) {
|
||||
refurb::rules::check_and_remove_from_set(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyBooleanExpressions) {
|
||||
pylint::rules::too_many_boolean_expressions(checker, if_);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{Expr, TypeParam};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::{ScopeId, Snapshot};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -10,7 +10,7 @@ pub(crate) struct Deferred<'a> {
|
||||
pub(crate) scopes: Vec<ScopeId>,
|
||||
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
||||
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) type_param_definitions: Vec<(&'a TypeParam, Snapshot)>,
|
||||
pub(crate) type_param_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) for_loops: Vec<Snapshot>,
|
||||
|
||||
@@ -143,11 +143,6 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
/// Return `true` if a patch should be generated for a given [`Rule`].
|
||||
pub(crate) fn patch(&self, code: Rule) -> bool {
|
||||
self.settings.rules.should_fix(code)
|
||||
}
|
||||
|
||||
/// Return `true` if a [`Rule`] is disabled by a `noqa` directive.
|
||||
pub(crate) fn rule_is_ignored(&self, code: Rule, offset: TextSize) -> bool {
|
||||
// TODO(charlie): `noqa` directives are mostly enforced in `check_lines.rs`.
|
||||
@@ -529,6 +524,7 @@ where
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
self.semantic.push_scope(ScopeKind::Function(function_def));
|
||||
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
|
||||
@@ -567,6 +563,7 @@ where
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
self.semantic.push_scope(ScopeKind::Class(class_def));
|
||||
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||
|
||||
// Extract any global bindings from the class body.
|
||||
if let Some(globals) = Globals::from_body(body) {
|
||||
@@ -585,7 +582,9 @@ where
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
self.visit_expr(value);
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((value, self.semantic.snapshot()));
|
||||
self.semantic.pop_scope();
|
||||
self.visit_expr(name);
|
||||
}
|
||||
@@ -1168,13 +1167,14 @@ where
|
||||
range: _,
|
||||
}) = slice.as_ref()
|
||||
{
|
||||
if let Some(expr) = elts.first() {
|
||||
let mut iter = elts.iter();
|
||||
if let Some(expr) = iter.next() {
|
||||
self.visit_expr(expr);
|
||||
for expr in elts.iter().skip(1) {
|
||||
self.visit_non_type_definition(expr);
|
||||
}
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
for expr in iter {
|
||||
self.visit_non_type_definition(expr);
|
||||
}
|
||||
self.visit_expr_context(ctx);
|
||||
} else {
|
||||
debug!("Found non-Expr::Tuple argument to PEP 593 Annotation.");
|
||||
}
|
||||
@@ -1366,7 +1366,7 @@ where
|
||||
fn visit_match_case(&mut self, match_case: &'b MatchCase) {
|
||||
self.visit_pattern(&match_case.pattern);
|
||||
if let Some(expr) = &match_case.guard {
|
||||
self.visit_expr(expr);
|
||||
self.visit_boolean_test(expr);
|
||||
}
|
||||
|
||||
self.semantic.push_branch();
|
||||
@@ -1389,9 +1389,14 @@ where
|
||||
}
|
||||
}
|
||||
// Step 2: Traversal
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((type_param, self.semantic.snapshot()));
|
||||
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
|
||||
bound: Some(bound), ..
|
||||
}) = type_param
|
||||
{
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((bound, self.semantic.snapshot()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1618,10 +1623,12 @@ impl<'a> Checker<'a> {
|
||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||
let parent = self.semantic.current_statement();
|
||||
|
||||
// Match the left-hand side of an annotated assignment, like `x` in `x: int`.
|
||||
if matches!(
|
||||
parent,
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
||||
) {
|
||||
) && !self.semantic.in_annotation()
|
||||
{
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
@@ -1764,12 +1771,9 @@ impl<'a> Checker<'a> {
|
||||
for (type_param, snapshot) in type_params {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
|
||||
bound: Some(bound), ..
|
||||
}) = type_param
|
||||
{
|
||||
self.visit_expr(bound);
|
||||
}
|
||||
self.semantic.flags |=
|
||||
SemanticModelFlags::TYPE_PARAM_DEFINITION | SemanticModelFlags::TYPE_DEFINITION;
|
||||
self.visit_expr(type_param);
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_parser::TokenKind;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
||||
missing_whitespace_around_operator, space_after_comma, space_around_operator,
|
||||
@@ -38,17 +38,6 @@ pub(crate) fn check_logical_lines(
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut context = LogicalLinesContext::new(settings);
|
||||
|
||||
let should_fix_missing_whitespace = settings.rules.should_fix(Rule::MissingWhitespace);
|
||||
let should_fix_whitespace_before_parameters =
|
||||
settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
|
||||
let should_fix_whitespace_after_open_bracket =
|
||||
settings.rules.should_fix(Rule::WhitespaceAfterOpenBracket);
|
||||
let should_fix_whitespace_before_close_bracket = settings
|
||||
.rules
|
||||
.should_fix(Rule::WhitespaceBeforeCloseBracket);
|
||||
let should_fix_whitespace_before_punctuation =
|
||||
settings.rules.should_fix(Rule::WhitespaceBeforePunctuation);
|
||||
|
||||
let mut prev_line = None;
|
||||
let mut prev_indent_level = None;
|
||||
let indent_char = stylist.indentation().as_char();
|
||||
@@ -58,7 +47,7 @@ pub(crate) fn check_logical_lines(
|
||||
space_around_operator(&line, &mut context);
|
||||
whitespace_around_named_parameter_equals(&line, &mut context);
|
||||
missing_whitespace_around_operator(&line, &mut context);
|
||||
missing_whitespace(&line, should_fix_missing_whitespace, &mut context);
|
||||
missing_whitespace(&line, &mut context);
|
||||
}
|
||||
if line.flags().contains(TokenFlags::PUNCTUATION) {
|
||||
space_after_comma(&line, &mut context);
|
||||
@@ -68,13 +57,7 @@ pub(crate) fn check_logical_lines(
|
||||
.flags()
|
||||
.intersects(TokenFlags::OPERATOR | TokenFlags::BRACKET | TokenFlags::PUNCTUATION)
|
||||
{
|
||||
extraneous_whitespace(
|
||||
&line,
|
||||
&mut context,
|
||||
should_fix_whitespace_after_open_bracket,
|
||||
should_fix_whitespace_before_close_bracket,
|
||||
should_fix_whitespace_before_punctuation,
|
||||
);
|
||||
extraneous_whitespace(&line, &mut context);
|
||||
}
|
||||
|
||||
if line.flags().contains(TokenFlags::KEYWORD) {
|
||||
@@ -87,11 +70,7 @@ pub(crate) fn check_logical_lines(
|
||||
}
|
||||
|
||||
if line.flags().contains(TokenFlags::BRACKET) {
|
||||
whitespace_before_parameters(
|
||||
&line,
|
||||
should_fix_whitespace_before_parameters,
|
||||
&mut context,
|
||||
);
|
||||
whitespace_before_parameters(&line, &mut context);
|
||||
}
|
||||
|
||||
// Extract the indentation level.
|
||||
|
||||
@@ -109,10 +109,8 @@ pub(crate) fn check_noqa(
|
||||
if line.matches.is_empty() {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(UnusedNOQA { codes: None }, directive.range());
|
||||
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
diagnostic
|
||||
.set_fix(Fix::suggested(delete_noqa(directive.range(), locator)));
|
||||
}
|
||||
diagnostic.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -173,18 +171,14 @@ pub(crate) fn check_noqa(
|
||||
},
|
||||
directive.range(),
|
||||
);
|
||||
if settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
if valid_codes.is_empty() {
|
||||
diagnostic.set_fix(Fix::suggested(delete_noqa(
|
||||
directive.range(),
|
||||
locator,
|
||||
)));
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
format!("# noqa: {}", valid_codes.join(", ")),
|
||||
directive.range(),
|
||||
)));
|
||||
}
|
||||
if valid_codes.is_empty() {
|
||||
diagnostic
|
||||
.set_fix(Fix::safe_edit(delete_noqa(directive.range(), locator)));
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("# noqa: {}", valid_codes.join(", ")),
|
||||
directive.range(),
|
||||
)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -71,11 +71,7 @@ pub(crate) fn check_physical_lines(
|
||||
}
|
||||
|
||||
if enforce_no_newline_at_end_of_file {
|
||||
if let Some(diagnostic) = no_newline_at_end_of_file(
|
||||
locator,
|
||||
stylist,
|
||||
settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
|
||||
) {
|
||||
if let Some(diagnostic) = no_newline_at_end_of_file(locator, stylist) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ pub(crate) fn check_tokens(
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::UTF8EncodingDeclaration) {
|
||||
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer, settings);
|
||||
pyupgrade::rules::unnecessary_coding_comment(&mut diagnostics, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::InvalidEscapeSequence) {
|
||||
@@ -83,7 +83,6 @@ pub(crate) fn check_tokens(
|
||||
indexer,
|
||||
tok,
|
||||
*range,
|
||||
settings.rules.should_fix(Rule::InvalidEscapeSequence),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -109,13 +108,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::MultipleStatementsOnOneLineSemicolon,
|
||||
Rule::UselessSemicolon,
|
||||
]) {
|
||||
pycodestyle::rules::compound_statements(
|
||||
&mut diagnostics,
|
||||
tokens,
|
||||
locator,
|
||||
indexer,
|
||||
settings,
|
||||
);
|
||||
pycodestyle::rules::compound_statements(&mut diagnostics, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::AvoidableEscapedQuote) && settings.flake8_quotes.avoid_escape {
|
||||
@@ -137,7 +130,7 @@ pub(crate) fn check_tokens(
|
||||
flake8_implicit_str_concat::rules::implicit(
|
||||
&mut diagnostics,
|
||||
tokens,
|
||||
&settings.flake8_implicit_str_concat,
|
||||
settings,
|
||||
locator,
|
||||
indexer,
|
||||
);
|
||||
@@ -148,11 +141,11 @@ pub(crate) fn check_tokens(
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
]) {
|
||||
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, settings);
|
||||
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::ExtraneousParentheses) {
|
||||
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator, settings);
|
||||
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator);
|
||||
}
|
||||
|
||||
if is_stub && settings.rules.enabled(Rule::TypeCommentInStub) {
|
||||
@@ -166,7 +159,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::ShebangNotFirstLine,
|
||||
Rule::ShebangMissingPython,
|
||||
]) {
|
||||
flake8_executable::rules::from_tokens(tokens, path, locator, settings, &mut diagnostics);
|
||||
flake8_executable::rules::from_tokens(tokens, path, locator, &mut diagnostics);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
@@ -191,7 +184,7 @@ pub(crate) fn check_tokens(
|
||||
TodoComment::from_comment(comment, *comment_range, i)
|
||||
})
|
||||
.collect();
|
||||
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer, settings);
|
||||
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer);
|
||||
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,16 +3,18 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::{
|
||||
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
|
||||
};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline, UniversalNewlines};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::fix::codemods;
|
||||
use crate::line_width::{LineLength, LineWidthBuilder, TabSize};
|
||||
|
||||
/// Return the `Fix` to use when deleting a `Stmt`.
|
||||
///
|
||||
@@ -285,6 +287,75 @@ pub(crate) fn pad(content: String, range: TextRange, locator: &Locator) -> Strin
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the fix fits within the maximum configured line length.
|
||||
pub(crate) fn fits(
|
||||
fix: &str,
|
||||
node: AnyNodeRef,
|
||||
locator: &Locator,
|
||||
line_length: LineLength,
|
||||
tab_size: TabSize,
|
||||
) -> bool {
|
||||
all_lines_fit(fix, node, locator, line_length.value() as usize, tab_size)
|
||||
}
|
||||
|
||||
/// Returns `true` if the fix fits within the maximum configured line length, or produces lines that
|
||||
/// are shorter than the maximum length of the existing AST node.
|
||||
pub(crate) fn fits_or_shrinks(
|
||||
fix: &str,
|
||||
node: AnyNodeRef,
|
||||
locator: &Locator,
|
||||
line_length: LineLength,
|
||||
tab_size: TabSize,
|
||||
) -> bool {
|
||||
// Use the larger of the line length limit, or the longest line in the existing AST node.
|
||||
let line_length = std::iter::once(line_length.value() as usize)
|
||||
.chain(
|
||||
locator
|
||||
.slice(locator.lines_range(node.range()))
|
||||
.universal_newlines()
|
||||
.map(|line| LineWidthBuilder::new(tab_size).add_str(&line).get()),
|
||||
)
|
||||
.max()
|
||||
.unwrap_or(line_length.value() as usize);
|
||||
|
||||
all_lines_fit(fix, node, locator, line_length, tab_size)
|
||||
}
|
||||
|
||||
/// Returns `true` if all lines in the fix are shorter than the given line length.
|
||||
fn all_lines_fit(
|
||||
fix: &str,
|
||||
node: AnyNodeRef,
|
||||
locator: &Locator,
|
||||
line_length: usize,
|
||||
tab_size: TabSize,
|
||||
) -> bool {
|
||||
let prefix = locator.slice(TextRange::new(
|
||||
locator.line_start(node.start()),
|
||||
node.start(),
|
||||
));
|
||||
|
||||
// Ensure that all lines are shorter than the line length limit.
|
||||
fix.universal_newlines().enumerate().all(|(idx, line)| {
|
||||
// If `template` is a multiline string, `col_offset` should only be applied to the first
|
||||
// line:
|
||||
// ```
|
||||
// a = """{} -> offset = col_offset (= 4)
|
||||
// {} -> offset = 0
|
||||
// """.format(0, 1) -> offset = 0
|
||||
// ```
|
||||
let measured_length = if idx == 0 {
|
||||
LineWidthBuilder::new(tab_size)
|
||||
.add_str(prefix)
|
||||
.add_str(&line)
|
||||
.get()
|
||||
} else {
|
||||
LineWidthBuilder::new(tab_size).add_str(&line).get()
|
||||
};
|
||||
|
||||
measured_length <= line_length
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -9,6 +9,7 @@ use ruff_source_file::Locator;
|
||||
|
||||
use crate::linter::FixTable;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
pub(crate) mod codemods;
|
||||
pub(crate) mod edits;
|
||||
@@ -23,11 +24,22 @@ pub(crate) struct FixResult {
|
||||
pub(crate) source_map: SourceMap,
|
||||
}
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub(crate) fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<FixResult> {
|
||||
/// Fix errors in a file, and write the fixed source code to disk.
|
||||
pub(crate) fn fix_file(
|
||||
diagnostics: &[Diagnostic],
|
||||
locator: &Locator,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
) -> Option<FixResult> {
|
||||
let required_applicability = unsafe_fixes.required_applicability();
|
||||
|
||||
let mut with_fixes = diagnostics
|
||||
.iter()
|
||||
.filter(|diag| diag.fix.is_some())
|
||||
.filter(|diagnostic| {
|
||||
diagnostic
|
||||
.fix
|
||||
.as_ref()
|
||||
.map_or(false, |fix| fix.applies(required_applicability))
|
||||
})
|
||||
.peekable();
|
||||
|
||||
if with_fixes.peek().is_none() {
|
||||
@@ -151,7 +163,7 @@ mod tests {
|
||||
// The choice of rule here is arbitrary.
|
||||
kind: MissingNewlineAtEndOfFile.into(),
|
||||
range: edit.range(),
|
||||
fix: Some(Fix::unspecified(edit)),
|
||||
fix: Some(Fix::safe_edit(edit)),
|
||||
parent: None,
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::hash::Hasher;
|
||||
use std::num::{NonZeroU16, NonZeroU8, ParseIntError};
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use ruff_cache::{CacheKey, CacheKeyHasher};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
/// The length of a line of text that is considered too long.
|
||||
///
|
||||
/// The allowed range of values is 1..=320
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct LineLength(NonZeroU16);
|
||||
pub struct LineLength(
|
||||
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))] NonZeroU16,
|
||||
);
|
||||
|
||||
impl LineLength {
|
||||
/// Maximum allowed value for a valid [`LineLength`]
|
||||
@@ -23,6 +27,10 @@ impl LineLength {
|
||||
pub fn value(&self) -> u16 {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub fn text_len(&self) -> TextSize {
|
||||
TextSize::from(u32::from(self.value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LineLength {
|
||||
@@ -245,3 +253,9 @@ impl From<NonZeroU8> for TabSize {
|
||||
Self(tab_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TabSize> for NonZeroU8 {
|
||||
fn from(value: TabSize) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::{Applicability, Diagnostic};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
@@ -32,6 +32,7 @@ use crate::message::Message;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::settings::{flags, LinterSettings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::{directives, fs};
|
||||
@@ -259,6 +260,36 @@ pub fn check_path(
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fixes for any rules marked as unfixable.
|
||||
for diagnostic in &mut diagnostics {
|
||||
if !settings.rules.should_fix(diagnostic.kind.rule()) {
|
||||
diagnostic.fix = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Update fix applicability to account for overrides
|
||||
if !settings.extend_safe_fixes.is_empty() || !settings.extend_unsafe_fixes.is_empty() {
|
||||
for diagnostic in &mut diagnostics {
|
||||
if let Some(fix) = diagnostic.fix.take() {
|
||||
// Enforce demotions over promotions so if someone puts a rule in both we are conservative
|
||||
if fix.applicability().is_safe()
|
||||
&& settings
|
||||
.extend_unsafe_fixes
|
||||
.contains(diagnostic.kind.rule())
|
||||
{
|
||||
diagnostic.set_fix(fix.with_applicability(Applicability::Unsafe));
|
||||
} else if fix.applicability().is_unsafe()
|
||||
&& settings.extend_safe_fixes.contains(diagnostic.kind.rule())
|
||||
{
|
||||
diagnostic.set_fix(fix.with_applicability(Applicability::Safe));
|
||||
} else {
|
||||
// Retain the existing fix (will be dropped from `.take()` otherwise)
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinterResult::new((diagnostics, imports), error)
|
||||
}
|
||||
|
||||
@@ -415,10 +446,12 @@ fn diagnostics_to_messages(
|
||||
|
||||
/// Generate `Diagnostic`s from source code content, iteratively fixing
|
||||
/// until stable.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn lint_fix<'a>(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
noqa: flags::Noqa,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
settings: &LinterSettings,
|
||||
source_kind: &'a SourceKind,
|
||||
source_type: PySourceType,
|
||||
@@ -494,7 +527,7 @@ pub fn lint_fix<'a>(
|
||||
code: fixed_contents,
|
||||
fixes: applied,
|
||||
source_map,
|
||||
}) = fix_file(&result.data.0, &locator)
|
||||
}) = fix_file(&result.data.0, &locator, unsafe_fixes)
|
||||
{
|
||||
if iterations < MAX_ITERATIONS {
|
||||
// Count the number of fixed errors.
|
||||
|
||||
@@ -177,18 +177,15 @@ impl Display for DisplayParseError<'_> {
|
||||
f,
|
||||
"cell {cell}{colon}",
|
||||
cell = jupyter_index
|
||||
.cell(source_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
.cell(source_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
colon = ":".cyan(),
|
||||
)?;
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
jupyter_index
|
||||
.cell_row(source_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
row: jupyter_index
|
||||
.cell_row(source_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
column: source_location.column,
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -35,6 +35,7 @@ impl<'a> Diff<'a> {
|
||||
|
||||
impl Display for Diff<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
|
||||
let mut output = String::with_capacity(self.source_code.source_text().len());
|
||||
let mut last_end = TextSize::default();
|
||||
|
||||
@@ -52,10 +53,10 @@ impl Display for Diff<'_> {
|
||||
let diff = TextDiff::from_lines(self.source_code.source_text(), &output);
|
||||
|
||||
let message = match self.fix.applicability() {
|
||||
Applicability::Automatic => "Fix",
|
||||
Applicability::Suggested => "Suggested fix",
|
||||
Applicability::Manual => "Possible fix",
|
||||
Applicability::Unspecified => "Suggested fix", /* For backwards compatibility, unspecified fixes are 'suggested' */
|
||||
// TODO(zanieb): Adjust this messaging once it's user-facing
|
||||
Applicability::Safe => "Fix",
|
||||
Applicability::Unsafe => "Suggested fix",
|
||||
Applicability::Display => "Possible fix",
|
||||
};
|
||||
writeln!(f, "ℹ {}", message.blue())?;
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
|
||||
use crate::message::{
|
||||
group_messages_by_filename, Emitter, EmitterContext, Message, MessageWithLocation,
|
||||
};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GroupedEmitter {
|
||||
show_fix_status: bool,
|
||||
show_source: bool,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
}
|
||||
|
||||
impl GroupedEmitter {
|
||||
@@ -32,6 +34,12 @@ impl GroupedEmitter {
|
||||
self.show_source = show_source;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
|
||||
self.unsafe_fixes = unsafe_fixes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for GroupedEmitter {
|
||||
@@ -68,6 +76,7 @@ impl Emitter for GroupedEmitter {
|
||||
notebook_index: context.notebook_index(message.filename()),
|
||||
message,
|
||||
show_fix_status: self.show_fix_status,
|
||||
unsafe_fixes: self.unsafe_fixes,
|
||||
show_source: self.show_source,
|
||||
row_length,
|
||||
column_length,
|
||||
@@ -89,6 +98,7 @@ impl Emitter for GroupedEmitter {
|
||||
struct DisplayGroupedMessage<'a> {
|
||||
message: MessageWithLocation<'a>,
|
||||
show_fix_status: bool,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
show_source: bool,
|
||||
row_length: NonZeroUsize,
|
||||
column_length: NonZeroUsize,
|
||||
@@ -115,18 +125,18 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
f,
|
||||
"cell {cell}{sep}",
|
||||
cell = jupyter_index
|
||||
.cell(start_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
.cell(start_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
sep = ":".cyan()
|
||||
)?;
|
||||
(
|
||||
jupyter_index
|
||||
.cell_row(start_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
start_location.column.get(),
|
||||
.cell_row(start_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
start_location.column,
|
||||
)
|
||||
} else {
|
||||
(start_location.row.get(), start_location.column.get())
|
||||
(start_location.row, start_location.column)
|
||||
};
|
||||
|
||||
writeln!(
|
||||
@@ -138,7 +148,8 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
),
|
||||
code_and_body = RuleCodeAndBody {
|
||||
message,
|
||||
show_fix_status: self.show_fix_status
|
||||
show_fix_status: self.show_fix_status,
|
||||
unsafe_fixes: self.unsafe_fixes
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -196,6 +207,7 @@ mod tests {
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::GroupedEmitter;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
@@ -222,4 +234,15 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_status_unsafe() {
|
||||
let mut emitter = GroupedEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled);
|
||||
let content = capture_emitter_output(&mut emitter, &create_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ use serde::{Serialize, Serializer};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_source_file::SourceCode;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
@@ -19,9 +20,9 @@ impl Emitter for JsonEmitter {
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
messages: &[Message],
|
||||
_context: &EmitterContext,
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
serde_json::to_writer_pretty(writer, &ExpandedMessages { messages })?;
|
||||
serde_json::to_writer_pretty(writer, &ExpandedMessages { messages, context })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -29,6 +30,7 @@ impl Emitter for JsonEmitter {
|
||||
|
||||
struct ExpandedMessages<'a> {
|
||||
messages: &'a [Message],
|
||||
context: &'a EmitterContext<'a>,
|
||||
}
|
||||
|
||||
impl Serialize for ExpandedMessages<'_> {
|
||||
@@ -39,7 +41,7 @@ impl Serialize for ExpandedMessages<'_> {
|
||||
let mut s = serializer.serialize_seq(Some(self.messages.len()))?;
|
||||
|
||||
for message in self.messages {
|
||||
let value = message_to_json_value(message);
|
||||
let value = message_to_json_value(message, self.context);
|
||||
s.serialize_element(&value)?;
|
||||
}
|
||||
|
||||
@@ -47,26 +49,40 @@ impl Serialize for ExpandedMessages<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn message_to_json_value(message: &Message) -> Value {
|
||||
pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext) -> Value {
|
||||
let source_code = message.file.to_source_code();
|
||||
let notebook_index = context.notebook_index(message.filename());
|
||||
|
||||
let fix = message.fix.as_ref().map(|fix| {
|
||||
json!({
|
||||
"applicability": fix.applicability(),
|
||||
"message": message.kind.suggestion.as_deref(),
|
||||
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code },
|
||||
"edits": &ExpandedEdits { edits: fix.edits(), source_code: &source_code, notebook_index },
|
||||
})
|
||||
});
|
||||
|
||||
let start_location = source_code.source_location(message.start());
|
||||
let end_location = source_code.source_location(message.end());
|
||||
let noqa_location = source_code.source_location(message.noqa_offset);
|
||||
let mut start_location = source_code.source_location(message.start());
|
||||
let mut end_location = source_code.source_location(message.end());
|
||||
let mut noqa_location = source_code.source_location(message.noqa_offset);
|
||||
let mut notebook_cell_index = None;
|
||||
|
||||
if let Some(notebook_index) = notebook_index {
|
||||
notebook_cell_index = Some(
|
||||
notebook_index
|
||||
.cell(start_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
);
|
||||
start_location = notebook_index.translate_location(&start_location);
|
||||
end_location = notebook_index.translate_location(&end_location);
|
||||
noqa_location = notebook_index.translate_location(&noqa_location);
|
||||
}
|
||||
|
||||
json!({
|
||||
"code": message.kind.rule().noqa_code().to_string(),
|
||||
"url": message.kind.rule().url(),
|
||||
"message": message.kind.body,
|
||||
"fix": fix,
|
||||
"cell": notebook_cell_index,
|
||||
"location": start_location,
|
||||
"end_location": end_location,
|
||||
"filename": message.filename(),
|
||||
@@ -77,6 +93,7 @@ pub(crate) fn message_to_json_value(message: &Message) -> Value {
|
||||
struct ExpandedEdits<'a> {
|
||||
edits: &'a [Edit],
|
||||
source_code: &'a SourceCode<'a, 'a>,
|
||||
notebook_index: Option<&'a NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Serialize for ExpandedEdits<'_> {
|
||||
@@ -87,10 +104,57 @@ impl Serialize for ExpandedEdits<'_> {
|
||||
let mut s = serializer.serialize_seq(Some(self.edits.len()))?;
|
||||
|
||||
for edit in self.edits {
|
||||
let mut location = self.source_code.source_location(edit.start());
|
||||
let mut end_location = self.source_code.source_location(edit.end());
|
||||
|
||||
if let Some(notebook_index) = self.notebook_index {
|
||||
// There exists a newline between each cell's source code in the
|
||||
// concatenated source code in Ruff. This newline doesn't actually
|
||||
// exists in the JSON source field.
|
||||
//
|
||||
// Now, certain edits may try to remove this newline, which means
|
||||
// the edit will spill over to the first character of the next cell.
|
||||
// If it does, we need to translate the end location to the last
|
||||
// character of the previous cell.
|
||||
match (
|
||||
notebook_index.cell(location.row),
|
||||
notebook_index.cell(end_location.row),
|
||||
) {
|
||||
(Some(start_cell), Some(end_cell)) if start_cell != end_cell => {
|
||||
debug_assert_eq!(end_location.column.get(), 1);
|
||||
|
||||
let prev_row = end_location.row.saturating_sub(1);
|
||||
end_location = SourceLocation {
|
||||
row: notebook_index.cell_row(prev_row).unwrap_or(OneIndexed::MIN),
|
||||
column: self
|
||||
.source_code
|
||||
.source_location(self.source_code.line_end_exclusive(prev_row))
|
||||
.column,
|
||||
};
|
||||
}
|
||||
(Some(_), None) => {
|
||||
debug_assert_eq!(end_location.column.get(), 1);
|
||||
|
||||
let prev_row = end_location.row.saturating_sub(1);
|
||||
end_location = SourceLocation {
|
||||
row: notebook_index.cell_row(prev_row).unwrap_or(OneIndexed::MIN),
|
||||
column: self
|
||||
.source_code
|
||||
.source_location(self.source_code.line_end_exclusive(prev_row))
|
||||
.column,
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
end_location = notebook_index.translate_location(&end_location);
|
||||
}
|
||||
}
|
||||
location = notebook_index.translate_location(&location);
|
||||
}
|
||||
|
||||
let value = json!({
|
||||
"content": edit.content().unwrap_or_default(),
|
||||
"location": self.source_code.source_location(edit.start()),
|
||||
"end_location": self.source_code.source_location(edit.end())
|
||||
"location": location,
|
||||
"end_location": end_location
|
||||
});
|
||||
|
||||
s.serialize_element(&value)?;
|
||||
@@ -104,7 +168,10 @@ impl Serialize for ExpandedEdits<'_> {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_messages,
|
||||
create_notebook_messages,
|
||||
};
|
||||
use crate::message::JsonEmitter;
|
||||
|
||||
#[test]
|
||||
@@ -114,4 +181,13 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let mut emitter = JsonEmitter;
|
||||
let (messages, notebook_indexes) = create_notebook_messages();
|
||||
let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes);
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ impl Emitter for JsonLinesEmitter {
|
||||
&mut self,
|
||||
writer: &mut dyn Write,
|
||||
messages: &[Message],
|
||||
_context: &EmitterContext,
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut w = writer;
|
||||
for message in messages {
|
||||
serde_json::to_writer(&mut w, &message_to_json_value(message))?;
|
||||
serde_json::to_writer(&mut w, &message_to_json_value(message, context))?;
|
||||
w.write_all(b"\n")?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -27,7 +27,10 @@ mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::json_lines::JsonLinesEmitter;
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_messages,
|
||||
create_notebook_messages,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn output() {
|
||||
@@ -36,4 +39,13 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let mut emitter = JsonLinesEmitter;
|
||||
let (messages, notebook_indexes) = create_notebook_messages();
|
||||
let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes);
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,8 @@ mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{OneIndexed, SourceFileBuilder};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
@@ -178,7 +179,7 @@ def fibonacci(n):
|
||||
},
|
||||
TextRange::new(TextSize::from(7), TextSize::from(9)),
|
||||
)
|
||||
.with_fix(Fix::suggested(Edit::range_deletion(TextRange::new(
|
||||
.with_fix(Fix::unsafe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(0),
|
||||
TextSize::from(10),
|
||||
))));
|
||||
@@ -193,7 +194,7 @@ def fibonacci(n):
|
||||
},
|
||||
TextRange::new(TextSize::from(94), TextSize::from(95)),
|
||||
)
|
||||
.with_fix(Fix::suggested(Edit::deletion(
|
||||
.with_fix(Fix::unsafe_edit(Edit::deletion(
|
||||
TextSize::from(94),
|
||||
TextSize::from(99),
|
||||
)));
|
||||
@@ -221,6 +222,113 @@ def fibonacci(n):
|
||||
]
|
||||
}
|
||||
|
||||
pub(super) fn create_notebook_messages() -> (Vec<Message>, FxHashMap<String, NotebookIndex>) {
|
||||
let notebook = r"# cell 1
|
||||
import os
|
||||
# cell 2
|
||||
import math
|
||||
|
||||
print('hello world')
|
||||
# cell 3
|
||||
def foo():
|
||||
print()
|
||||
x = 1
|
||||
";
|
||||
|
||||
let unused_import_os = Diagnostic::new(
|
||||
DiagnosticKind {
|
||||
name: "UnusedImport".to_string(),
|
||||
body: "`os` imported but unused".to_string(),
|
||||
suggestion: Some("Remove unused import: `os`".to_string()),
|
||||
},
|
||||
TextRange::new(TextSize::from(16), TextSize::from(18)),
|
||||
)
|
||||
.with_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(9),
|
||||
TextSize::from(19),
|
||||
))));
|
||||
|
||||
let unused_import_math = Diagnostic::new(
|
||||
DiagnosticKind {
|
||||
name: "UnusedImport".to_string(),
|
||||
body: "`math` imported but unused".to_string(),
|
||||
suggestion: Some("Remove unused import: `math`".to_string()),
|
||||
},
|
||||
TextRange::new(TextSize::from(35), TextSize::from(39)),
|
||||
)
|
||||
.with_fix(Fix::safe_edit(Edit::range_deletion(TextRange::new(
|
||||
TextSize::from(28),
|
||||
TextSize::from(40),
|
||||
))));
|
||||
|
||||
let unused_variable = Diagnostic::new(
|
||||
DiagnosticKind {
|
||||
name: "UnusedVariable".to_string(),
|
||||
body: "Local variable `x` is assigned to but never used".to_string(),
|
||||
suggestion: Some("Remove assignment to unused variable `x`".to_string()),
|
||||
},
|
||||
TextRange::new(TextSize::from(98), TextSize::from(99)),
|
||||
)
|
||||
.with_fix(Fix::unsafe_edit(Edit::deletion(
|
||||
TextSize::from(94),
|
||||
TextSize::from(104),
|
||||
)));
|
||||
|
||||
let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish();
|
||||
|
||||
let mut notebook_indexes = FxHashMap::default();
|
||||
notebook_indexes.insert(
|
||||
"notebook.ipynb".to_string(),
|
||||
NotebookIndex::new(
|
||||
vec![
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
],
|
||||
vec![
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(3),
|
||||
OneIndexed::from_zero_indexed(0),
|
||||
OneIndexed::from_zero_indexed(1),
|
||||
OneIndexed::from_zero_indexed(2),
|
||||
OneIndexed::from_zero_indexed(3),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
let unused_import_os_start = unused_import_os.start();
|
||||
let unused_import_math_start = unused_import_math.start();
|
||||
let unused_variable_start = unused_variable.start();
|
||||
|
||||
(
|
||||
vec![
|
||||
Message::from_diagnostic(
|
||||
unused_import_os,
|
||||
notebook_source.clone(),
|
||||
unused_import_os_start,
|
||||
),
|
||||
Message::from_diagnostic(
|
||||
unused_import_math,
|
||||
notebook_source.clone(),
|
||||
unused_import_math_start,
|
||||
),
|
||||
Message::from_diagnostic(unused_variable, notebook_source, unused_variable_start),
|
||||
],
|
||||
notebook_indexes,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn capture_emitter_output(
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
@@ -232,4 +340,16 @@ def fibonacci(n):
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
}
|
||||
|
||||
pub(super) fn capture_emitter_notebook_output(
|
||||
emitter: &mut dyn Emitter,
|
||||
messages: &[Message],
|
||||
notebook_indexes: &FxHashMap<String, NotebookIndex>,
|
||||
) -> String {
|
||||
let context = EmitterContext::new(notebook_indexes);
|
||||
let mut output: Vec<u8> = Vec::new();
|
||||
emitter.emit(&mut output, messages, &context).unwrap();
|
||||
|
||||
String::from_utf8(output).expect("Output to be valid UTF-8")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ source: crates/ruff_linter/src/message/grouped.rs
|
||||
expression: content
|
||||
---
|
||||
fib.py:
|
||||
1:8 F401 [*] `os` imported but unused
|
||||
1:8 F401 `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
6:5 F841 [*] Local variable `x` is assigned to but never used
|
||||
6:5 F841 Local variable `x` is assigned to but never used
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/grouped.rs
|
||||
expression: content
|
||||
---
|
||||
fib.py:
|
||||
1:8 F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
6:5 F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^ F841
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
undef.py:
|
||||
1:4 F821 Undefined name `a`
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^ F821
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/json.rs
|
||||
expression: content
|
||||
---
|
||||
[
|
||||
{
|
||||
"cell": 1,
|
||||
"code": "F401",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "notebook.ipynb",
|
||||
"fix": {
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
"row": 2
|
||||
},
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"cell": 2,
|
||||
"code": "F401",
|
||||
"end_location": {
|
||||
"column": 12,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "notebook.ipynb",
|
||||
"fix": {
|
||||
"applicability": "safe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 3
|
||||
},
|
||||
"location": {
|
||||
"column": 1,
|
||||
"row": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": "Remove unused import: `math`"
|
||||
},
|
||||
"location": {
|
||||
"column": 8,
|
||||
"row": 2
|
||||
},
|
||||
"message": "`math` imported but unused",
|
||||
"noqa_row": 2,
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
||||
},
|
||||
{
|
||||
"cell": 3,
|
||||
"code": "F841",
|
||||
"end_location": {
|
||||
"column": 6,
|
||||
"row": 4
|
||||
},
|
||||
"filename": "notebook.ipynb",
|
||||
"fix": {
|
||||
"applicability": "unsafe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
"row": 4
|
||||
},
|
||||
"location": {
|
||||
"column": 1,
|
||||
"row": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": "Remove assignment to unused variable `x`"
|
||||
},
|
||||
"location": {
|
||||
"column": 5,
|
||||
"row": 4
|
||||
},
|
||||
"message": "Local variable `x` is assigned to but never used",
|
||||
"noqa_row": 4,
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
|
||||
}
|
||||
]
|
||||
@@ -4,6 +4,7 @@ expression: content
|
||||
---
|
||||
[
|
||||
{
|
||||
"cell": null,
|
||||
"code": "F401",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
@@ -11,7 +12,7 @@ expression: content
|
||||
},
|
||||
"filename": "fib.py",
|
||||
"fix": {
|
||||
"applicability": "Suggested",
|
||||
"applicability": "unsafe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
@@ -36,6 +37,7 @@ expression: content
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
||||
},
|
||||
{
|
||||
"cell": null,
|
||||
"code": "F841",
|
||||
"end_location": {
|
||||
"column": 6,
|
||||
@@ -43,7 +45,7 @@ expression: content
|
||||
},
|
||||
"filename": "fib.py",
|
||||
"fix": {
|
||||
"applicability": "Suggested",
|
||||
"applicability": "unsafe",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
@@ -68,6 +70,7 @@ expression: content
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
|
||||
},
|
||||
{
|
||||
"cell": null,
|
||||
"code": "F821",
|
||||
"end_location": {
|
||||
"column": 5,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/json_lines.rs
|
||||
expression: content
|
||||
---
|
||||
{"cell":1,"code":"F401","end_location":{"column":10,"row":2},"filename":"notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":10,"row":2},"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"}
|
||||
{"cell":2,"code":"F401","end_location":{"column":12,"row":2},"filename":"notebook.ipynb","fix":{"applicability":"safe","edits":[{"content":"","end_location":{"column":1,"row":3},"location":{"column":1,"row":2}}],"message":"Remove unused import: `math`"},"location":{"column":8,"row":2},"message":"`math` imported but unused","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":3,"code":"F841","end_location":{"column":6,"row":4},"filename":"notebook.ipynb","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":4},"location":{"column":1,"row":4}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":4},"message":"Local variable `x` is assigned to but never used","noqa_row":4,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/message/json_lines.rs
|
||||
expression: content
|
||||
---
|
||||
{"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"Suggested","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
{"cell":null,"code":"F401","end_location":{"column":10,"row":1},"filename":"fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":1,"row":2},"location":{"column":1,"row":1}}],"message":"Remove unused import: `os`"},"location":{"column":8,"row":1},"message":"`os` imported but unused","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/unused-import"}
|
||||
{"cell":null,"code":"F841","end_location":{"column":6,"row":6},"filename":"fib.py","fix":{"applicability":"unsafe","edits":[{"content":"","end_location":{"column":10,"row":6},"location":{"column":5,"row":6}}],"message":"Remove assignment to unused variable `x`"},"location":{"column":5,"row":6},"message":"Local variable `x` is assigned to but never used","noqa_row":6,"url":"https://docs.astral.sh/ruff/rules/unused-variable"}
|
||||
{"cell":null,"code":"F821","end_location":{"column":5,"row":1},"filename":"undef.py","fix":null,"location":{"column":4,"row":1},"message":"Undefined name `a`","noqa_row":1,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
fib.py:1:8: F401 [*] `os` imported but unused
|
||||
fib.py:1:8: F401 `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
fib.py:6:5: F841 Local variable `x` is assigned to but never used
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
fib.py:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
4 | def fibonacci(n):
|
||||
5 | """Compute the nth number in the Fibonacci sequence."""
|
||||
6 | x = 1
|
||||
| ^ F841
|
||||
7 | if n == 0:
|
||||
8 | return 0
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
undef.py:1:4: F821 Undefined name `a`
|
||||
|
|
||||
1 | if a == 1: pass
|
||||
| ^ F821
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/message/text.rs
|
||||
expression: content
|
||||
---
|
||||
notebook.ipynb:cell 1:2:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
notebook.ipynb:cell 2:2:8: F401 [*] `math` imported but unused
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^ F401
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
= help: Remove unused import: `math`
|
||||
|
||||
notebook.ipynb:cell 3:4:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| ^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
|
||||
@@ -16,22 +16,24 @@ use crate::line_width::{LineWidthBuilder, TabSize};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
use crate::registry::AsRule;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
struct EmitterFlags: u8 {
|
||||
/// Whether to show the fix status of a diagnostic.
|
||||
const SHOW_FIX_STATUS = 0b0000_0001;
|
||||
const SHOW_FIX_STATUS = 0b0000_0001;
|
||||
/// Whether to show the diff of a fix, for diagnostics that have a fix.
|
||||
const SHOW_FIX_DIFF = 0b0000_0010;
|
||||
const SHOW_FIX_DIFF = 0b0000_0010;
|
||||
/// Whether to show the source code of a diagnostic.
|
||||
const SHOW_SOURCE = 0b0000_0100;
|
||||
const SHOW_SOURCE = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextEmitter {
|
||||
flags: EmitterFlags,
|
||||
unsafe_fixes: UnsafeFixes,
|
||||
}
|
||||
|
||||
impl TextEmitter {
|
||||
@@ -53,6 +55,12 @@ impl TextEmitter {
|
||||
self.flags.set(EmitterFlags::SHOW_SOURCE, show_source);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
|
||||
self.unsafe_fixes = unsafe_fixes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for TextEmitter {
|
||||
@@ -79,18 +87,15 @@ impl Emitter for TextEmitter {
|
||||
writer,
|
||||
"cell {cell}{sep}",
|
||||
cell = notebook_index
|
||||
.cell(start_location.row.get())
|
||||
.unwrap_or_default(),
|
||||
.cell(start_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
sep = ":".cyan(),
|
||||
)?;
|
||||
|
||||
SourceLocation {
|
||||
row: OneIndexed::new(
|
||||
notebook_index
|
||||
.cell_row(start_location.row.get())
|
||||
.unwrap_or(1) as usize,
|
||||
)
|
||||
.unwrap(),
|
||||
row: notebook_index
|
||||
.cell_row(start_location.row)
|
||||
.unwrap_or(OneIndexed::MIN),
|
||||
column: start_location.column,
|
||||
}
|
||||
} else {
|
||||
@@ -105,7 +110,8 @@ impl Emitter for TextEmitter {
|
||||
sep = ":".cyan(),
|
||||
code_and_body = RuleCodeAndBody {
|
||||
message,
|
||||
show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS)
|
||||
show_fix_status: self.flags.intersects(EmitterFlags::SHOW_FIX_STATUS),
|
||||
unsafe_fixes: self.unsafe_fixes,
|
||||
}
|
||||
)?;
|
||||
|
||||
@@ -134,28 +140,33 @@ impl Emitter for TextEmitter {
|
||||
pub(super) struct RuleCodeAndBody<'a> {
|
||||
pub(crate) message: &'a Message,
|
||||
pub(crate) show_fix_status: bool,
|
||||
pub(crate) unsafe_fixes: UnsafeFixes,
|
||||
}
|
||||
|
||||
impl Display for RuleCodeAndBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let kind = &self.message.kind;
|
||||
if self.show_fix_status {
|
||||
if let Some(fix) = self.message.fix.as_ref() {
|
||||
// Do not display an indicator for unapplicable fixes
|
||||
if fix.applies(self.unsafe_fixes.required_applicability()) {
|
||||
return write!(
|
||||
f,
|
||||
"{code} {fix}{body}",
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
fix = format_args!("[{}] ", "*".cyan()),
|
||||
body = kind.body,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if self.show_fix_status && self.message.fix.is_some() {
|
||||
write!(
|
||||
f,
|
||||
"{code} {fix}{body}",
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
fix = format_args!("[{}] ", "*".cyan()),
|
||||
body = kind.body,
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
body = kind.body,
|
||||
)
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = kind.rule().noqa_code().to_string().red().bold(),
|
||||
body = kind.body,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +200,9 @@ impl Display for MessageCodeFrame<'_> {
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_start_cell = index.cell(content_start_index.get()).unwrap_or_default();
|
||||
let content_start_cell = index.cell(content_start_index).unwrap_or(OneIndexed::MIN);
|
||||
while start_index < content_start_index {
|
||||
if index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
if index.cell(start_index).unwrap_or(OneIndexed::MIN) == content_start_cell {
|
||||
break;
|
||||
}
|
||||
start_index = start_index.saturating_add(1);
|
||||
@@ -214,9 +225,9 @@ impl Display for MessageCodeFrame<'_> {
|
||||
// If we're working with a Jupyter Notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(index) = self.notebook_index {
|
||||
let content_end_cell = index.cell(content_end_index.get()).unwrap_or_default();
|
||||
let content_end_cell = index.cell(content_end_index).unwrap_or(OneIndexed::MIN);
|
||||
while end_index > content_end_index {
|
||||
if index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
if index.cell(end_index).unwrap_or(OneIndexed::MIN) == content_end_cell {
|
||||
break;
|
||||
}
|
||||
end_index = end_index.saturating_sub(1);
|
||||
@@ -256,8 +267,9 @@ impl Display for MessageCodeFrame<'_> {
|
||||
|| start_index.get(),
|
||||
|notebook_index| {
|
||||
notebook_index
|
||||
.cell_row(start_index.get())
|
||||
.unwrap_or_default() as usize
|
||||
.cell_row(start_index)
|
||||
.unwrap_or(OneIndexed::MIN)
|
||||
.get()
|
||||
},
|
||||
),
|
||||
annotations: vec![SourceAnnotation {
|
||||
@@ -339,8 +351,12 @@ struct SourceCode<'a> {
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
use crate::message::tests::{
|
||||
capture_emitter_notebook_output, capture_emitter_output, create_messages,
|
||||
create_notebook_messages,
|
||||
};
|
||||
use crate::message::TextEmitter;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
@@ -359,4 +375,27 @@ mod tests {
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_status_unsafe() {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled);
|
||||
let content = capture_emitter_output(&mut emitter, &create_messages());
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output() {
|
||||
let mut emitter = TextEmitter::default()
|
||||
.with_show_fix_status(true)
|
||||
.with_show_source(true)
|
||||
.with_unsafe_fixes(UnsafeFixes::Enabled);
|
||||
let (messages, notebook_indexes) = create_notebook_messages();
|
||||
let content = capture_emitter_notebook_output(&mut emitter, &messages, ¬ebook_indexes);
|
||||
|
||||
assert_snapshot!(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
use super::super::detection::comment_contains_code;
|
||||
@@ -67,11 +66,9 @@ pub(crate) fn commented_out_code(
|
||||
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
|
||||
|
||||
if settings.rules.should_fix(Rule::CommentedOutCode) {
|
||||
diagnostic.set_fix(Fix::manual(Edit::range_deletion(
|
||||
locator.full_lines_range(*range),
|
||||
)));
|
||||
}
|
||||
diagnostic.set_fix(Fix::display_edit(Edit::range_deletion(
|
||||
locator.full_lines_range(*range),
|
||||
)));
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/eradicate/mod.rs
|
||||
---
|
||||
ERA001.py:1:1: ERA001 [*] Found commented-out code
|
||||
ERA001.py:1:1: ERA001 Found commented-out code
|
||||
|
|
||||
1 | #import os
|
||||
| ^^^^^^^^^^ ERA001
|
||||
@@ -16,7 +16,7 @@ ERA001.py:1:1: ERA001 [*] Found commented-out code
|
||||
3 2 | #a = 3
|
||||
4 3 | a = 4
|
||||
|
||||
ERA001.py:2:1: ERA001 [*] Found commented-out code
|
||||
ERA001.py:2:1: ERA001 Found commented-out code
|
||||
|
|
||||
1 | #import os
|
||||
2 | # from foo import junk
|
||||
@@ -33,7 +33,7 @@ ERA001.py:2:1: ERA001 [*] Found commented-out code
|
||||
4 3 | a = 4
|
||||
5 4 | #foo(1, 2, 3)
|
||||
|
||||
ERA001.py:3:1: ERA001 [*] Found commented-out code
|
||||
ERA001.py:3:1: ERA001 Found commented-out code
|
||||
|
|
||||
1 | #import os
|
||||
2 | # from foo import junk
|
||||
@@ -52,7 +52,7 @@ ERA001.py:3:1: ERA001 [*] Found commented-out code
|
||||
5 4 | #foo(1, 2, 3)
|
||||
6 5 |
|
||||
|
||||
ERA001.py:5:1: ERA001 [*] Found commented-out code
|
||||
ERA001.py:5:1: ERA001 Found commented-out code
|
||||
|
|
||||
3 | #a = 3
|
||||
4 | a = 4
|
||||
@@ -72,7 +72,7 @@ ERA001.py:5:1: ERA001 [*] Found commented-out code
|
||||
7 6 | def foo(x, y, z):
|
||||
8 7 | content = 1 # print('hello')
|
||||
|
||||
ERA001.py:13:5: ERA001 [*] Found commented-out code
|
||||
ERA001.py:13:5: ERA001 Found commented-out code
|
||||
|
|
||||
11 | # This is a real comment.
|
||||
12 | # # This is a (nested) comment.
|
||||
@@ -91,7 +91,7 @@ ERA001.py:13:5: ERA001 [*] Found commented-out code
|
||||
15 14 |
|
||||
16 15 | #import os # noqa: ERA001
|
||||
|
||||
ERA001.py:21:5: ERA001 [*] Found commented-out code
|
||||
ERA001.py:21:5: ERA001 Found commented-out code
|
||||
|
|
||||
19 | class A():
|
||||
20 | pass
|
||||
@@ -109,7 +109,7 @@ ERA001.py:21:5: ERA001 [*] Found commented-out code
|
||||
23 22 |
|
||||
24 23 | dictionary = {
|
||||
|
||||
ERA001.py:26:5: ERA001 [*] Found commented-out code
|
||||
ERA001.py:26:5: ERA001 Found commented-out code
|
||||
|
|
||||
24 | dictionary = {
|
||||
25 | # "key1": 123, # noqa: ERA001
|
||||
@@ -129,7 +129,7 @@ ERA001.py:26:5: ERA001 [*] Found commented-out code
|
||||
28 27 | }
|
||||
29 28 |
|
||||
|
||||
ERA001.py:27:5: ERA001 [*] Found commented-out code
|
||||
ERA001.py:27:5: ERA001 Found commented-out code
|
||||
|
|
||||
25 | # "key1": 123, # noqa: ERA001
|
||||
26 | # "key2": 456,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user