Compare commits

...

151 Commits

Author SHA1 Message Date
Charlie Marsh
4a7167520a Match pylint logic 2023-03-17 17:31:34 -04:00
Charlie Marsh
41ab37e792 Rebase 2023-03-17 17:07:51 -04:00
Charlie Marsh
0b1806fdfa Merge branch 'main' into useless_return 2023-03-17 17:04:37 -04:00
Tomer Chachamu
1dd3cbd047 [pylint] invalid-characters-* (#3552) 2023-03-17 19:30:41 +00:00
Johan
bd935cbd49 [flake8-bugbear] Add no-explicit-stacklevel (B028) (#3550) 2023-03-17 19:20:08 +00:00
Charlie Marsh
babd0a05ac Avoid adding dashed line outside of docstring (#3581) 2023-03-17 14:40:32 -04:00
Micha Reiser
87fab4a2e1 Benchmark all rules (#3570) 2023-03-17 19:29:39 +01:00
Charlie Marsh
2e21920adf Respect type overrides in E721 (#3582) 2023-03-17 14:29:05 -04:00
Micha Reiser
dedf4cbdeb refactor: Move scope and binding types to scope.rs (#3573) 2023-03-17 17:31:33 +01:00
Micha Reiser
92179e6369 Scope and Binding IDs (#3572) 2023-03-17 17:12:27 +01:00
Evan Rittenhouse
33d2457909 Prefer itertools.pairwise() over zip() for successive pairs (RUF007) (#3501) 2023-03-16 23:50:45 -04:00
Charlie Marsh
373a77e8c2 Avoid C1901 violations within subscripts (#3517) 2023-03-17 02:52:05 +00:00
Jacob Latonis
73df267635 [pylint]: Implement continue-in-finally (E0116) (#3541) 2023-03-17 02:47:49 +00:00
Jonathan Plasse
f5e5caaa25 Fix autofix conflict between D209 and D400 (#3564) 2023-03-17 02:36:25 +00:00
Henry Schreiner
d9ed0aae69 ci(check_ecosystem): add cibuildwheel (#3567) 2023-03-16 22:34:56 -04:00
Charlie Marsh
e0df62b841 Rewrite mock import with starred imports (#3566) 2023-03-16 20:54:29 -04:00
Henry Schreiner
bbc87b7177 ci(check_ecosystem): add scikit-build-core (#3563) 2023-03-16 19:46:42 -04:00
Charlie Marsh
667130a4c3 Add some additional users to the users list (#3565) 2023-03-16 23:32:17 +00:00
Nyakku Shigure
72febf98b7 add PaddlePaddle to Who's Using Ruff? (#3562) 2023-03-16 14:20:11 -04:00
Xuehai Pan
e99e1fae2b ci: add python/typeshed to ecosystem check (#3559) 2023-03-16 14:19:48 -04:00
Micha Reiser
eff84442bc refactor: Add Copy implementation to Rule (#3556) 2023-03-16 17:50:18 +01:00
Micha Reiser
aa51ecedc5 ci: Benchmark CI Step (#3480) 2023-03-16 09:05:10 +01:00
Edgar R. M
9ae9cc9d2f Use value > max style in pylint and mccabe messages (#3553) 2023-03-16 01:37:25 -04:00
Micha Reiser
de1106b95a Allow dispatching the PR comment job for testing (#3535) 2023-03-15 09:34:53 +01:00
Charlie Marsh
e636c5fcf0 Avoid unused argument violations in .pyi files (#3533) 2023-03-15 03:17:19 +00:00
Charlie Marsh
12dfd57211 Bump version to v0.0.256 (#3531) 2023-03-14 22:52:21 -04:00
Charlie Marsh
d188d242a0 Avoid tracking as-imports separately with force-single-line (#3530) 2023-03-15 02:26:01 +00:00
Charlie Marsh
57796c5e59 Add last remaining deprecated typing imports (#3529) 2023-03-15 00:08:09 +00:00
Charlie Marsh
2545869797 Avoid PEP 604 isinstance errors for starred tuples (#3527) 2023-03-14 22:08:43 +00:00
Charlie Marsh
58353a4bf4 Avoid PEP 604 panic with empty tuple (#3526) 2023-03-14 22:02:15 +00:00
Charlie Marsh
a36139ae21 Replicate inline comments when splitting single-line imports (#3521) 2023-03-14 14:48:12 -04:00
Jonathan Plasse
7e904111b1 Fix PYI011 and add auto-fix (#3492) 2023-03-14 14:43:09 -04:00
Charlie Marsh
344daebb1b Refine complexity rules for try-except-else-finally (#3519) 2023-03-14 14:40:33 -04:00
Charlie Marsh
432059de35 Allow # ruff: prefix for isort action comments (#3493) 2023-03-14 14:34:28 -04:00
Charlie Marsh
c50d6da8b4 Allow string percent formatting in os.getenv (#3518) 2023-03-14 14:27:21 -04:00
Charlie Marsh
1b738f88c4 Allow f-strings and concatenations in os.getenv (#3516) 2023-03-14 17:46:34 +00:00
Charlie Marsh
1eff3dffa5 Ensure that redirect warnings appear exactly once per code (#3500) 2023-03-14 15:22:14 +00:00
Xuehai Pan
8c7317eb8d ci: fix missing short tag for cloudflare/wrangler-action (#3513) 2023-03-14 15:16:09 +00:00
Charlie Marsh
106a93eab0 Make Clap an optional feature for ruff crate (#3498) 2023-03-14 11:02:05 -04:00
Xuehai Pan
78c2b0ac47 ci: add dependabot integration for GitHub Actions (#3504) 2023-03-14 10:31:26 -04:00
Micha Reiser
d5700d7c69 Add Micro Benchmark (#3466) 2023-03-14 08:35:07 +01:00
Samuel Cormier-Iijima
3a7bdb39c9 Fix base ref determination for artifact download in ecosystem CI check (#3499) 2023-03-13 22:37:12 -04:00
Grzegorz Bokota
a82fe4a139 Fix lack of not in PLC1901 error message (#3497) 2023-03-13 19:19:41 -04:00
Charlie Marsh
62ff3b62e3 Add requires-python inference to docs (#3495) 2023-03-13 18:14:39 -04:00
Charlie Marsh
1e5db58b7b Include individual path checks in --verbose logging (#3489) 2023-03-13 17:13:47 -04:00
Charlie Marsh
a6e998d639 Remove Wasm-specific Rayon workarounds (#3490) 2023-03-13 16:48:43 -04:00
Charlie Marsh
a8c1915e2e Remove erroneous C4-to-C40 redirect (#3488) 2023-03-13 19:52:05 +00:00
Xuehai Pan
c515a1b31a PYI011: allow math constants in defaults (#3484) 2023-03-13 14:23:00 -04:00
Charlie Marsh
aa97a092bd Bump version to v0.0.255 (#3485) 2023-03-13 14:06:51 -04:00
Micha Reiser
685c242761 refactor(ruff_python_ast): Split get_argument (#3478) 2023-03-13 18:18:25 +01:00
Jonathan Plasse
b540407b74 Infer target-version from project metadata (#3470)
* Infer target-version from project metadata

* Fix requires-python with ">=3.8.16"

* Load requires-python at runtime

* Use upstream VersionSpecifiers

* Add debug information when parsing ruff.toml

* Display debug only if target_version is not set

* Bump pep440-rs to add impl Error for Pep440Error
2023-03-13 18:16:01 +01:00
Charlie Marsh
3a5fbd6d74 Upgrade RustPython to fix Serde dependency (#3481) 2023-03-13 12:29:31 -04:00
Charlie Marsh
227679b5cb Re-enable the T and C linter prefix selectors (#3452) 2023-03-13 08:20:30 -04:00
Charlie Marsh
c2750a59ab Implement an iterator for universal newlines (#3454)
# Summary

We need to support CR line endings (as opposed to LF and CRLF line endings, which are already supported). They're rare, but they do appear in Python code, and we tend to panic on any file that uses them.

Our `Locator` abstraction now supports CR line endings. However, Rust's `str#lines` implementation does _not_.

This PR adds a `UniversalNewlineIterator` implementation that respects all of CR, LF, and CRLF line endings, and plugs it into most of the `.lines()` call sites.

As an alternative design, it could be nice if we could leverage `Locator` for this. We've already computed all of the line endings, so we could probably iterate much more efficiently?

# Test Plan

Largely relying on automated testing, however, also ran over some known failure cases, like #3404.
2023-03-13 00:01:29 -04:00
Charlie Marsh
2a4d6ab3b2 Remove unnecessary Path::new from fs calls (#3476) 2023-03-12 23:18:23 -04:00
Charlie Marsh
7a80bcec58 Output GitLab paths relative to CI_PROJECT_DIR (#3475) 2023-03-13 03:03:37 +00:00
Y.D.X
297749a3a8 [doc] Update FAQ on Flake8 for structural pattern matching (#3473) 2023-03-13 02:27:22 +00:00
Charlie Marsh
8955e32b5c Respect ignores for runtime-import-in-type-checking-block (TCH004) (#3474) 2023-03-13 02:23:26 +00:00
Charlie Marsh
cd192eddf9 Add some new users to the README (#3471) 2023-03-13 00:08:58 +00:00
Jacob Latonis
675227db5c pylint: E1507 invalid-envvar-value (#3467) 2023-03-12 21:43:06 +00:00
Charlie Marsh
a65c6806a6 Avoid respecting noqa directives when RUF100 is enabled (#3469) 2023-03-12 14:37:35 -04:00
Calum Young
6c576872d4 List changes for all ecosystem repos (#3461) 2023-03-12 14:30:38 -04:00
Xuehai Pan
9858df1ac9 [FIX] PYI011: recognize Bool / Float / Complex numbers as simple defaults (#3459) 2023-03-12 17:34:09 +00:00
Charlie Marsh
7fb7268e8a Use a hash to fingerprint GitLab CI output (#3456) 2023-03-12 00:22:39 -05:00
Jacob Latonis
0f78f27713 pylint: W1508 invalid-envvar-default (#3449) 2023-03-11 16:44:42 -05:00
Charlie Marsh
12a6fc7041 Avoid removing un-aliased exceptions in OSError-aliased handlers (#3451) 2023-03-11 15:24:11 -05:00
Micha Reiser
d2988043af perf: Optimize UTF8/ASCII byte offset index (#3439) 2023-03-11 13:12:10 +01:00
Micha Reiser
cc8b13d3a7 refactor: Replace Vec in options metadata with static array (#3433) 2023-03-11 09:03:56 +00:00
Charlie Marsh
1e081cf9a6 Flag deprecated (but renamed) imports in UP035 (#3448) 2023-03-11 01:06:32 -05:00
Charlie Marsh
841bcf1cdd Remove unnecessary Serde derives (#3447) 2023-03-11 00:16:51 -05:00
Charlie Marsh
7062d1db16 Run ecosystem CI checks without --isolated (#3445) 2023-03-10 19:03:51 -05:00
Jonathan Plasse
8b561313aa Remove empty line after RUF100 auto-fix (#3414) 2023-03-10 22:57:13 +00:00
Samuel Cormier-Iijima
cfa2924664 Setup ecosystem CI (#3390)
This PR sets up an "ecosystem" check as an optional part of the CI step for pull requests. The primary piece of this is a new script in `scripts/check_ecosystem.py` which takes two ruff binaries as input and compares their outputs against a corpus of open-source code in parallel. I used ruff's `text` reporting format and stdlib's `difflib` (rather than JSON output and jsondiffs) to avoid adding another dependency. There is a new ecosystem-comment workflow to add a comment to the PR (see [this link](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) which explains why it needs to be done as a new workflow for security reasons).
2023-03-10 17:39:07 -05:00
Florian Best
a3aeec6377 docs(pycodestyle): document rules (#3407) 2023-03-10 22:36:38 +00:00
Micha Reiser
b983d5eb3f fix: method red not found in release builds (#3434) 2023-03-10 10:17:35 +01:00
kyoto7250
bb3bb24b59 Autofix PIE810 rule violations (#3411) 2023-03-10 05:17:22 +00:00
Charlie Marsh
872829ca72 When "Args" and "Parameters" are present, prefer NumPy style (#3430) 2023-03-10 02:58:05 +00:00
Charlie Marsh
2383228709 Respect --show-fixes with --fix-only (#3426) 2023-03-09 21:37:39 +00:00
Aryaman Marathe
952307d39d [pylint] C1901: compare-to-empty-string (#3405) 2023-03-09 21:33:34 +00:00
Charlie Marsh
024caca233 Introduce a ruff_diagnostics crate (#3409)
## Summary

This PR moves `Diagnostic`, `DiagnosticKind`, and `Fix` into their own crate, which will enable us to further split up Ruff, since sub-linter crates (which need to implement functions that return `Diagnostic`) can now depend on `ruff_diagnostics` rather than Ruff.
2023-03-09 20:48:57 +00:00
DanCardin
08ec11a31e fix: Emit a more useful error if an extend points at a non-existent ruff.toml file. (#3417) 2023-03-09 19:55:09 +00:00
Micha Reiser
bd05a8a74d fix: WASM tests (#3415) 2023-03-09 11:27:59 +01:00
Micha Reiser
229f1c34cb refactor: Extract ruff_wasm (#3401) 2023-03-09 10:07:39 +00:00
Charlie Marsh
a7f3532395 Ignore multiply-assigned variables in RET504 (#3393) 2023-03-08 19:11:55 -05:00
Aaron Cunningham
3349ceb969 [flake8-bugbear] Add flake8-bugbear's B030 rule (#3400) 2023-03-08 20:41:29 +00:00
Charlie Marsh
da1f83fe32 Remove core module from ruff_python_formatter (#3373) 2023-03-08 19:11:39 +00:00
Charlie Marsh
0a9d259f9c Remove copied core modules from ruff_python_formatter (#3371) 2023-03-08 19:03:40 +00:00
Charlie Marsh
130e733023 Implement From<Located> for Range (#3377) 2023-03-08 18:50:20 +00:00
Charlie Marsh
ff2c0dd491 Use shared leading_quote implementation in ruff_python_formatter (#3396) 2023-03-08 18:21:59 +00:00
Charlie Marsh
dfe1cad928 Rename DiagnosticKind#commit to DiagnosticKind#suggestion (#3397) 2023-03-08 18:06:19 +00:00
Charlie Marsh
ffad0bcdaa Decouple Diagnostic from "all violations" enumeration (#3352) 2023-03-08 17:51:37 +00:00
Jonathan Plasse
bc869d4f52 Fix PIE802 broken auto-fix with trailing comma (#3402) 2023-03-08 12:49:01 -05:00
Micha Reiser
a3de791f0a Make ruff_cli binary a small wrapper around lib (#3398) 2023-03-08 12:11:55 +01:00
Charlie Marsh
d9dfec30eb Catch RET504 usages via decorators (#3395) 2023-03-08 00:38:01 +00:00
Charlie Marsh
3f04def3a5 Remap ChainMap, Counter, and OrderedDict imports to collections (#3392) 2023-03-07 23:53:35 +00:00
Charlie Marsh
98177754de Handle multi-line fixes for byte-string prefixing (#3391) 2023-03-07 23:33:47 +00:00
Tom Forbes
8d5374762c Relax minimum rust version to allow for point releases (#3389) 2023-03-07 13:52:25 -05:00
Charlie Marsh
bad6bdda1f Create a rust_python_ast crate (#3370)
This PR productionizes @MichaReiser's suggestion in https://github.com/charliermarsh/ruff/issues/1820#issuecomment-1440204423, by creating a separate crate for the `ast` module (`rust_python_ast`). This will enable us to further split up the `ruff` crate, as we'll be able to create (e.g.) separate sub-linter crates that have access to these common AST utilities.

This was mostly a straightforward copy (with adjustments to module imports), as the few dependencies that _did_ require modifications were handled in #3366, #3367, and #3368.
2023-03-07 15:18:40 +00:00
Charlie Marsh
a5d302fcbf Pass Range struct by value (#3376) 2023-03-07 09:53:31 -05:00
Charlie Marsh
bced58ce40 Rename runtime-evaluated-baseclasses to runtime-evaluated-base-classes (#3379) 2023-03-07 09:51:12 -05:00
Aaron Cunningham
10e252e2fb Updated forced-separate type from Rust to abstract (#3380) 2023-03-07 09:35:39 -05:00
Sasan Jacob Rasti
4dead7541f Implement configuration options runtime-evaluated-decorators and runtime-evaluated-baseclasses for flake8-type-checking (#3292) 2023-03-06 23:34:19 -05:00
Charlie Marsh
fea1af5a63 Include entire prefix when reporting rule selector errors (#3375) 2023-03-07 00:04:52 +00:00
Charlie Marsh
c0ad875339 Remove unnecessary quote-stripping method (#3372) 2023-03-06 18:28:20 -05:00
Charlie Marsh
8437399496 Remove AST checker's dependency on resolver (#3368) 2023-03-06 21:45:09 +00:00
StefanBRas
074f5634a5 Remove duplicate info in azure format (#3369) 2023-03-06 16:40:03 -05:00
Charlie Marsh
694d41897a Move visibility module into ast crate (#3367) 2023-03-06 20:14:47 +00:00
Charlie Marsh
e1ebd9130d Don't enforce typing-import rules in .pyi files (#3362) 2023-03-06 15:03:34 -05:00
Charlie Marsh
fc8ca6edd2 Remove source_code's dependency on pydocstyle (#3366) 2023-03-06 15:01:01 -05:00
konstin
709dba2e71 Remove old define_violation! (in favor of #[violation]) (#3310) 2023-03-06 17:00:29 +00:00
Charlie Marsh
d1c48016eb Rename ruff_python crate to ruff_python_stdlib (#3354)
In hindsight, `ruff_python` is too general. A good giveaway is that it's actually a prefix of some other crates. The intent of this crate is to reimplement pieces of the Python standard library and CPython itself, so `ruff_python_stdlib` feels appropriate.
2023-03-06 13:43:22 +00:00
konstin
348a38d261 Deprecate define violation (#3358)
* Add `#[violation]` proc macro as a replacement for `define_violation!`

* Switch all rules to #[violation]
2023-03-06 10:59:06 +00:00
konstin
22e6778e17 Add cargo dev generate-all --check and catch outdated docs in cargo test (#3320) 2023-03-06 11:28:38 +01:00
StefanBRas
30c71dc59a Add Azure Devops as a -format option. (#3335) 2023-03-06 02:48:39 +00:00
Charlie Marsh
5d8591fec4 Skip byte-order-mark at start of file (#3343) 2023-03-05 21:37:14 -05:00
Carlos Gonçalves
673aa6e90f feat(e231): add rule + autofix (#3344) 2023-03-05 20:09:35 +00:00
Charlie Marsh
51fe9f7d4b Treat unary operations on constants as constant-like (#3348) 2023-03-04 16:30:33 -05:00
Charlie Marsh
d7767b2bad Use u8 to represent ambiguous representants (#3345) 2023-03-04 16:01:05 -05:00
Charlie Marsh
40d3b40c14 Move binding and scope tracking into a separate ast::Context struct (#3298) 2023-03-04 14:01:20 -05:00
Charlie Marsh
376ef929b1 Upgrade RustPython (#3341) 2023-03-04 14:01:03 -05:00
Jonathan Plasse
8828e12283 Bump dependencies and move more shared dependencies into workspace (#3340) 2023-03-04 12:36:26 -05:00
Charlie Marsh
f13633cc9f Avoid panicking in invalid_escape_sequence (#3338) 2023-03-04 12:14:33 -05:00
Evan Rittenhouse
889c05c87e Explicitly put Path(...) in Pathlib violations (#3333) 2023-03-04 04:33:12 +00:00
Charlie Marsh
bbbc44336e Bump version to 0.0.254 (#3331) 2023-03-03 19:11:07 -05:00
Charlie Marsh
d216b2aaa8 Treat callables within type definitions as default-non-types (#3329) 2023-03-03 23:07:30 +00:00
Charlie Marsh
367cc43c42 Un-gate PEP 604 isinstance rewrites from keep_runtime_typing checks (#3328) 2023-03-03 17:29:41 -05:00
Charlie Marsh
b5b26d5a3e Gate PEP604 isinstance rewrites behind Python 3.10+ (#3327) 2023-03-03 22:22:14 +00:00
Charlie Marsh
dedf8aa5cc Use presence of convention-specific sections during docstring inference (#3325) 2023-03-03 17:13:11 -05:00
Charlie Marsh
eb42ce9319 Extend RET503 autofixes to "end of statement", including comments (#3324) 2023-03-03 19:15:55 +00:00
Micha Reiser
cdbe2ee496 refactor: Introduce CacheKey trait (#3323)
This PR introduces a new `CacheKey` trait for types that can be used as a cache key.

I'm not entirely sure if this is worth the "overhead", but I was surprised to find `HashableHashSet` and got scared when I looked at the time complexity of the `hash` function. These implementations must be extremely slow in hashed collections.

I then searched for usages and quickly realized that only the cache uses these `Hash` implementations, where performance is less sensitive.

This PR introduces a new `CacheKey` trait to communicate the difference between a hash and computing a key for the cache. The new trait can be implemented for types that don't implement `Hash` for performance reasons, and we can define additional constraints on the implementation:  For example, we'll want to enforce portability when we add remote caching support. Using a different trait further allows us not to implement it for types without stable identities (e.g. pointers) or use other implementations than the standard hash function.
2023-03-03 18:29:49 +00:00
konstin
d1288dc2b1 Remove maturin build from CI (#3322)
maturin build generally works when cargo build works, so imho it's not worth running it with every CI run.
2023-03-03 16:50:26 +01:00
konstin
3bcffb5bdd Add flake-pyi PYI033 "Do not use type comments in stubs" (#3302) 2023-03-03 10:45:34 -05:00
Martin Packman
98209be8aa Detect quote style ignoring docstrings (#3306)
Currently the quote style of the first string in a file is used for autodetecting what to use when rewriting code for fixes. This is an okay heuristic, but often the first line in a file is a docstring, rather than a string constant, and it's not uncommon for pre-Black code to have different quoting styles for those.

For example, in the Google style guide:
https://google.github.io/styleguide/pyguide.html
> Be consistent with your choice of string quote character within a file. Pick ' or " and stick with it. ... Docstrings must use """ regardless.

This branch adjusts the logic to instead skip over any `"""` triple doublequote string tokens. The default, if there are no single quoted strings, is still to use double quote as the style.
2023-03-02 23:59:33 -05:00
Charlie Marsh
a03fa93c3a Abort when unable to fix relative imports past module root (#3319) 2023-03-03 04:38:51 +00:00
Charlie Marsh
4de3882088 Upgrade RustPython (#3316) 2023-03-02 22:59:29 -05:00
Charlie Marsh
3a98b68dc0 Always include @classmethod and @staticmethod in decorator lists (#3314) 2023-03-03 03:49:04 +00:00
Carlos Gonçalves
7e291e542d feat(e225,226,227,228): add rules (#3300) 2023-03-02 22:54:45 +00:00
Carlos Gonçalves
6f649d6579 feat(E211): add rule + autofix (#3313) 2023-03-02 22:48:04 +00:00
Yaroslav Chvanov
508bc605a5 Implement property-decorators configuration option for pydocstyle (#3311) 2023-03-02 16:59:56 -05:00
Charlie Marsh
ffdf6e35e6 Treat function type annotations within classes as runtime-required (#3312) 2023-03-02 16:45:26 -05:00
Martin Lehoux
886992c6c2 Replace tuples with type union in isinstance or issubclass calls (#3280) 2023-03-02 15:59:15 -05:00
tomecki
d20474da87 fix for autofix 2023-02-25 13:12:27 +01:00
tomecki
b7df7ee370 Merge branch 'useless_return' of github.com:tomecki/ruff into useless_return 2023-02-25 13:08:38 +01:00
tomecki
d6f0d1fab2 Merge branch 'charliermarsh:main' into useless_return 2023-02-25 13:08:09 +01:00
tomecki
c9bf979e61 Merge branch 'main' into useless_return 2023-02-25 13:06:51 +01:00
tomecki
e556fb4c77 Update crates/ruff/src/rules/pylint/rules/useless_return.rs
Co-authored-by: Jeong YunWon <69878+youknowone@users.noreply.github.com>
2023-02-25 12:59:13 +01:00
tomecki
02ae14037b Merge branch 'main' into useless_return 2023-02-22 09:38:18 +01:00
tomecki
385ccb2a50 rename for clarity 2023-02-21 20:54:22 +01:00
tomecki
ecacccbd5e cleanup 2023-02-21 20:39:33 +01:00
tomecki
bd7b472902 Merge branch 'main' into useless_return 2023-02-21 20:28:48 +01:00
tomecki
89b1b06dc7 wip 2023-02-21 20:28:33 +01:00
1331 changed files with 37518 additions and 20380 deletions

View File

@@ -1,5 +1,6 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"
benchmark = "bench -p ruff_benchmark --"
[target.'cfg(all())']
rustflags = [

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "12:00"
timezone: "America/New_York"
commit-message:
prefix: "ci(deps)"

133
.github/workflows/benchmark.yaml vendored Normal file
View File

@@ -0,0 +1,133 @@
name: Benchmark
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
run-benchmark:
if: github.event_name == 'pull_request'
name: "Run | ${{ matrix.os }}"
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: "PR - Checkout Branch"
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: "PR - Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- name: "PR - Build benchmarks"
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "PR - Run benchmarks"
run: cargo benchmark --save-baseline=pr
- name: "Main - Checkout Branch"
uses: actions/checkout@v3
with:
clean: false
ref: main
- name: "Main - Install Rust toolchain"
run: rustup show
- name: "Main - Build benchmarks"
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "Main - Run benchmarks"
run: cargo benchmark --save-baseline=main
- name: "Upload benchmark results"
uses: actions/upload-artifact@v3
with:
name: benchmark-results-${{ matrix.os }}
path: ./target/criterion
# Cleanup
- name: Remove Criterion Artifact
uses: JesseTG/rm@v1.0.3
with:
path: ./target/criterion
benchmark-compare:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
name: Compare
needs:
- run-benchmark
steps:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install critcmp"
# Use debug build: Building takes much longer than the "slowness" of using the debug build.
run: cargo install --debug critcmp
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Compare benchmark results"
shell: bash
run: |
echo "### Benchmark" >> summary.md
echo "#### Linux" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./target/criterion
- name: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Windows | Compare benchmark results"
shell: bash
run: |
echo "#### Windows" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
echo ${{ github.event.pull_request.number }} > pr-number
cat summary.md > $GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md

View File

@@ -17,20 +17,6 @@ env:
RUSTUP_MAX_RETRIES: 10
jobs:
cargo-build:
name: "cargo build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- run: cargo build --all
- run: ./target/debug/ruff_dev generate-all
- run: git diff --quiet README.md || echo "::error file=README.md::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. Run 'cargo dev generate-all'."
- run: git diff --exit-code -- README.md ruff.schema.json docs
cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
@@ -48,7 +34,7 @@ jobs:
- name: "Install Rust toolchain"
run: |
rustup component add clippy
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo-clippy-wasm:
@@ -60,8 +46,8 @@ jobs:
run: |
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test:
strategy:
@@ -73,9 +59,9 @@ jobs:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-insta
- run: pip install black[d]==22.12.0
- run: pip install black[d]==23.1.0
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
@@ -93,6 +79,31 @@ jobs:
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@v3
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
name: ruff
path: target/debug/ruff
cargo-test-wasm:
runs-on: ubuntu-latest
name: "cargo test (wasm)"
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@v3
with:
node-version: 18
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-pack-action@v0.4.0
- uses: Swatinem/rust-cache@v2
- name: "Run wasm-pack"
run: |
cd crates/ruff_wasm
wasm-pack test --node
scripts:
name: "test scripts"
@@ -101,7 +112,7 @@ jobs:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
- run: |
@@ -109,21 +120,6 @@ jobs:
./scripts/add_rule.py --name FirstRule --code TST001 --linter test
- run: cargo check
maturin-build:
name: "maturin build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- run: pip install maturin
- run: maturin build -b bin
- run: python scripts/transform_readme.py --target pypi
typos:
name: "spell check"
runs-on: ubuntu-latest
@@ -132,3 +128,51 @@ jobs:
- uses: crate-ci/typos@master
with:
files: .
ecosystem:
name: "ecosystem"
runs-on: ubuntu-latest
needs: cargo-test
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- uses: actions/download-artifact@v3
name: Download Ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- uses: dawidd6/action-download-artifact@v2
name: Download base results
with:
name: ruff
branch: ${{ github.event.pull_request.base.ref }}
check_artifacts: true
- name: Run ecosystem check
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
cat ecosystem-result > $GITHUB_STEP_SUMMARY
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Results
with:
name: ecosystem-result
path: ecosystem-result

View File

@@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-python@v4
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: "Install dependencies"
run: |
pip install -r docs/requirements.txt

View File

@@ -133,7 +133,7 @@ jobs:
target: ${{ matrix.target }}
manylinux: auto
args: --no-default-features --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- uses: uraimo/run-on-arch-action@v2.5.0
- uses: uraimo/run-on-arch-action@v2
if: matrix.target != 'ppc64'
name: Install built wheel
with:
@@ -206,7 +206,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- uses: uraimo/run-on-arch-action@master
- uses: uraimo/run-on-arch-action@v2
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}

View File

@@ -28,7 +28,7 @@ jobs:
- uses: jetli/wasm-pack-action@v0.4.0
- uses: jetli/wasm-bindgen-action@v0.2.0
- name: "Run wasm-pack"
run: wasm-pack build --target web --out-dir ../../playground/src/pkg crates/ruff
run: wasm-pack build --target web --out-dir ../../playground/src/pkg crates/ruff_wasm
- name: "Install Node dependencies"
run: npm ci
working-directory: playground

83
.github/workflows/pr-comment.yaml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: PR Check Comment
on:
workflow_run:
workflows: [CI, Benchmark]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ecosystem workflow that triggers the workflow run
required: true
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v2
name: Download PR Number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
- name: Extract PR Number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
fi
- uses: dawidd6/action-download-artifact@v2
name: "Download Ecosystem Result"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
name: ecosystem-result
workflow: ci.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/ecosystem
if_no_artifact_found: ignore
- uses: dawidd6/action-download-artifact@v2
name: "Download Benchmark Result"
id: download-benchmark-result
if: steps.pr-number.outputs.pr-number
with:
name: summary
workflow: benchmark.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/benchmark
if_no_artifact_found: ignore
- name: Generate Comment
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true' || steps.download-benchmark-result.outputs.found_artifact == 'true'
run: |
echo 'comment<<EOF' >> $GITHUB_OUTPUT
echo '## PR Check Results' >> $GITHUB_OUTPUT
if [[ -f pr/ecosystem/ecosystem-result ]]
then
echo "### Ecosystem" >> $GITHUB_OUTPUT
cat pr/ecosystem/ecosystem-result >> $GITHUB_OUTPUT
fi
if [[ -f pr/benchmark/summary.md ]]
then
cat pr/benchmark/summary.md >> $GITHUB_OUTPUT
fi
echo 'EOF' >> $GITHUB_OUTPUT
- name: Create or update comment
if: steps.generate-comment.outputs.comment
uses: thollander/actions-comment-pull-request@v2
with:
pr_number: ${{ steps.pr-number.outputs.pr-number }}
message: ${{ steps.generate-comment.outputs.comment }}
comment_tag: PR Check Results

View File

@@ -208,7 +208,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: auto
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2.5.0
- uses: uraimo/run-on-arch-action@v2
if: matrix.platform.arch != 'ppc64'
name: Install built wheel
with:
@@ -309,7 +309,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist
- uses: uraimo/run-on-arch-action@master
- uses: uraimo/run-on-arch-action@v2
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}

View File

@@ -1,7 +1,7 @@
fail_fast: true
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
rev: v0.12.1
hooks:
- id: validate-pyproject

View File

@@ -104,7 +104,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention).
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
1. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
1. In that file, define a violation struct. You can grep for `#[violation]` to see examples.
1. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks), `crates/ruff/src/checkers/lines.rs`
@@ -115,7 +115,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
To define the violation, start by creating a dedicated file for your rule under the appropriate
rule linter (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
contain a struct defined via `define_violation!`, along with a function that creates the violation
contain a struct defined via `#[violation]`, along with a function that creates the violation
based on any required inputs. (Many of the existing examples live in `crates/ruff/src/violations.rs`,
but we're looking to place new rules in their own files.)

363
Cargo.lock generated
View File

@@ -289,9 +289,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.1.6"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_derive",
@@ -308,18 +308,19 @@ version = "4.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd125be87bf4c255ebc50de0b7f4d2a6201e8ac3dc86e39c0ad081dc5e7236fe"
dependencies = [
"clap 4.1.6",
"clap 4.1.8",
]
[[package]]
name = "clap_complete_command"
version = "0.4.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d"
dependencies = [
"clap 4.1.6",
"clap 4.1.8",
"clap_complete",
"clap_complete_fig",
"clap_complete_nushell",
]
[[package]]
@@ -328,15 +329,25 @@ version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63a06158a72dbb088f864887b4409fd22600ba379f3cee3040f842234cc5c2d0"
dependencies = [
"clap 4.1.6",
"clap 4.1.8",
"clap_complete",
]
[[package]]
name = "clap_complete_nushell"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
dependencies = [
"clap 4.1.8",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.1.0"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
dependencies = [
"heck",
"proc-macro-error",
@@ -427,9 +438,9 @@ dependencies = [
[[package]]
name = "console_log"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
checksum = "878ad067d4089144a36ee412d665916c665430eb84c0057b9987f424a5d15c03"
dependencies = [
"log",
"web-sys",
@@ -554,6 +565,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctor"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "cxx"
version = "1.0.91"
@@ -598,17 +619,6 @@ dependencies = [
"syn",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diff"
version = "0.1.13"
@@ -770,10 +780,10 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.253"
version = "0.0.256"
dependencies = [
"anyhow",
"clap 4.1.6",
"clap 4.1.8",
"colored",
"configparser",
"once_cell",
@@ -1080,12 +1090,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "joinery"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
[[package]]
name = "js-sys"
version = "0.3.61"
@@ -1369,15 +1373,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "notify"
version = "5.1.0"
@@ -1405,7 +1400,6 @@ dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
@@ -1415,7 +1409,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
@@ -1447,27 +1440,6 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0072973714303aa6e3631c7e8e777970cf4bdd25dc4932e41031027b8bcc4e"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0629cbd6b897944899b1f10496d9c4a7ac5878d45fd61bc22e9e79bfbbc29597"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@@ -1486,6 +1458,15 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "output_vt100"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
dependencies = [
"winapi",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -1533,6 +1514,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "peg"
version = "0.8.1"
@@ -1560,6 +1547,18 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
[[package]]
name = "pep440_rs"
version = "0.2.0"
source = "git+https://github.com/konstin/pep440-rs.git?rev=a8fef4ec47f4c25b070b39cdbe6a0b9847e49941#a8fef4ec47f4c25b070b39cdbe6a0b9847e49941"
dependencies = [
"lazy_static",
"regex",
"serde",
"tracing",
"unicode-width",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
@@ -1758,13 +1757,15 @@ dependencies = [
]
[[package]]
name = "proc-macro-crate"
name = "pretty_assertions"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
dependencies = [
"once_cell",
"toml_edit",
"ctor",
"diff",
"output_vt100",
"yansi",
]
[[package]]
@@ -1870,9 +1871,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.6.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
@@ -1880,9 +1881,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.10.2"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -1981,22 +1982,17 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.253"
version = "0.0.256"
dependencies = [
"anyhow",
"bisection",
"bitflags",
"cfg-if",
"chrono",
"clap 4.1.6",
"clap 4.1.8",
"colored",
"console_error_panic_hook",
"console_log",
"criterion",
"derivative",
"dirs",
"fern",
"getrandom",
"glob",
"globset",
"ignore",
@@ -2004,7 +2000,6 @@ dependencies = [
"insta",
"is-macro",
"itertools",
"js-sys",
"libcst",
"log",
"natord",
@@ -2013,10 +2008,16 @@ dependencies = [
"num-traits",
"once_cell",
"path-absolutize",
"pathdiff",
"pep440_rs",
"pretty_assertions",
"regex",
"result-like",
"ruff_cache",
"ruff_diagnostics",
"ruff_macros",
"ruff_python",
"ruff_python_ast",
"ruff_python_stdlib",
"ruff_rustpython",
"rustc-hash",
"rustpython-common",
@@ -2024,7 +2025,6 @@ dependencies = [
"schemars",
"semver",
"serde",
"serde-wasm-bindgen",
"shellexpand",
"smallvec",
"strum",
@@ -2032,15 +2032,38 @@ dependencies = [
"test-case",
"textwrap",
"thiserror",
"titlecase",
"toml",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "ruff_benchmark"
version = "0.0.0"
dependencies = [
"criterion",
"mimalloc",
"once_cell",
"ruff",
"serde",
"serde_json",
"tikv-jemallocator",
"ureq",
"url",
]
[[package]]
name = "ruff_cache"
version = "0.0.0"
dependencies = [
"filetime",
"globset",
"itertools",
"regex",
"ruff_macros",
]
[[package]]
name = "ruff_cli"
version = "0.0.253"
version = "0.0.256"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2050,7 +2073,7 @@ dependencies = [
"bitflags",
"cachedir",
"chrono",
"clap 4.1.6",
"clap 4.1.8",
"clap_complete_command",
"clearscreen",
"colored",
@@ -2066,6 +2089,8 @@ dependencies = [
"rayon",
"regex",
"ruff",
"ruff_cache",
"ruff_diagnostics",
"rustc-hash",
"serde",
"serde_json",
@@ -2083,13 +2108,15 @@ name = "ruff_dev"
version = "0.0.0"
dependencies = [
"anyhow",
"clap 4.1.6",
"clap 4.1.8",
"itertools",
"libcst",
"once_cell",
"pretty_assertions",
"regex",
"ruff",
"ruff_cli",
"ruff_diagnostics",
"rustpython-common",
"rustpython-parser",
"schemars",
@@ -2099,6 +2126,15 @@ dependencies = [
"textwrap",
]
[[package]]
name = "ruff_diagnostics"
version = "0.0.0"
dependencies = [
"ruff_python_ast",
"rustpython-parser",
"serde",
]
[[package]]
name = "ruff_formatter"
version = "0.0.0"
@@ -2125,12 +2161,25 @@ dependencies = [
]
[[package]]
name = "ruff_python"
name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags",
"is-macro",
"itertools",
"log",
"nohash-hasher",
"num-bigint",
"num-traits",
"once_cell",
"regex",
"ruff_python_stdlib",
"ruff_rustpython",
"rustc-hash",
"rustpython-common",
"rustpython-parser",
"smallvec",
]
[[package]]
@@ -2138,13 +2187,14 @@ name = "ruff_python_formatter"
version = "0.0.0"
dependencies = [
"anyhow",
"clap 4.1.6",
"clap 4.1.8",
"insta",
"is-macro",
"itertools",
"once_cell",
"ruff_formatter",
"ruff_python",
"ruff_python_ast",
"ruff_python_stdlib",
"ruff_rustpython",
"ruff_testing_macros",
"ruff_text_size",
@@ -2155,6 +2205,15 @@ dependencies = [
"test-case",
]
[[package]]
name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"once_cell",
"regex",
"rustc-hash",
]
[[package]]
name = "ruff_rustpython"
version = "0.0.0"
@@ -2170,7 +2229,6 @@ name = "ruff_testing_macros"
version = "0.0.0"
dependencies = [
"glob",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
@@ -2186,6 +2244,25 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "ruff_wasm"
version = "0.0.0"
dependencies = [
"console_error_panic_hook",
"console_log",
"getrandom",
"js-sys",
"log",
"ruff",
"ruff_python_ast",
"ruff_rustpython",
"rustpython-parser",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
@@ -2231,7 +2308,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2240,7 +2317,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"ascii",
"bitflags",
@@ -2265,24 +2342,21 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"bincode",
"bitflags",
"bstr 0.2.17",
"itertools",
"lz4_flex",
"num-bigint",
"num-complex",
"num_enum",
"serde",
"thiserror",
]
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=aa8336ee94492b52458ed8e1517238e5c6c2914c#aa8336ee94492b52458ed8e1517238e5c6c2914c"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"ahash",
"anyhow",
@@ -2297,7 +2371,7 @@ dependencies = [
"rustc-hash",
"rustpython-ast",
"rustpython-compiler-core",
"thiserror",
"serde",
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
@@ -2327,9 +2401,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.11"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
dependencies = [
"dyn-clone",
"schemars_derive",
@@ -2339,9 +2413,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.11"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
dependencies = [
"proc-macro2",
"quote",
@@ -2394,9 +2468,9 @@ dependencies = [
[[package]]
name = "serde-wasm-bindgen"
version = "0.4.5"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
@@ -2553,9 +2627,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.108"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
@@ -2626,18 +2700,18 @@ checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
[[package]]
name = "test-case"
version = "2.2.2"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21d6cf5a7dffb3f9dceec8e6b8ca528d9bd71d36c9f074defb548ce161f598c0"
checksum = "679b019fb241da62cc449b33b224d19ebe1c6767b495569765115dd7f7f9fba4"
dependencies = [
"test-case-macros",
]
[[package]]
name = "test-case-macros"
version = "2.2.2"
name = "test-case-core"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26"
checksum = "72dc21b5887f4032c4656502d085dc28f2afbb686f25f216472bb0526f4b1b88"
dependencies = [
"cfg-if",
"proc-macro-error",
@@ -2646,6 +2720,19 @@ dependencies = [
"syn",
]
[[package]]
name = "test-case-macros"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3786898e0be151a96f730fd529b0e8a10f5990fa2a7ea14e37ca27613c05190"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
"test-case-core",
]
[[package]]
name = "textwrap"
version = "0.16.0"
@@ -2752,22 +2839,11 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "titlecase"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38397a8cdb017cfeb48bf6c154d6de975ac69ffeed35980fde199d2ee0842042"
dependencies = [
"joinery",
"lazy_static",
"regex",
]
[[package]]
name = "toml"
version = "0.6.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
@@ -2777,24 +2853,24 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.5.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.18.1"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
@@ -2805,9 +2881,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
@@ -2949,7 +3037,7 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "0.6.0"
source = "git+https://github.com/youknowone/unicode_names2.git?tag=v0.6.0+character-alias#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
source = "git+https://github.com/youknowone/unicode_names2.git?rev=4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
dependencies = [
"phf",
]
@@ -3285,6 +3373,15 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658"
dependencies = [
"memchr",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
@@ -3294,6 +3391,12 @@ dependencies = [
"linked-hash-map",
]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "yansi-term"
version = "0.1.2"

View File

@@ -3,25 +3,49 @@ members = ["crates/*"]
[workspace.package]
edition = "2021"
rust-version = "1.67.0"
rust-version = "1.67"
homepage = "https://beta.ruff.rs/docs/"
documentation = "https://beta.ruff.rs/docs/"
repository = "https://github.com/charliermarsh/ruff"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
[workspace.dependencies]
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
itertools = { version = "0.10.5" }
anyhow = { version = "1.0.69" }
bitflags = { version = "1.3.2" }
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
clap = { version = "4.1.8", 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.28.0" }
is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
log = { version = "0.4.17" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.0.14" }
proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "aa8336ee94492b52458ed8e1517238e5c6c2914c" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "aa8336ee94492b52458ed8e1517238e5c6c2914c" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
rustpython-parser = { features = [
"lalrpop",
"serde",
], git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
toml = { version = "0.6.0" }
syn = { version = "1.0.109" }
test-case = { version = "3.0.0" }
textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
[profile.release]
panic = "abort"
@@ -39,3 +63,9 @@ opt-level = 3
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
[profile.dev.package.rustpython-parser]
opt-level = 1
# Use the `--profile release-debug` flag to show symbols in release mode.
# e.g. `cargo build --profile release-debug`
[profile.release-debug]
inherits = "release"
debug = 1

View File

@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.253'
rev: 'v0.0.256'
hooks:
- id: ruff
```
@@ -306,6 +306,13 @@ Ruff is used in a number of major open-source projects, including:
- [meson-python](https://github.com/mesonbuild/meson-python)
- [ZenML](https://github.com/zenml-io/zenml)
- [delta-rs](https://github.com/delta-io/delta-rs)
- [Starlite](https://github.com/starlite-api/starlite)
- [telemetry-airflow (Mozilla)](https://github.com/mozilla/telemetry-airflow)
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [nox](https://github.com/wntrblm/nox)
- [Neon](https://github.com/neondatabase/neon)
- [The Algorithms](https://github.com/TheAlgorithms/Python)
## License

View File

@@ -1,17 +1,18 @@
[package]
name = "flake8-to-ruff"
version = "0.0.253"
version = "0.0.256"
edition = { workspace = true }
rust-version = { workspace = true }
[dependencies]
ruff = { path = "../ruff", default-features = false }
anyhow = { workspace = true }
clap = { workspace = true }
colored = { version = "2.0.0" }
colored = { workspace = true }
configparser = { version = "3.0.2" }
once_cell = { workspace = true }
regex = { workspace = true }
ruff = { path = "../ruff", default-features = false }
rustc-hash = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View File

@@ -46,8 +46,15 @@ fn main() -> Result<()> {
.map(|tool| ExternalConfig {
black: tool.black.as_ref(),
isort: tool.isort.as_ref(),
..Default::default()
})
.unwrap_or_default();
let external_config = ExternalConfig {
project: pyproject
.as_ref()
.and_then(|pyproject| pyproject.project.as_ref()),
..external_config
};
// Create Ruff's pyproject.toml section.
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;

View File

@@ -1,82 +1,76 @@
[package]
name = "ruff"
version = "0.0.253"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = { workspace = true }
rust-version = { workspace = true }
documentation = "https://github.com/charliermarsh/ruff"
homepage = "https://github.com/charliermarsh/ruff"
repository = "https://github.com/charliermarsh/ruff"
version = "0.0.256"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
readme = "README.md"
license = "MIT"
[lib]
name = "ruff"
crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
ruff_python_ast = { path = "../ruff_python_ast" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_rustpython = { path = "../ruff_rustpython" }
anyhow = { workspace = true }
bisection = { version = "0.1.0" }
bitflags = { version = "1.3.2" }
cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { workspace = true, features = ["derive", "env", "string"] }
colored = { version = "2.0.0" }
derivative = { version = "2.2.0" }
bitflags = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
glob = { version = "0.3.0" }
globset = { version = "0.4.9" }
ignore = { version = "0.4.18" }
imperative = { version = "1.0.3" }
glob = { workspace = true }
globset = { workspace = true }
ignore = { workspace = true }
imperative = { version = "1.0.4" }
is-macro = { workspace = true }
itertools = { workspace = true }
libcst = { workspace = true }
log = { version = "0.4.17" }
log = { workspace = true }
natord = { version = "1.0.9" }
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = "0.2.15"
num-traits = { version = "0.2.15" }
once_cell = { workspace = true }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
path-absolutize = { workspace = true, features = [
"once_cell_cache",
"use_unix_paths_on_wasm",
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { git = "https://github.com/konstin/pep440-rs.git", features = [
"serde",
], rev = "a8fef4ec47f4c25b070b39cdbe6a0b9847e49941" }
regex = { workspace = true }
result-like = "0.4.6"
result-like = { version = "0.4.6" }
rustc-hash = { workspace = true }
rustpython-common = { workspace = true }
rustpython-parser = { workspace = true }
schemars = { workspace = true }
semver = { version = "1.0.16" }
serde = { workspace = true }
shellexpand = { version = "3.0.0" }
shellexpand = { workspace = true }
smallvec = { version = "1.10.0" }
strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
textwrap = { workspace = true }
thiserror = { version = "1.0.38" }
toml = { workspace = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "0.2.0" }
serde-wasm-bindgen = { version = "0.4" }
js-sys = { version = "0.3.60" }
wasm-bindgen = { version = "0.2.83" }
[dev-dependencies]
insta = { version = "1.19.0", features = ["yaml", "redactions"] }
test-case = { version = "2.2.2" }
wasm-bindgen-test = { version = "0.3.33" }
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
criterion = { version = "0.4.0" }
insta = { workspace = true, features = ["yaml", "redactions"] }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
[features]
default = []

View File

@@ -0,0 +1,11 @@
import warnings
"""
Should emit:
B028 - on lines 8 and 9
"""
warnings.warn(DeprecationWarning("test"))
warnings.warn(DeprecationWarning("test"), source=None)
warnings.warn(DeprecationWarning("test"), source=None, stacklevel=2)
warnings.warn(DeprecationWarning("test"), stacklevel=1)

View File

@@ -0,0 +1,47 @@
"""
Should emit:
B030:
- line 12, column 8
- line 17, column 9
- line 22, column 21
- line 27, column 37
"""
try:
pass
except 1: # error
pass
try:
pass
except (1, ValueError): # error
pass
try:
pass
except (ValueError, (RuntimeError, (KeyError, TypeError))): # error
pass
try:
pass
except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error
pass
try:
pass
except (ValueError, *(RuntimeError, TypeError)): # ok
pass
try:
pass
except (ValueError, *[RuntimeError, *(TypeError,)]): # ok
pass
def what_to_catch():
return ...
try:
pass
except what_to_catch(): # ok
pass

View File

@@ -8,3 +8,9 @@ any({x.id for x in bar})
# PIE 802
any([x.id for x in bar])
all([x.id for x in bar])
any( # first comment
[x.id for x in bar], # second comment
) # third comment
all( # first comment
[x.id for x in bar], # second comment
) # third comment

View File

@@ -6,6 +6,8 @@ obj.endswith("foo") or obj.endswith("bar")
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# error
obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))

View File

@@ -1,3 +1,10 @@
import math
import os
import sys
from math import inf
import numpy as np
def f12(
x,
y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments
@@ -61,3 +68,49 @@ def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...
def f23(
x: bool = True, # OK
) -> None: ...
def f24(
x: float = 3.14, # OK
) -> None: ...
def f25(
x: float = -3.14, # OK
) -> None: ...
def f26(
x: complex = -3.14j, # OK
) -> None: ...
def f27(
x: complex = -3 - 3.14j, # OK
) -> None: ...
def f28(
x: float = math.tau, # OK
) -> None: ...
def f29(
x: float = math.inf, # OK
) -> None: ...
def f30(
x: float = -math.inf, # OK
) -> None: ...
def f31(
x: float = inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f32(
x: float = np.inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f33(
x: float = math.nan, # OK
) -> None: ...
def f34(
x: float = -math.nan, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f35(
x: complex = math.inf # Error PYI011 Only simple default values allowed for typed arguments
+ 1j,
) -> None: ...
def f36(
*, x: str = sys.version, # OK
) -> None: ...
def f37(
*, x: str = "" + "", # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...

View File

@@ -43,3 +43,6 @@ def f21(
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...
def f23(
x=True, # OK
) -> None: ...

View File

@@ -0,0 +1,33 @@
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
from collections.abc import Sequence
from typing import TypeAlias
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
def func(
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
): ...
class Foo:
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
G: TypeAlias = None # type: ignore
H: TypeAlias = None # type: ignore[attr-defined]
I: TypeAlias = None #type: ignore
J: TypeAlias = None # type: ignore
K: TypeAlias = None# type: ignore
L: TypeAlias = None#type:ignore
# Whole line commented out # type: int
M: TypeAlias = None # type: can't parse me!
class Bar:
N: TypeAlias = None # type: can't parse me either!
# This whole line is commented out and indented # type: str

View File

@@ -0,0 +1,33 @@
# From https://github.com/PyCQA/flake8-pyi/blob/4212bec43dbc4020a59b90e2957c9488575e57ba/tests/type_comments.pyi
from collections.abc import Sequence
from typing import TypeAlias
A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
def func(
arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
): ...
class Foo:
Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")
G: TypeAlias = None # type: ignore
H: TypeAlias = None # type: ignore[attr-defined]
I: TypeAlias = None #type: ignore
J: TypeAlias = None # type: ignore
K: TypeAlias = None# type: ignore
L: TypeAlias = None#type:ignore
# Whole line commented out # type: int
M: TypeAlias = None # type: can't parse me!
class Bar:
N: TypeAlias = None # type: can't parse me either!
# This whole line is commented out and indented # type: str

View File

@@ -293,3 +293,30 @@ def x(y):
def foo(baz: str) -> str:
return baz
def end_of_statement():
def example():
if True:
return ""
def example():
if True:
return ""
def example():
if True:
return "" # type: ignore
def example():
if True:
return "" ;
def example():
if True:
return "" \
; # type: ignore

View File

@@ -6,22 +6,6 @@ def x():
return a # error
def x():
a = 1
print(a)
a = 2
return a # error
def x():
a = 1
if True:
return a # error
a = 2
print(a)
return a
# Can be refactored false positives
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
@@ -165,44 +149,6 @@ def my_func():
return foo
# Refactored incorrect false positives
# See test cases above marked: "Can be refactored false positives"
# https://github.com/afonasev/flake8-return/issues/47#issuecomment-1122571066
def get_bar_if_exists(obj):
if hasattr(obj, "bar"):
return str(obj.bar)
return ""
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def x():
formatted = _USER_AGENT_FORMATTER.format(format_string, **values)
# clean up after any blank components
return formatted.replace("()", "").replace(" ", " ").strip()
# https://github.com/afonasev/flake8-return/issues/47#issue-641117366
def user_agent_username(username=None):
if not username:
return ""
username = username.replace(" ", "_") # Avoid spaces or %20.
try:
username.encode("ascii") # just test,
# but not actually use it
except UnicodeEncodeError:
username = quote(username.encode("utf-8"))
else:
# % is legal in the default $wgLegalTitleChars
# This is so that ops know the real pywikibot will not
# allow a useragent in the username to allow through a
# hand-coded percent-encoded value.
if "%" in username:
username = quote(username)
return username
# https://github.com/afonasev/flake8-return/issues/116#issue-1367575481
def no_exception_loop():
success = False
@@ -260,3 +206,44 @@ def nonlocal_assignment():
nonlocal X
X = 1
return X
def decorator() -> Flask:
app = Flask(__name__)
@app.route('/hello')
def hello() -> str:
"""Hello endpoint."""
return 'Hello, World!'
return app
def default():
y = 1
def f(x = y) -> X:
return x
return y
def get_queryset(option_1, option_2):
queryset: Any = None
queryset = queryset.filter(a=1)
if option_1:
queryset = queryset.annotate(b=Value(2))
if option_2:
queryset = queryset.filter(c=3)
return queryset
def get_queryset():
queryset = Model.filter(a=1)
queryset = queryset.filter(c=3)
return queryset
def get_queryset():
queryset = Model.filter(a=1)
return queryset # error

View File

@@ -6,6 +6,8 @@
"yoda" <= compare # SIM300
"yoda" < compare # SIM300
42 > age # SIM300
-42 > age # SIM300
+42 > age # SIM300
YODA == age # SIM300
YODA > age # SIM300
YODA >= age # SIM300

View File

@@ -1,7 +1,8 @@
from typing import TYPE_CHECKING, Any, ClassVar
import attrs
from ....import unknown
from ..protocol import commands, definitions, responses
from ..server import example
from .. import server

View File

@@ -0,0 +1,3 @@
from typing import Tuple
x: Tuple

View File

@@ -0,0 +1,38 @@
from __future__ import annotations
import pathlib
from typing import TYPE_CHECKING
import pydantic
from pydantic import BaseModel
if TYPE_CHECKING:
import datetime # TCH004
from array import array # TCH004
import pandas # TCH004
import pyproj
class A(pydantic.BaseModel):
x: datetime.datetime
class B(BaseModel):
x: pandas.DataFrame
class C(BaseModel):
x: pathlib.Path
class E:
pass
class F(E):
x: pyproj.Transformer
class G(BaseModel):
x: array

View File

@@ -0,0 +1,28 @@
from __future__ import annotations
import geopandas as gpd # TCH002
import pydantic
import pyproj # TCH002
from pydantic import BaseModel
import numpy
class A(BaseModel):
x: pandas.DataFrame
class B(pydantic.BaseModel):
x: numpy.ndarray
class C:
pass
class D(C):
x: pyproj.Transformer
class E(C):
x: gpd.GeoDataFrame

View File

@@ -0,0 +1,24 @@
from __future__ import annotations
import datetime
import pathlib
from uuid import UUID # TCH003
import pydantic
from pydantic import BaseModel
class A(pydantic.BaseModel):
x: datetime.datetime
class B(BaseModel):
x: pathlib.Path
class C:
pass
class D(C):
x: UUID

View File

@@ -0,0 +1,46 @@
from __future__ import annotations
import pathlib
from typing import TYPE_CHECKING
import attrs
from attrs import frozen
import numpy
if TYPE_CHECKING:
import datetime # TCH004
from array import array # TCH004
import pandas # TCH004
import pyproj
@attrs.define(auto_attribs=True)
class A:
x: datetime.datetime
@attrs.define
class B:
x: pandas.DataFrame
@frozen(auto_attribs=True)
class C:
x: pathlib.Path
@frozen
class D:
x: array
@dataclass
class E:
x: pyproj.Transformer
@attrs.define
class F:
x: numpy.ndarray

View File

@@ -0,0 +1,30 @@
from __future__ import annotations
from dataclasses import dataclass
import attrs
import pandas
import pyproj
from attrs import frozen
import numpy # TCH002
@attrs.define(auto_attribs=True)
class A:
x: pyproj.Transformer
@attrs.define
class B:
x: pandas.DataFrame
@frozen
class C:
x: pandas.DataFrame
@dataclass
class D:
x: numpy.ndarray

View File

@@ -0,0 +1,24 @@
from __future__ import annotations
import datetime
from array import array
from dataclasses import dataclass
from uuid import UUID # TCH003
import attrs
from attrs import frozen
@attrs.define(auto_attribs=True)
class A:
x: datetime.datetime
@frozen
class B:
x: array
@dataclass
class C:
x: UUID

View File

@@ -1,5 +1,10 @@
import sys, math
from os import path, uname
from json import detect_encoding
from json import dump
from json import dumps as json_dumps
from json import load
from json import loads as json_loads
from logging.handlers import StreamHandler, FileHandler
# comment 1
@@ -10,9 +15,10 @@ from third_party import lib4
from foo import bar # comment 3
from foo2 import bar2 # comment 4
from foo3 import bar3, baz3 # comment 5
# comment 5
# comment 6
from bar import (
a, # comment 6
b, # comment 7
)
a, # comment 7
b, # comment 8
)

View File

@@ -0,0 +1,10 @@
# ruff: isort: skip_file
import e
import f
# isort: split
import a
import b
import c
import d

View File

@@ -6,6 +6,14 @@ def f():
# isort: on
def f():
# ruff: isort: off
import sys
import os
import collections
# ruff: isort: on
def f():
import sys
import os # isort: skip

View File

@@ -0,0 +1,14 @@
#: E211
spam (1)
#: E211 E211
dict ['key'] = list [index]
#: E211
dict['key'] ['subkey'] = list[index]
#: Okay
spam(1)
dict['key'] = list[index]
# This is not prohibited by PEP8, but avoid it.
class Foo (Bar, Baz):
pass

View File

@@ -0,0 +1,15 @@
#: E231
a = (1,2)
#: E231
a[b1,:]
#: E231
a = [{'a':''}]
#: Okay
a = (4,)
b = (5, )
c = {'text': text[5:]}
result = {
'key1': 'value',
'key2': 'value',
}

View File

@@ -54,3 +54,7 @@ if type(a) != type(b) or type(a) == type(ccc):
pass
assert type(res) == type(None)
types = StrEnum
if x == types.X:
pass

View File

@@ -0,0 +1,3 @@
def lorem():
"""lorem ipsum dolor sit amet consectetur adipiscing elit
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"""

View File

@@ -2,6 +2,8 @@
from functools import cached_property
from gi.repository import GObject
# Bad examples
def bad_liouiwnlkjl():
@@ -76,6 +78,11 @@ class Thingy:
"""This property method docstring does not need to be written in imperative mood."""
return self._beep
@GObject.Property
def good_custom_property(self):
"""This property method docstring does not need to be written in imperative mood."""
return self._beep
@cached_property
def good_cached_property(self):
"""This property method docstring does not need to be written in imperative mood."""

View File

@@ -0,0 +1 @@
''' SAM macro definitions '''

View File

@@ -58,6 +58,15 @@ def no_underline_and_no_description(): # noqa: D416
"""
@expect(_D213)
@expect("D407: Missing dashed underline after section ('Returns')")
@expect("D414: Section has no content ('Returns')")
def no_underline_and_no_newline(): # noqa: D416
"""Toggle the gizmo.
Returns"""
@expect(_D213)
@expect("D410: Missing blank line after section ('Returns')")
@expect("D414: Section has no content ('Returns')")

View File

@@ -0,0 +1,23 @@
"""Test case: strings used within calls within type annotations."""
from typing import Callable
import bpy
from mypy_extensions import VarArg
from foo import Bar
class LightShow(bpy.types.Operator):
label = "Create Character"
name = "lightshow.letter_creation"
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # OK
def f(x: Callable[[VarArg("os")], None]): # F821
pass
f(Callable[["Bar"], None])
f(Callable[["Baz"], None])

View File

@@ -0,0 +1,25 @@
"""Test case: strings used within calls within type annotations."""
from __future__ import annotations
from typing import Callable
import bpy
from mypy_extensions import VarArg
from foo import Bar
class LightShow(bpy.types.Operator):
label = "Create Character"
name = "lightshow.letter_creation"
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # OK
def f(x: Callable[[VarArg("os")], None]): # F821
pass
f(Callable[["Bar"], None])
f(Callable[["Baz"], None])

View File

@@ -0,0 +1,23 @@
x = "a string"
y = "another string"
z = ""
def errors():
if x is "" or x == "":
print("x is an empty string")
if y is not "" or y != "":
print("y is not an empty string")
if "" != z:
print("z is an empty string")
def ok():
if x and not y:
print("x is not an empty string, but y is an empty string")
data.loc[data["a"] != ""]
data.loc[data["a"] != "", :]

View File

@@ -0,0 +1,95 @@
while True:
try:
pass
finally:
continue # [continue-in-finally]
while True:
try:
pass
except Exception:
continue
finally:
try:
pass
finally:
continue # [continue-in-finally]
pass
while True:
try:
pass
finally:
test = "aa"
match test:
case "aa":
continue # [continue-in-finally]
while True:
try:
pass
finally:
with "aa" as f:
continue # [continue-in-finally]
while True:
try:
pass
finally:
if True:
continue # [continue-in-finally]
continue # [continue-in-finally]
def test():
while True:
continue
try:
pass
finally:
continue # [continue-in-finally]
while True:
try:
pass
finally:
continue # [continue-in-finally]
def test():
while True:
continue
while True:
try:
pass
finally:
for i in range(12):
continue
continue # [continue-in-finally]
while True:
pass
else:
continue # [continue-in-finally]
def test():
continue
while True:
continue
while True:
try:
pass
finally:
if True:
pass
elif False:
continue # [continue-in-finally]
else:
continue # [continue-in-finally]
for i in range(10):
pass
else:
continue # [continue-in-finally]

Binary file not shown.

View File

@@ -0,0 +1,12 @@
import os
tempVar = os.getenv("TEST", 12) # [invalid-envvar-default]
goodVar = os.getenv("TESTING", None)
dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default]
print(os.getenv("TEST", False)) # [invalid-envvar-default]
os.getenv("AA", "GOOD")
os.getenv("AA", f"GOOD")
os.getenv("AA", "GOOD" + "BAD")
os.getenv("AA", "GOOD" + 1)
os.getenv("AA", "GOOD %s" % "BAD")
os.getenv("B", Z)

View File

@@ -0,0 +1,15 @@
import os
os.getenv(1) # [invalid-envvar-value]
os.getenv("a")
os.getenv("test")
os.getenv(key="testingAgain")
os.getenv(key=11) # [invalid-envvar-value]
os.getenv(["hello"]) # [invalid-envvar-value]
os.getenv(key="foo", default="bar")
os.getenv(key=f"foo", default="bar")
os.getenv(key="foo" + "bar", default=1)
os.getenv(key=1 + "bar", default=1) # [invalid-envvar-value]
AA = "aa"
os.getenv(AA)

View File

@@ -11,6 +11,9 @@ if 10 == 100: # [comparison-of-constants] R0133
if 1 == 3: # [comparison-of-constants] R0133
pass
if 1 == -3: # [comparison-of-constants] R0133
pass
x = 0
if 4 == 3 == x: # [comparison-of-constants] R0133
pass
@@ -35,6 +38,12 @@ if argc != 1: # correct
if argc != 2: # [magic-value-comparison]
pass
if argc != -2: # [magic-value-comparison]
pass
if argc != +2: # [magic-value-comparison]
pass
if __name__ == "__main__": # correct
pass

View File

@@ -0,0 +1,39 @@
import sys
def print_python_version():
print(sys.version)
return None # [useless-return]
def print_python_version():
print(sys.version)
return None # [useless-return]
def print_python_version():
print(sys.version)
return None # [useless-return]
class SomeClass:
def print_python_version(self):
print(sys.version)
return None # [useless-return]
def print_python_version():
if 2 * 2 == 4:
return
print(sys.version)
def print_python_version():
if 2 * 2 == 4:
return None
return
def print_python_version():
if 2 * 2 == 4:
return None

View File

@@ -25,22 +25,7 @@ avoid-escape = true
max-complexity = 10
[tool.ruff.pep8-naming]
ignore-names = [
"setUp",
"tearDown",
"setUpClass",
"tearDownClass",
"setUpModule",
"tearDownModule",
"asyncSetUp",
"asyncTearDown",
"setUpTestData",
"failureException",
"longMessage",
"maxDiff",
]
classmethod-decorators = ["classmethod", "pydantic.validator"]
staticmethod-decorators = ["staticmethod"]
classmethod-decorators = ["pydantic.validator"]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "parents"

View File

@@ -12,11 +12,17 @@ Ipsum
""".encode(
"utf-8"
)
# b"""
# Lorem
#
# Ipsum
# """
(
"Lorem "
"Ipsum".encode()
)
(
"Lorem " # Comment
"Ipsum".encode() # Comment
)
(
"Lorem " "Ipsum".encode()
)
# `encode` on variables should not be processed.
string = "hello there"

View File

@@ -0,0 +1,10 @@
import socket
from kombu import Connection, exceptions
try:
conn = Connection(settings.CELERY_BROKER_URL)
conn.ensure_connection(max_retries=2)
conn._close()
except (socket.error, exceptions.OperationalError):
return HttpResponseServerError("cache: cannot connect to broker.")

View File

@@ -1,24 +1,29 @@
# These should be changed
# Error (`from unittest import mock`)
if True:
import mock
# Error (`from unittest import mock`)
if True:
import mock, sys
# This goes to from unitest import mock
# Error (`from unittest.mock import *`)
if True:
from mock import *
# Error (`from unittest import mock`)
import mock.mock
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
import contextlib, mock, sys
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
import mock, sys
x = "This code should be preserved one line below the mock"
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
from mock import mock
# Should keep trailing comma
# Error (keep trailing comma)
from mock import (
mock,
a,
@@ -32,7 +37,7 @@ from mock import (
mock,
)
# Should not get a trailing comma
# Error (avoid trailing comma)
from mock import (
mock,
a,
@@ -57,16 +62,16 @@ if True:
c
)
# These should not change:
# OK
import os, io
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
import mock, mock
# Mock should go on a new line as `from unittest import mock as foo`
# Error (`from unittest import mock as foo`)
import mock as foo
# Mock should go on a new line as `from unittest import mock as foo`
# Error (`from unittest import mock as foo`)
from mock import mock as foo
if True:
@@ -81,8 +86,8 @@ if True:
from mock import mock as foo, mock as bar, mock
# This should be unchanged.
# OK.
x = mock.Mock()
# This should change to `mock.Mock`().
# Error (`mock.Mock()`).
x = mock.mock.Mock()

View File

@@ -41,7 +41,7 @@ if True:
Good,
)
from typing import Callable, Match, Pattern, List
from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet, ContextManager
if True: from collections import (
Mapping, Counter)

View File

@@ -0,0 +1,9 @@
isinstance(1, (int, float)) # UP038
issubclass("yes", (int, float, str)) # UP038
isinstance(1, int) # OK
issubclass("yes", int) # OK
isinstance(1, int | float) # OK
issubclass("yes", int | str) # OK
isinstance(1, ()) # OK
isinstance(1, (int, *(str, bytes))) # OK

View File

@@ -0,0 +1,19 @@
input = [1, 2, 3]
otherInput = [2, 3, 4]
# OK
zip(input, otherInput) # different inputs
zip(input, otherInput[1:]) # different inputs
zip(input, input[2:]) # not successive
zip(input[:-1], input[2:]) # not successive
list(zip(input, otherInput)) # nested call
zip(input, input[1::2]) # not successive
# Errors
zip(input, input[1:])
zip(input, input[1::1])
zip(input[:-1], input[1:])
zip(input[1:], input[2:])
zip(input[1:-1], input[2:])
list(zip(input, input[1:]))
list(zip(input[:-1], input[1:]))

View File

@@ -0,0 +1,13 @@
# noqa
# noqa # comment
print() # noqa
print() # noqa # comment
print(a) # noqa
print(a) # noqa # comment
# noqa: E501, F821
# noqa: E501, F821 # comment
print() # noqa: E501, F821
print() # noqa: E501, F821 # comment
print(a) # noqa: E501, F821
print(a) # noqa: E501, F821 # comment

View File

@@ -1,279 +0,0 @@
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Arguments, Expr, Keyword, Located, Location, Stmt};
fn id() -> usize {
static COUNTER: AtomicUsize = AtomicUsize::new(1);
COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone)]
pub enum Node<'a> {
Stmt(&'a Stmt),
Expr(&'a Expr),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Range {
pub location: Location,
pub end_location: Location,
}
impl Range {
pub const fn new(location: Location, end_location: Location) -> Self {
Self {
location,
end_location,
}
}
pub fn from_located<T, U>(located: &Located<T, U>) -> Self {
Range::new(located.location, located.end_location.unwrap())
}
pub fn contains(&self, other: &Range) -> bool {
self.location <= other.location && self.end_location >= other.end_location
}
}
#[derive(Debug)]
pub struct FunctionDef<'a> {
// Properties derived from StmtKind::FunctionDef.
pub name: &'a str,
pub args: &'a Arguments,
pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// pub returns: Option<&'a Expr>,
// pub type_comment: Option<&'a str>,
// Scope-specific properties.
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
pub async_: bool,
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct ClassDef<'a> {
// Properties derived from StmtKind::ClassDef.
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
// pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// Scope-specific properties.
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct Lambda<'a> {
pub args: &'a Arguments,
pub body: &'a Expr,
}
#[derive(Debug)]
pub enum ScopeKind<'a> {
Class(ClassDef<'a>),
Function(FunctionDef<'a>),
Generator,
Module,
Lambda(Lambda<'a>),
}
#[derive(Debug)]
pub struct Scope<'a> {
pub id: usize,
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub uses_locals: bool,
/// A map from bound name to binding index, for live bindings.
pub bindings: FxHashMap<&'a str, usize>,
/// A map from bound name to binding index, for bindings that were created
/// in the scope but rebound (and thus overridden) later on in the same
/// scope.
pub rebounds: FxHashMap<&'a str, Vec<usize>>,
}
impl<'a> Scope<'a> {
pub fn new(kind: ScopeKind<'a>) -> Self {
Scope {
id: id(),
kind,
import_starred: false,
uses_locals: false,
bindings: FxHashMap::default(),
rebounds: FxHashMap::default(),
}
}
}
// Pyflakes defines the following binding hierarchy (via inheritance):
// Binding
// ExportBinding
// Annotation
// Argument
// Assignment
// NamedExprAssignment
// Definition
// FunctionDefinition
// ClassDefinition
// Builtin
// Importation
// SubmoduleImportation
// ImportationFrom
// StarImportation
// FutureImportation
#[derive(Clone, Debug, is_macro::Is)]
pub enum BindingKind<'a> {
Annotation,
Argument,
Assignment,
Binding,
LoopVar,
Global,
Nonlocal,
Builtin,
ClassDefinition,
FunctionDefinition,
Export(Vec<String>),
FutureImportation,
StarImportation(Option<usize>, Option<String>),
Importation(&'a str, &'a str),
FromImportation(&'a str, String),
SubmoduleImportation(&'a str, &'a str),
}
#[derive(Debug, Clone)]
pub struct Binding<'a> {
pub kind: BindingKind<'a>,
pub range: Range,
/// The context in which the binding was created.
pub context: ExecutionContext,
/// The statement in which the [`Binding`] was defined.
pub source: Option<RefEquality<'a, Stmt>>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a runtime context.
pub runtime_usage: Option<(usize, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a typing-time context.
pub typing_usage: Option<(usize, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a synthetic context. This is used for
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
/// that should be considered used even if they're never referenced.
pub synthetic_usage: Option<(usize, Range)>,
}
#[derive(Copy, Debug, Clone)]
pub enum ExecutionContext {
Runtime,
Typing,
}
impl<'a> Binding<'a> {
pub fn mark_used(&mut self, scope: usize, range: Range, context: ExecutionContext) {
match context {
ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)),
ExecutionContext::Typing => self.typing_usage = Some((scope, range)),
}
}
pub const fn used(&self) -> bool {
self.runtime_usage.is_some()
|| self.synthetic_usage.is_some()
|| self.typing_usage.is_some()
}
pub const fn is_definition(&self) -> bool {
matches!(
self.kind,
BindingKind::ClassDefinition
| BindingKind::FunctionDefinition
| BindingKind::Builtin
| BindingKind::FutureImportation
| BindingKind::StarImportation(..)
| BindingKind::Importation(..)
| BindingKind::FromImportation(..)
| BindingKind::SubmoduleImportation(..)
)
}
pub fn redefines(&self, existing: &'a Binding) -> bool {
match &self.kind {
BindingKind::Importation(.., full_name) => {
if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind {
return full_name == existing;
}
}
BindingKind::FromImportation(.., full_name) => {
if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind {
return full_name == existing;
}
}
BindingKind::SubmoduleImportation(.., full_name) => match &existing.kind {
BindingKind::Importation(.., existing)
| BindingKind::SubmoduleImportation(.., existing) => {
return full_name == existing;
}
BindingKind::FromImportation(.., existing) => {
return full_name == existing;
}
_ => {}
},
BindingKind::Annotation => {
return false;
}
BindingKind::FutureImportation => {
return false;
}
BindingKind::StarImportation(..) => {
return false;
}
_ => {}
}
existing.is_definition()
}
}
#[derive(Debug, Copy, Clone)]
pub struct RefEquality<'a, T>(pub &'a T);
impl<'a, T> std::hash::Hash for RefEquality<'a, T> {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
(self.0 as *const T).hash(state);
}
}
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<'b, T>) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl<'a, T> Eq for RefEquality<'a, T> {}
impl<'a, T> Deref for RefEquality<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.0
}
}
impl<'a> From<&RefEquality<'a, Stmt>> for &'a Stmt {
fn from(r: &RefEquality<'a, Stmt>) -> Self {
r.0
}
}
impl<'a> From<&RefEquality<'a, Expr>> for &'a Expr {
fn from(r: &RefEquality<'a, Expr>) -> Self {
r.0
}
}
pub type CallPath<'a> = smallvec::SmallVec<[&'a str; 8]>;

View File

@@ -6,14 +6,14 @@ use libcst_native::{
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
use rustpython_parser::{lexer, Mode, Tok};
use crate::ast::helpers;
use crate::ast::helpers::to_absolute;
use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline;
use ruff_diagnostics::Fix;
use ruff_python_ast::helpers;
use ruff_python_ast::helpers::to_absolute;
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_module;
use crate::fix::Fix;
use crate::source_code::{Indexer, Locator, Stylist};
/// Determine if a body contains only a single statement, taking into account
/// deleted.
@@ -100,7 +100,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
/// of a multi-statement line.
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
let contents = locator.skip(stmt.end_location.unwrap());
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
if trimmed.starts_with(';') {
let column = line
@@ -123,7 +123,7 @@ fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
let start_location = Location::new(semicolon.row(), semicolon.column() + 1);
let contents = locator.skip(start_location);
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
// Skip past any continuations.
if trimmed.starts_with('\\') {
@@ -227,7 +227,7 @@ pub fn remove_unused_imports<'a>(
indexer: &Indexer,
stylist: &Stylist,
) -> Result<Fix> {
let module_text = locator.slice(&Range::from_located(stmt));
let module_text = locator.slice(stmt);
let mut tree = match_module(module_text)?;
let Some(Statement::Simple(body)) = tree.body.first_mut() else {
@@ -450,8 +450,9 @@ mod tests {
use rustpython_parser as parser;
use rustpython_parser::ast::Location;
use ruff_python_ast::source_code::Locator;
use crate::autofix::helpers::{next_stmt_break, trailing_semicolon};
use crate::source_code::Locator;
#[test]
fn find_semicolon() -> Result<()> {

View File

@@ -4,11 +4,12 @@ use itertools::Itertools;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::fix::Fix;
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
use crate::linter::FixTable;
use crate::registry::Diagnostic;
use crate::source_code::Locator;
use crate::registry::{AsRule, Rule};
pub mod helpers;
@@ -38,7 +39,7 @@ fn apply_fixes<'a>(
.as_ref()
.map(|fix| (diagnostic.kind.rule(), fix))
})
.sorted_by_key(|(.., fix)| fix.location)
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
{
// If we already applied an identical fix as part of another correction, skip
// any re-application.
@@ -54,7 +55,7 @@ fn apply_fixes<'a>(
}
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice(&Range::new(last_pos, fix.location));
let slice = locator.slice(Range::new(last_pos, fix.location));
output.push_str(slice);
// Add the patch itself.
@@ -78,7 +79,7 @@ pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String {
let mut output = String::with_capacity(locator.len());
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice(&Range::new(Location::new(1, 0), fix.location));
let slice = locator.slice(Range::new(Location::new(1, 0), fix.location));
output.push_str(slice);
// Add the patch itself.
@@ -91,47 +92,54 @@ pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String {
output
}
/// Compare two fixes.
fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering {
fix1.location
.cmp(&fix2.location)
.then_with(|| match (&rule1, &rule2) {
// Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes.
(Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less,
(Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod) => std::cmp::Ordering::Greater,
_ => std::cmp::Ordering::Equal,
})
}
#[cfg(test)]
mod tests {
use rustpython_parser::ast::Location;
use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Fix;
use ruff_python_ast::source_code::Locator;
use crate::autofix::{apply_fix, apply_fixes};
use crate::fix::Fix;
use crate::registry::Diagnostic;
use crate::rules::pycodestyle::rules::NoNewLineAtEndOfFile;
use crate::source_code::Locator;
#[test]
fn empty_file() {
let fixes: Vec<Diagnostic> = vec![];
let locator = Locator::new(r#""#);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(contents, "");
assert_eq!(fixed.values().sum::<usize>(), 0);
}
impl From<Fix> for Diagnostic {
fn from(fix: Fix) -> Self {
Diagnostic {
fn create_diagnostics(fixes: impl IntoIterator<Item = Fix>) -> Vec<Diagnostic> {
fixes
.into_iter()
.map(|fix| Diagnostic {
// The choice of rule here is arbitrary.
kind: NoNewLineAtEndOfFile.into(),
location: fix.location,
end_location: fix.end_location,
fix: Some(fix),
parent: None,
}
}
})
.collect()
}
#[test]
fn empty_file() {
let locator = Locator::new(r#""#);
let diagnostics = create_diagnostics([]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(contents, "");
assert_eq!(fixed.values().sum::<usize>(), 0);
}
#[test]
fn apply_one_replacement() {
let fixes: Vec<Diagnostic> = vec![Fix {
content: "Bar".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 14),
}
.into()];
let locator = Locator::new(
r#"
class A(object):
@@ -139,7 +147,12 @@ class A(object):
"#
.trim(),
);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
let diagnostics = create_diagnostics([Fix {
content: "Bar".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 14),
}]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
contents,
r#"
@@ -153,12 +166,6 @@ class A(Bar):
#[test]
fn apply_one_removal() {
let fixes: Vec<Diagnostic> = vec![Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
}
.into()];
let locator = Locator::new(
r#"
class A(object):
@@ -166,7 +173,12 @@ class A(object):
"#
.trim(),
);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
let diagnostics = create_diagnostics([Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
}]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
contents,
r#"
@@ -180,20 +192,6 @@ class A:
#[test]
fn apply_two_removals() {
let fixes: Vec<Diagnostic> = vec![
Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 16),
}
.into(),
Fix {
content: String::new(),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
}
.into(),
];
let locator = Locator::new(
r#"
class A(object, object):
@@ -201,7 +199,19 @@ class A(object, object):
"#
.trim(),
);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
let diagnostics = create_diagnostics([
Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 16),
},
Fix {
content: String::new(),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
},
]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
contents,
@@ -216,20 +226,6 @@ class A:
#[test]
fn ignore_overlapping_fixes() {
let fixes: Vec<Diagnostic> = vec![
Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
}
.into(),
Fix {
content: "ignored".to_string(),
location: Location::new(1, 9),
end_location: Location::new(1, 11),
}
.into(),
];
let locator = Locator::new(
r#"
class A(object):
@@ -237,7 +233,19 @@ class A(object):
"#
.trim(),
);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
let diagnostics = create_diagnostics([
Fix {
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
},
Fix {
content: "ignored".to_string(),
location: Location::new(1, 9),
end_location: Location::new(1, 11),
},
]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
assert_eq!(
contents,
r#"

View File

@@ -0,0 +1,25 @@
use ruff_python_ast::scope::ScopeStack;
use rustpython_parser::ast::{Expr, Stmt};
use ruff_python_ast::types::Range;
use ruff_python_ast::types::RefEquality;
use ruff_python_ast::visibility::{Visibility, VisibleScope};
use crate::checkers::ast::AnnotationContext;
use crate::docstrings::definition::Definition;
type Context<'a> = (ScopeStack, Vec<RefEquality<'a, Stmt>>);
/// A collection of AST nodes that are deferred for later analysis.
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
/// module-level definitions have been analyzed.
#[derive(Default)]
pub struct Deferred<'a> {
pub definitions: Vec<(Definition<'a>, Visibility, Context<'a>)>,
pub string_type_definitions: Vec<(Range, &'a str, AnnotationContext, Context<'a>)>,
pub type_definitions: Vec<(&'a Expr, AnnotationContext, Context<'a>)>,
pub functions: Vec<(&'a Stmt, Context<'a>, VisibleScope)>,
pub lambdas: Vec<(&'a Expr, Context<'a>)>,
pub for_loops: Vec<(&'a Stmt, Context<'a>)>,
pub assignments: Vec<Context<'a>>,
}

View File

@@ -1,6 +1,8 @@
use std::path::Path;
use crate::registry::{Diagnostic, Rule};
use ruff_diagnostics::Diagnostic;
use crate::registry::Rule;
use crate::rules::flake8_no_pep420::rules::implicit_namespace_package;
use crate::rules::pep8_naming::rules::invalid_module_name;
use crate::settings::Settings;
@@ -13,7 +15,7 @@ pub fn check_file_path(
let mut diagnostics: Vec<Diagnostic> = vec![];
// flake8-no-pep420
if settings.rules.enabled(&Rule::ImplicitNamespacePackage) {
if settings.rules.enabled(Rule::ImplicitNamespacePackage) {
if let Some(diagnostic) =
implicit_namespace_package(path, package, &settings.project_root, &settings.src)
{
@@ -22,7 +24,7 @@ pub fn check_file_path(
}
// pep8-naming
if settings.rules.enabled(&Rule::InvalidModuleName) {
if settings.rules.enabled(Rule::InvalidModuleName) {
if let Some(diagnostic) = invalid_module_name(path, package) {
diagnostics.push(diagnostic);
}

View File

@@ -4,13 +4,15 @@ use std::path::Path;
use rustpython_parser::ast::Suite;
use crate::ast::visitor::Visitor;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_ast::visitor::Visitor;
use crate::directives::IsortDirectives;
use crate::registry::{Diagnostic, Rule};
use crate::registry::Rule;
use crate::rules::isort;
use crate::rules::isort::track::{Block, ImportTracker};
use crate::settings::{flags, Settings};
use crate::source_code::{Indexer, Locator, Stylist};
#[allow(clippy::too_many_arguments)]
pub fn check_imports(
@@ -36,7 +38,7 @@ pub fn check_imports(
// Enforce import rules.
let mut diagnostics = vec![];
if settings.rules.enabled(&Rule::UnsortedImports) {
if settings.rules.enabled(Rule::UnsortedImports) {
for block in &blocks {
if !block.imports.is_empty() {
if let Some(diagnostic) = isort::rules::organize_imports(
@@ -47,7 +49,7 @@ pub fn check_imports(
}
}
}
if settings.rules.enabled(&Rule::MissingRequiredImport) {
if settings.rules.enabled(Rule::MissingRequiredImport) {
diagnostics.extend(isort::rules::add_required_imports(
&blocks, python_ast, locator, stylist, settings, autofix,
));

View File

@@ -1,18 +1,23 @@
#![allow(dead_code, unused_imports, unused_variables)]
use bisection::bisect_left;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer::LexResult;
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::{Locator, Stylist};
use ruff_python_ast::types::Range;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
use crate::rules::pycodestyle::rules::{
extraneous_whitespace, indentation, missing_whitespace_after_keyword, space_around_operator,
whitespace_around_keywords, whitespace_around_named_parameter_equals,
whitespace_before_comment,
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords,
whitespace_around_named_parameter_equals, whitespace_before_comment,
whitespace_before_parameters,
};
use crate::settings::Settings;
use crate::source_code::{Locator, Stylist};
use crate::settings::{flags, Settings};
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
fn expand_indent(mut line: &str) -> usize {
@@ -40,6 +45,7 @@ pub fn check_logical_lines(
locator: &Locator,
stylist: &Stylist,
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
@@ -53,7 +59,7 @@ pub fn check_logical_lines(
// Extract the indentation level.
let start_loc = line.mapping[0].1;
let start_line = locator.slice(&Range::new(Location::new(start_loc.row(), 0), start_loc));
let start_line = locator.slice(Range::new(Location::new(start_loc.row(), 0), start_loc));
let indent_level = expand_indent(start_line);
let indent_size = 4;
@@ -147,6 +153,44 @@ pub fn check_logical_lines(
});
}
}
for (location, kind) in missing_whitespace_around_operator(&line.tokens) {
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: None,
parent: None,
});
}
}
#[cfg(feature = "logical_lines")]
let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
#[cfg(not(feature = "logical_lines"))]
let should_fix = false;
for diagnostic in missing_whitespace(&line.text, start_loc.row(), should_fix) {
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
}
}
if line.flags.contains(TokenFlags::BRACKET) {
#[cfg(feature = "logical_lines")]
let should_fix =
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
#[cfg(not(feature = "logical_lines"))]
let should_fix = false;
for diagnostic in whitespace_before_parameters(&line.tokens, should_fix) {
if settings.rules.enabled(diagnostic.kind.rule()) {
diagnostics.push(diagnostic);
}
}
}
for (index, kind) in indentation(
@@ -183,8 +227,9 @@ mod tests {
use rustpython_parser::lexer::LexResult;
use rustpython_parser::{lexer, Mode};
use ruff_python_ast::source_code::Locator;
use crate::checkers::logical_lines::iter_logical_lines;
use crate::source_code::Locator;
#[test]
fn split_logical_lines() {
@@ -261,7 +306,7 @@ f()"#;
.into_iter()
.map(|line| line.text)
.collect();
let expected = vec!["def f():", "\"xxx\"", "", "x = 1", "f()"];
let expected = vec!["def f():", "\"xxxxxxxxxxxxxxxxxxxx\"", "", "x = 1", "f()"];
assert_eq!(actual, expected);
}
}

View File

@@ -4,12 +4,14 @@ use log::warn;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
use crate::fix::Fix;
use crate::noqa;
use crate::noqa::{extract_file_exemption, Directive, Exemption};
use crate::registry::{Diagnostic, DiagnosticKind, Rule};
use crate::registry::{AsRule, Rule};
use crate::rule_redirects::get_redirect_target;
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
use crate::settings::{flags, Settings};
@@ -21,8 +23,8 @@ pub fn check_noqa(
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: flags::Autofix,
) {
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
) -> Vec<usize> {
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -37,7 +39,7 @@ pub fn check_noqa(
// Indices of diagnostics that were ignored by a `noqa` directive.
let mut ignored_diagnostics = vec![];
let lines: Vec<&str> = contents.lines().collect();
let lines: Vec<&str> = contents.universal_newlines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
@@ -65,7 +67,7 @@ pub fn check_noqa(
// Remove any ignored diagnostics.
for (index, diagnostic) in diagnostics.iter().enumerate() {
if matches!(diagnostic.kind, DiagnosticKind::BlanketNOQA(..)) {
if matches!(diagnostic.kind.rule(), Rule::BlanketNOQA) {
continue;
}
@@ -96,7 +98,7 @@ pub fn check_noqa(
ignored_diagnostics.push(index);
continue;
}
(Directive::Codes(.., codes), matches) => {
(Directive::Codes(.., codes, _), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
@@ -123,7 +125,7 @@ pub fn check_noqa(
ignored_diagnostics.push(index);
continue;
}
(Directive::Codes(.., codes), matches) => {
(Directive::Codes(.., codes, _), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
@@ -139,7 +141,7 @@ pub fn check_noqa(
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
Directive::All(spaces, start_byte, end_byte) => {
Directive::All(leading_spaces, start_byte, end_byte, trailing_spaces) => {
if matches.is_empty() {
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
@@ -149,15 +151,27 @@ pub fn check_noqa(
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
if start - leading_spaces == 0 && end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
}
diagnostics.push(diagnostic);
}
}
Directive::Codes(spaces, start_byte, end_byte, codes) => {
Directive::Codes(leading_spaces, start_byte, end_byte, codes, trailing_spaces) => {
let mut disabled_codes = vec![];
let mut unknown_codes = vec![];
let mut unmatched_codes = vec![];
@@ -174,7 +188,7 @@ pub fn check_noqa(
valid_codes.push(code);
} else {
if let Ok(rule) = Rule::from_code(code) {
if settings.rules.enabled(&rule) {
if settings.rules.enabled(rule) {
unmatched_codes.push(code);
} else {
disabled_codes.push(code);
@@ -217,15 +231,28 @@ pub fn check_noqa(
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
if start - leading_spaces == 0 && end == lines[row].chars().count()
{
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
} else {
diagnostic.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, lines[row].chars().count()),
Location::new(row + 1, end),
));
}
}
@@ -238,7 +265,5 @@ pub fn check_noqa(
}
ignored_diagnostics.sort_unstable();
for index in ignored_diagnostics.iter().rev() {
diagnostics.swap_remove(*index);
}
ignored_diagnostics
}

View File

@@ -2,7 +2,11 @@
use std::path::Path;
use crate::registry::{Diagnostic, Rule};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::registry::Rule;
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
@@ -15,12 +19,11 @@ use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pylint;
use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
use crate::settings::{flags, Settings};
use crate::source_code::Stylist;
pub fn check_physical_lines(
path: &Path,
locator: &Locator,
stylist: &Stylist,
contents: &str,
commented_lines: &[usize],
doc_lines: &[usize],
settings: &Settings,
@@ -29,32 +32,32 @@ pub fn check_physical_lines(
let mut diagnostics: Vec<Diagnostic> = vec![];
let mut has_any_shebang = false;
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(&Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(&Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(&Rule::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(&Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(&Rule::ShebangPython);
let enforce_blanket_type_ignore = settings.rules.enabled(&Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(&Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(&Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(&Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace);
let enforce_blanket_noqa = settings.rules.enabled(Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(Rule::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangPython);
let enforce_blanket_type_ignore = settings.rules.enabled(Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings.rules.enabled(Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(&Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(&Rule::IndentationContainsTabs);
settings.rules.enabled(Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(Rule::IndentationContainsTabs);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
autofix.into() && settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace =
autofix.into() && settings.rules.should_fix(&Rule::ShebangWhitespace);
autofix.into() && settings.rules.should_fix(Rule::ShebangWhitespace);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
for (index, line) in contents.lines().enumerate() {
for (index, line) in locator.contents().universal_newlines().enumerate() {
while commented_lines_iter
.next_if(|lineno| &(index + 1) == *lineno)
.is_some()
@@ -160,9 +163,9 @@ pub fn check_physical_lines(
if enforce_no_newline_at_end_of_file {
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
contents,
autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
autofix.into() && settings.rules.should_fix(Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}
@@ -179,13 +182,14 @@ pub fn check_physical_lines(
#[cfg(test)]
mod tests {
use std::path::Path;
use super::check_physical_lines;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::registry::Rule;
use crate::settings::{flags, Settings};
use crate::source_code::{Locator, Stylist};
use super::check_physical_lines;
#[test]
fn e501_non_ascii_char() {
@@ -196,8 +200,8 @@ mod tests {
let check_with_max_line_length = |line_length: usize| {
check_physical_lines(
Path::new("foo.py"),
&locator,
&stylist,
line,
&[],
&[],
&Settings {

View File

@@ -4,57 +4,62 @@ use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{Diagnostic, Rule};
use crate::registry::{AsRule, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, pyupgrade,
ruff,
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle,
pylint, pyupgrade, ruff,
};
use crate::settings::{flags, Settings};
use crate::source_code::Locator;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::Locator;
pub fn check_tokens(
locator: &Locator,
tokens: &[LexResult],
settings: &Settings,
autofix: flags::Autofix,
is_stub: bool,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
let enforce_ambiguous_unicode_character = settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterString)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterDocstring)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterComment);
let enforce_quotes = settings.rules.enabled(&Rule::BadQuotesInlineString)
|| settings.rules.enabled(&Rule::BadQuotesMultilineString)
|| settings.rules.enabled(&Rule::BadQuotesDocstring)
|| settings.rules.enabled(&Rule::AvoidableEscapedQuote);
let enforce_commented_out_code = settings.rules.enabled(&Rule::CommentedOutCode);
let enforce_compound_statements = settings
.rules
.enabled(&Rule::MultipleStatementsOnOneLineColon)
|| settings
.rules
.enabled(&Rule::MultipleStatementsOnOneLineSemicolon)
|| settings.rules.enabled(&Rule::UselessSemicolon);
let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings
.rules
.enabled(&Rule::SingleLineImplicitStringConcatenation)
|| settings
.rules
.enabled(&Rule::MultiLineImplicitStringConcatenation);
let enforce_trailing_comma = settings.rules.enabled(&Rule::TrailingCommaMissing)
|| settings
.rules
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
let enforce_ambiguous_unicode_character = settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]);
let enforce_invalid_string_character = settings.rules.any_enabled(&[
Rule::InvalidCharacterBackspace,
Rule::InvalidCharacterSub,
Rule::InvalidCharacterEsc,
Rule::InvalidCharacterNul,
Rule::InvalidCharacterZeroWidthSpace,
]);
let enforce_quotes = settings.rules.any_enabled(&[
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
]);
let enforce_commented_out_code = settings.rules.enabled(Rule::CommentedOutCode);
let enforce_compound_statements = settings.rules.any_enabled(&[
Rule::MultipleStatementsOnOneLineColon,
Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon,
]);
let enforce_invalid_escape_sequence = settings.rules.enabled(Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]);
let enforce_trailing_comma = settings.rules.any_enabled(&[
Rule::TrailingCommaMissing,
Rule::TrailingCommaOnBareTupleProhibited,
Rule::TrailingCommaProhibited,
]);
let enforce_extraneous_parenthesis = settings.rules.enabled(Rule::ExtraneousParentheses);
let enforce_type_comment_in_stub = settings.rules.enabled(Rule::TypeCommentInStub);
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
@@ -108,11 +113,23 @@ pub fn check_tokens(
locator,
*start,
*end,
autofix.into() && settings.rules.should_fix(&Rule::InvalidEscapeSequence),
autofix.into() && settings.rules.should_fix(Rule::InvalidEscapeSequence),
));
}
}
}
// PLE2510, PLE2512, PLE2513
if enforce_invalid_string_character {
for (start, tok, end) in tokens.iter().flatten() {
if matches!(tok, Tok::String { .. }) {
diagnostics.extend(
pylint::rules::invalid_string_characters(locator, *start, *end, autofix.into())
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
}
}
}
// E701, E702, E703
if enforce_compound_statements {
@@ -161,5 +178,10 @@ pub fn check_tokens(
);
}
// PYI033
if enforce_type_comment_in_stub && is_stub {
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(tokens));
}
diagnostics
}

View File

@@ -1,5 +1,23 @@
use crate::registry::{Linter, Rule};
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct NoqaCode(&'static str, &'static str);
impl std::fmt::Display for NoqaCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}{}", self.0, self.1)
}
}
impl PartialEq<&str> for NoqaCode {
fn eq(&self, other: &&str) -> bool {
match other.strip_prefix(self.0) {
Some(suffix) => suffix == self.1,
None => false,
}
}
}
#[ruff_macros::map_codes]
pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[allow(clippy::enum_glob_use)]
@@ -29,6 +47,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E211") => Rule::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
@@ -37,6 +57,16 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E224") => Rule::TabAfterOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E225") => Rule::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E226") => Rule::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E227") => Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E228") => Rule::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E231") => Rule::MissingWhitespace,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E251") => Rule::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
(Pycodestyle, "E252") => Rule::MissingWhitespaceAroundParameterEquals,
@@ -131,34 +161,44 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyflakes, "901") => Rule::RaiseNotImplemented,
// pylint
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C1901") => Rule::CompareToEmptyString,
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0100") => Rule::YieldInInit,
(Pylint, "E0101") => Rule::ReturnInInit,
(Pylint, "E0116") => Rule::ContinueInFinally,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
(Pylint, "E1307") => Rule::BadStringFormatType,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1507") => Rule::InvalidEnvvarValue,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E2510") => Rule::InvalidCharacterBackspace,
(Pylint, "E2512") => Rule::InvalidCharacterSub,
(Pylint, "E2513") => Rule::InvalidCharacterEsc,
(Pylint, "E2514") => Rule::InvalidCharacterNul,
(Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1711") => Rule::UselessReturn,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "W2901") => Rule::RedefinedLoopName,
// flake8-builtins
@@ -193,7 +233,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Bugbear, "025") => Rule::DuplicateTryBlockException,
(Flake8Bugbear, "026") => Rule::StarArgUnpackingAfterKeywordArg,
(Flake8Bugbear, "027") => Rule::EmptyMethodWithoutAbstractDecorator,
(Flake8Bugbear, "028") => Rule::NoExplicitStacklevel,
(Flake8Bugbear, "029") => Rule::ExceptWithEmptyTuple,
(Flake8Bugbear, "030") => Rule::ExceptWithNonExceptionClasses,
(Flake8Bugbear, "032") => Rule::UnintentionalTypeAnnotation,
(Flake8Bugbear, "904") => Rule::RaiseWithoutFromInsideExcept,
(Flake8Bugbear, "905") => Rule::ZipWithoutExplicitStrict,
@@ -341,9 +383,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
// pydocstyle
(Pydocstyle, "100") => Rule::PublicModule,
@@ -500,6 +543,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
(Flake8Pyi, "021") => Rule::DocstringInStub,
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
// flake8-pytest-style
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
@@ -624,6 +668,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment,
(Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "007") => Rule::PairwiseOverZipped,
(Ruff, "100") => Rule::UnusedNOQA,
// flake8-django

View File

@@ -107,15 +107,21 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives {
// omit a space after the colon. The remaining action comments are
// required to include the space, and must appear on their own lines.
let comment_text = comment_text.trim_end();
if comment_text == "# isort: split" {
if matches!(comment_text, "# isort: split" | "# ruff: isort: split") {
splits.push(start.row());
} else if comment_text == "# isort: skip_file" || comment_text == "# isort:skip_file" {
} else if matches!(
comment_text,
"# isort: skip_file"
| "# isort:skip_file"
| "# ruff: isort: skip_file"
| "# ruff: isort:skip_file"
) {
return IsortDirectives {
skip_file: true,
..IsortDirectives::default()
};
} else if off.is_some() {
if comment_text == "# isort: on" {
if comment_text == "# isort: on" || comment_text == "# ruff: isort: on" {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
@@ -126,7 +132,7 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives {
} else {
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
exclusions.insert(start.row());
} else if comment_text == "# isort: off" {
} else if comment_text == "# isort: off" || comment_text == "# ruff: isort: off" {
off = Some(start);
}
}

View File

@@ -7,8 +7,8 @@ use rustpython_parser::ast::{Constant, ExprKind, Stmt, StmtKind, Suite};
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor;
/// Extract doc lines (standalone comments) from a token sequence.
pub fn doc_lines_from_tokens(lxr: &[LexResult]) -> DocLines {

View File

@@ -1,3 +1,6 @@
use ruff_python_ast::visibility::{
class_visibility, function_visibility, method_visibility, Modifier, Visibility, VisibleScope,
};
use rustpython_parser::ast::{Expr, Stmt};
#[derive(Debug, Clone)]
@@ -30,3 +33,36 @@ pub enum Documentable {
Class,
Function,
}
pub fn transition_scope(scope: &VisibleScope, stmt: &Stmt, kind: &Documentable) -> VisibleScope {
match kind {
Documentable::Function => VisibleScope {
modifier: Modifier::Function,
visibility: match scope {
VisibleScope {
modifier: Modifier::Module,
visibility: Visibility::Public,
} => function_visibility(stmt),
VisibleScope {
modifier: Modifier::Class,
visibility: Visibility::Public,
} => method_visibility(stmt),
_ => Visibility::Private,
},
},
Documentable::Class => VisibleScope {
modifier: Modifier::Class,
visibility: match scope {
VisibleScope {
modifier: Modifier::Module,
visibility: Visibility::Public,
} => class_visibility(stmt),
VisibleScope {
modifier: Modifier::Class,
visibility: Visibility::Public,
} => class_visibility(stmt),
_ => Visibility::Private,
},
},
}
}

View File

@@ -3,7 +3,7 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use crate::visibility::{Modifier, VisibleScope};
use ruff_python_ast::visibility::{Modifier, VisibleScope};
/// Extract a docstring from a function or class body.
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {

View File

@@ -1,70 +1,36 @@
//! Abstractions for Google-style docstrings.
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use crate::docstrings::sections::SectionKind;
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"Args",
"Arguments",
"Attention",
"Attributes",
"Caution",
"Danger",
"Error",
"Example",
"Examples",
"Hint",
"Important",
"Keyword Args",
"Keyword Arguments",
"Methods",
"Note",
"Notes",
"Return",
"Returns",
"Raises",
"References",
"See Also",
"Tip",
"Todo",
"Warning",
"Warnings",
"Warns",
"Yield",
"Yields",
])
});
pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"args",
"arguments",
"attention",
"attributes",
"caution",
"danger",
"error",
"example",
"examples",
"hint",
"important",
"keyword args",
"keyword arguments",
"methods",
"note",
"notes",
"return",
"returns",
"raises",
"references",
"see also",
"tip",
"todo",
"warning",
"warnings",
"warns",
"yield",
"yields",
])
});
pub(crate) static GOOGLE_SECTIONS: &[SectionKind] = &[
SectionKind::Attributes,
SectionKind::Examples,
SectionKind::Methods,
SectionKind::Notes,
SectionKind::Raises,
SectionKind::References,
SectionKind::Returns,
SectionKind::SeeAlso,
SectionKind::Yields,
// Google-only
SectionKind::Args,
SectionKind::Arguments,
SectionKind::Attention,
SectionKind::Caution,
SectionKind::Danger,
SectionKind::Error,
SectionKind::Example,
SectionKind::Hint,
SectionKind::Important,
SectionKind::KeywordArgs,
SectionKind::KeywordArguments,
SectionKind::Note,
SectionKind::Notes,
SectionKind::Return,
SectionKind::Tip,
SectionKind::Todo,
SectionKind::Warning,
SectionKind::Warnings,
SectionKind::Warns,
SectionKind::Yield,
];

View File

@@ -1,40 +1,20 @@
//! Abstractions for NumPy-style docstrings.
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use crate::docstrings::sections::SectionKind;
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"short summary",
"extended summary",
"parameters",
"returns",
"yields",
"other parameters",
"raises",
"see also",
"notes",
"references",
"examples",
"attributes",
"methods",
])
});
pub(crate) static NUMPY_SECTION_NAMES: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
FxHashSet::from_iter([
"Short Summary",
"Extended Summary",
"Parameters",
"Returns",
"Yields",
"Other Parameters",
"Raises",
"See Also",
"Notes",
"References",
"Examples",
"Attributes",
"Methods",
])
});
pub(crate) static NUMPY_SECTIONS: &[SectionKind] = &[
SectionKind::Attributes,
SectionKind::Examples,
SectionKind::Methods,
SectionKind::Notes,
SectionKind::Raises,
SectionKind::References,
SectionKind::Returns,
SectionKind::SeeAlso,
SectionKind::Yields,
// NumPy-only
SectionKind::ExtendedSummary,
SectionKind::OtherParameters,
SectionKind::Parameters,
SectionKind::ShortSummary,
];

View File

@@ -1,8 +1,126 @@
use crate::ast::whitespace;
use strum_macros::EnumIter;
use crate::docstrings::styles::SectionStyle;
use ruff_python_ast::whitespace;
#[derive(EnumIter, PartialEq, Eq, Debug, Clone, Copy)]
pub enum SectionKind {
Args,
Arguments,
Attention,
Attributes,
Caution,
Danger,
Error,
Example,
Examples,
ExtendedSummary,
Hint,
Important,
KeywordArgs,
KeywordArguments,
Methods,
Note,
Notes,
OtherParameters,
Parameters,
Raises,
References,
Return,
Returns,
SeeAlso,
ShortSummary,
Tip,
Todo,
Warning,
Warnings,
Warns,
Yield,
Yields,
}
impl SectionKind {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"args" => Some(Self::Args),
"arguments" => Some(Self::Arguments),
"attention" => Some(Self::Attention),
"attributes" => Some(Self::Attributes),
"caution" => Some(Self::Caution),
"danger" => Some(Self::Danger),
"error" => Some(Self::Error),
"example" => Some(Self::Example),
"examples" => Some(Self::Examples),
"extended summary" => Some(Self::ExtendedSummary),
"hint" => Some(Self::Hint),
"important" => Some(Self::Important),
"keyword args" => Some(Self::KeywordArgs),
"keyword arguments" => Some(Self::KeywordArguments),
"methods" => Some(Self::Methods),
"note" => Some(Self::Note),
"notes" => Some(Self::Notes),
"other parameters" => Some(Self::OtherParameters),
"parameters" => Some(Self::Parameters),
"raises" => Some(Self::Raises),
"references" => Some(Self::References),
"return" => Some(Self::Return),
"returns" => Some(Self::Returns),
"see also" => Some(Self::SeeAlso),
"short summary" => Some(Self::ShortSummary),
"tip" => Some(Self::Tip),
"todo" => Some(Self::Todo),
"warning" => Some(Self::Warning),
"warnings" => Some(Self::Warnings),
"warns" => Some(Self::Warns),
"yield" => Some(Self::Yield),
"yields" => Some(Self::Yields),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Args => "Args",
Self::Arguments => "Arguments",
Self::Attention => "Attention",
Self::Attributes => "Attributes",
Self::Caution => "Caution",
Self::Danger => "Danger",
Self::Error => "Error",
Self::Example => "Example",
Self::Examples => "Examples",
Self::ExtendedSummary => "Extended Summary",
Self::Hint => "Hint",
Self::Important => "Important",
Self::KeywordArgs => "Keyword Args",
Self::KeywordArguments => "Keyword Arguments",
Self::Methods => "Methods",
Self::Note => "Note",
Self::Notes => "Notes",
Self::OtherParameters => "Other Parameters",
Self::Parameters => "Parameters",
Self::Raises => "Raises",
Self::References => "References",
Self::Return => "Return",
Self::Returns => "Returns",
Self::SeeAlso => "See Also",
Self::ShortSummary => "Short Summary",
Self::Tip => "Tip",
Self::Todo => "Todo",
Self::Warning => "Warning",
Self::Warnings => "Warnings",
Self::Warns => "Warns",
Self::Yield => "Yield",
Self::Yields => "Yields",
}
}
}
#[derive(Debug)]
pub(crate) struct SectionContext<'a> {
/// The "kind" of the section, e.g. "SectionKind::Args" or "SectionKind::Returns".
pub(crate) kind: SectionKind,
/// The name of the section as it appears in the docstring, e.g. "Args" or "Returns".
pub(crate) section_name: &'a str,
pub(crate) previous_line: &'a str,
pub(crate) line: &'a str,
@@ -11,10 +129,13 @@ pub(crate) struct SectionContext<'a> {
pub(crate) original_index: usize,
}
fn suspected_as_section(line: &str, style: &SectionStyle) -> bool {
style
.lowercase_section_names()
.contains(&whitespace::leading_words(line).to_lowercase().as_str())
fn suspected_as_section(line: &str, style: &SectionStyle) -> Option<SectionKind> {
if let Some(kind) = SectionKind::from_str(whitespace::leading_words(line)) {
if style.sections().contains(&kind) {
return Some(kind);
}
}
None
}
/// Check if the suspected context is really a section header.
@@ -49,21 +170,15 @@ pub(crate) fn section_contexts<'a>(
lines: &'a [&'a str],
style: &SectionStyle,
) -> Vec<SectionContext<'a>> {
let suspected_section_indices: Vec<usize> = lines
let mut contexts = vec![];
for (kind, lineno) in lines
.iter()
.enumerate()
.filter_map(|(lineno, line)| {
if lineno > 0 && suspected_as_section(line, style) {
Some(lineno)
} else {
None
}
})
.collect();
let mut contexts = vec![];
for lineno in suspected_section_indices {
.skip(1)
.filter_map(|(lineno, line)| suspected_as_section(line, style).map(|kind| (kind, lineno)))
{
let context = SectionContext {
kind,
section_name: whitespace::leading_words(lines[lineno]),
previous_line: lines[lineno - 1],
line: lines[lineno],
@@ -76,11 +191,12 @@ pub(crate) fn section_contexts<'a>(
}
}
let mut truncated_contexts = vec![];
let mut truncated_contexts = Vec::with_capacity(contexts.len());
let mut end: Option<usize> = None;
for context in contexts.into_iter().rev() {
let next_end = context.original_index;
truncated_contexts.push(SectionContext {
kind: context.kind,
section_name: context.section_name,
previous_line: context.previous_line,
line: context.line,

View File

@@ -1,8 +1,6 @@
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use crate::docstrings::google::{GOOGLE_SECTION_NAMES, LOWERCASE_GOOGLE_SECTION_NAMES};
use crate::docstrings::numpy::{LOWERCASE_NUMPY_SECTION_NAMES, NUMPY_SECTION_NAMES};
use crate::docstrings::google::GOOGLE_SECTIONS;
use crate::docstrings::numpy::NUMPY_SECTIONS;
use crate::docstrings::sections::SectionKind;
pub(crate) enum SectionStyle {
Numpy,
@@ -10,17 +8,10 @@ pub(crate) enum SectionStyle {
}
impl SectionStyle {
pub(crate) fn section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
pub(crate) fn sections(&self) -> &[SectionKind] {
match self {
SectionStyle::Numpy => &NUMPY_SECTION_NAMES,
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
}
}
pub(crate) fn lowercase_section_names(&self) -> &Lazy<FxHashSet<&'static str>> {
match self {
SectionStyle::Numpy => &LOWERCASE_NUMPY_SECTION_NAMES,
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
SectionStyle::Numpy => NUMPY_SECTIONS,
SectionStyle::Google => GOOGLE_SECTIONS,
}
}
}

View File

@@ -1,6 +1,3 @@
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, Hash)]
pub enum FixMode {
Generate,
@@ -18,40 +15,3 @@ impl From<bool> for FixMode {
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Fix {
pub content: String,
pub location: Location,
pub end_location: Location,
}
impl Fix {
pub const fn deletion(start: Location, end: Location) -> Self {
Self {
content: String::new(),
location: start,
end_location: end,
}
}
pub fn replacement(content: String, start: Location, end: Location) -> Self {
debug_assert!(!content.is_empty(), "Prefer `Fix::deletion`");
Self {
content,
location: start,
end_location: end,
}
}
pub fn insertion(content: String, at: Location) -> Self {
debug_assert!(!content.is_empty(), "Insert content is empty");
Self {
content,
location: at,
end_location: at,
}
}
}

View File

@@ -20,6 +20,7 @@ use crate::rules::{
};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
use crate::settings::types::PythonVersion;
use crate::warn_user;
const DEFAULT_SELECTORS: &[RuleSelector] = &[
@@ -424,6 +425,15 @@ pub fn convert(
}
}
if let Some(project) = &external_config.project {
if let Some(requires_python) = &project.requires_python {
if options.target_version.is_none() {
options.target_version =
PythonVersion::get_minimum_supported_version(requires_python);
}
}
}
// Create the pyproject.toml.
Ok(Pyproject::new(options))
}
@@ -439,13 +449,17 @@ fn resolve_select(plugins: &[Plugin]) -> HashSet<RuleSelector> {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::str::FromStr;
use anyhow::Result;
use itertools::Itertools;
use pep440_rs::VersionSpecifiers;
use pretty_assertions::assert_eq;
use super::super::plugin::Plugin;
use super::convert;
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
use crate::flake8_to_ruff::pep621::Project;
use crate::flake8_to_ruff::ExternalConfig;
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
@@ -453,6 +467,7 @@ mod tests {
use crate::rules::{flake8_quotes, pydocstyle};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
use crate::settings::types::PythonVersion;
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
Options {
@@ -577,6 +592,7 @@ mod tests {
pydocstyle: Some(pydocstyle::settings::Options {
convention: Some(Convention::Numpy),
ignore_decorators: None,
property_decorators: None,
}),
..default_options([Linter::Pydocstyle.into()])
});
@@ -608,4 +624,25 @@ mod tests {
Ok(())
}
#[test]
fn it_converts_project_requires_python() -> Result<()> {
let actual = convert(
&HashMap::from([("flake8".to_string(), HashMap::default())]),
&ExternalConfig {
project: Some(&Project {
requires_python: Some(VersionSpecifiers::from_str(">=3.8.16, <3.11")?),
}),
..ExternalConfig::default()
},
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
target_version: Some(PythonVersion::Py38),
..default_options([])
});
assert_eq!(actual, expected);
Ok(())
}
}

View File

@@ -1,8 +1,10 @@
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Default)]
pub struct ExternalConfig<'a> {
pub black: Option<&'a Black>,
pub isort: Option<&'a Isort>,
pub project: Option<&'a Project>,
}

View File

@@ -3,6 +3,7 @@ mod converter;
mod external_config;
mod isort;
mod parser;
pub mod pep621;
mod plugin;
mod pyproject;

View File

@@ -0,0 +1,10 @@
//! Extract PEP 621 configuration settings from a pyproject.toml.
use pep440_rs::VersionSpecifiers;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Project {
#[serde(alias = "requires-python", alias = "requires_python")]
pub requires_python: Option<VersionSpecifiers>,
}

View File

@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tools {
@@ -15,6 +16,7 @@ pub struct Tools {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pyproject {
pub tool: Option<Tools>,
pub project: Option<Project>,
}
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Pyproject> {

View File

@@ -1,13 +1,12 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use log::debug;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use crate::registry::Rule;
use crate::settings::hashable::{HashableGlobMatcher, HashableHashSet};
/// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@@ -23,15 +22,12 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
}
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>(
pub(crate) fn ignores_from_path(
path: &Path,
pattern_code_pairs: &'a [(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<Rule>,
)],
) -> FxHashSet<&'a Rule> {
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, FxHashSet<Rule>)],
) -> FxHashSet<Rule> {
let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename");
pattern_code_pairs
.iter()
.filter_map(|(absolute, basename, codes)| {
@@ -39,23 +35,24 @@ pub(crate) fn ignores_from_path<'a>(
debug!(
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path,
basename.deref().glob().regex(),
&**codes
basename.glob().regex(),
codes
);
return Some(codes.iter());
}
if absolute.is_match(file_path) {
Some(codes)
} else if absolute.is_match(file_path) {
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
absolute.deref().glob().regex(),
&**codes
absolute.glob().regex(),
codes
);
return Some(codes.iter());
Some(codes)
} else {
None
}
None
})
.flatten()
.copied()
.collect()
}
@@ -79,10 +76,20 @@ pub fn normalize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root:
}
/// Convert an absolute path to be relative to the current working directory.
pub fn relativize_path(path: impl AsRef<Path>) -> String {
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
let path = path.as_ref();
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
return format!("{}", path.display());
}
format!("{}", path.display())
}
/// Convert an absolute path to be relative to the specified project root.
pub fn relativize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root: R) -> String {
format!(
"{}",
pathdiff::diff_paths(&path, project_root)
.expect("Could not diff paths")
.display()
)
}

View File

@@ -5,19 +5,16 @@
//!
//! [Ruff]: https://github.com/charliermarsh/ruff
pub use ast::types::Range;
use cfg_if::cfg_if;
pub use ruff_python_ast::source_code::round_trip;
pub use ruff_python_ast::types::Range;
pub use rule_selector::RuleSelector;
pub use rules::pycodestyle::rules::IOError;
pub use violation::{AutofixKind, Availability as AutofixAvailability};
mod ast;
mod autofix;
pub mod cache;
mod checkers;
mod codes;
mod cst;
mod directives;
pub mod directives;
mod doc_lines;
mod docstrings;
pub mod fix;
@@ -28,24 +25,13 @@ pub mod linter;
pub mod logging;
pub mod message;
mod noqa;
pub mod packaging;
pub mod registry;
pub mod resolver;
mod rule_redirects;
mod rule_selector;
mod rules;
pub mod rules;
pub mod settings;
pub mod source_code;
mod violation;
mod visibility;
cfg_if! {
if #[cfg(target_family = "wasm")] {
mod lib_wasm;
pub use lib_wasm::check;
} else {
pub mod packaging;
}
}
#[cfg(test)]
mod test;

View File

@@ -8,6 +8,10 @@ use rustc_hash::FxHashMap;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::ParseError;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_stdlib::path::is_python_stub_file;
use crate::autofix::fix_file;
use crate::checkers::ast::check_ast;
use crate::checkers::filesystem::check_file_path;
@@ -20,10 +24,9 @@ use crate::directives::Directives;
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
use crate::message::{Message, Source};
use crate::noqa::{add_noqa, rule_is_ignored};
use crate::registry::{Diagnostic, Rule};
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle;
use crate::settings::{flags, Settings};
use crate::source_code::{Indexer, Locator, Stylist};
use crate::{directives, fs};
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
@@ -47,7 +50,7 @@ impl<T> LinterResult<T> {
}
}
pub type FixTable = FxHashMap<&'static Rule, usize>;
pub type FixTable = FxHashMap<Rule, usize>;
/// Generate `Diagnostic`s from the source code contents at the
/// given `Path`.
@@ -71,7 +74,7 @@ pub fn check_path(
// Collect doc lines. This requires a rare mix of tokens (for comments) and AST
// (for docstrings), which demands special-casing at this level.
let use_doc_lines = settings.rules.enabled(&Rule::DocLineTooLong);
let use_doc_lines = settings.rules.enabled(Rule::DocLineTooLong);
let mut doc_lines = vec![];
if use_doc_lines {
doc_lines.extend(doc_lines_from_tokens(&tokens));
@@ -83,7 +86,8 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_tokens())
{
diagnostics.extend(check_tokens(locator, &tokens, settings, autofix));
let is_stub = is_python_stub_file(path);
diagnostics.extend(check_tokens(locator, &tokens, settings, autofix, is_stub));
}
// Run the filesystem-based rules.
@@ -101,7 +105,13 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_logical_lines())
{
diagnostics.extend(check_logical_lines(&tokens, locator, stylist, settings));
diagnostics.extend(check_logical_lines(
&tokens,
locator,
stylist,
settings,
flags::Autofix::Enabled,
));
}
// Run the AST-based rules.
@@ -149,14 +159,14 @@ pub fn check_path(
}
}
Err(parse_error) => {
if settings.rules.enabled(&Rule::SyntaxError) {
if settings.rules.enabled(Rule::SyntaxError) {
pycodestyle::rules::syntax_error(&mut diagnostics, &parse_error);
}
// If the syntax error is ignored, suppress it (regardless of whether
// `Rule::SyntaxError` is enabled).
if !rule_is_ignored(
&Rule::SyntaxError,
Rule::SyntaxError,
parse_error.location.row(),
&directives.noqa_line_for,
locator,
@@ -181,8 +191,8 @@ pub fn check_path(
{
diagnostics.extend(check_physical_lines(
path,
locator,
stylist,
contents,
indexer.commented_lines(),
&doc_lines,
settings,
@@ -205,7 +215,7 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_noqa())
{
check_noqa(
let ignored = check_noqa(
&mut diagnostics,
contents,
indexer.commented_lines(),
@@ -213,6 +223,11 @@ pub fn check_path(
settings,
error.as_ref().map_or(autofix, |_| flags::Autofix::Disabled),
);
if noqa.into() {
for index in ignored.iter().rev() {
diagnostics.swap_remove(*index);
}
}
}
LinterResult::new(diagnostics, error)

View File

@@ -1,8 +1,31 @@
use std::sync::Mutex;
use anyhow::Result;
use colored::Colorize;
use fern;
use log::Level;
use once_cell::sync::Lazy;
pub(crate) static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
/// Warn a user once, with uniqueness determined by the given ID.
#[macro_export]
macro_rules! warn_user_once_by_id {
($id:expr, $($arg:tt)*) => {
use colored::Colorize;
use log::warn;
if let Ok(mut states) = $crate::logging::WARNINGS.lock() {
if !states.contains(&$id) {
let message = format!("{}", format_args!($($arg)*));
warn!("{}", message.bold());
states.push($id);
}
}
};
}
/// Warn a user once, with uniqueness determined by the calling location itself.
#[macro_export]
macro_rules! warn_user_once {
($($arg:tt)*) => {

View File

@@ -3,10 +3,9 @@ use std::cmp::Ordering;
pub use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
use crate::fix::Fix;
use crate::registry::{Diagnostic, DiagnosticKind};
use crate::source_code::Locator;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Message {
@@ -74,9 +73,9 @@ impl Source {
} else {
Location::new(diagnostic.end_location.row() + 1, 0)
};
let source = locator.slice(&Range::new(location, end_location));
let source = locator.slice(Range::new(location, end_location));
let num_chars_in_range = locator
.slice(&Range::new(diagnostic.location, diagnostic.end_location))
.slice(Range::new(diagnostic.location, diagnostic.end_location))
.chars()
.count();
Source {

View File

@@ -11,15 +11,18 @@ use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{LineEnding, Locator};
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
use crate::registry::{Diagnostic, Rule};
use crate::registry::{AsRule, Rule};
use crate::rule_redirects::get_redirect_target;
use crate::source_code::{LineEnding, Locator};
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?P<spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)",
r"(?P<leading_spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>(?:[A-Z]+[0-9]+)(?:[,\s]+[A-Z]+[0-9]+)*))?)(?P<trailing_spaces>\s*)",
)
.unwrap()
});
@@ -71,35 +74,42 @@ pub fn extract_file_exemption(line: &str) -> Exemption {
#[derive(Debug)]
pub enum Directive<'a> {
None,
All(usize, usize, usize),
Codes(usize, usize, usize, Vec<&'a str>),
All(usize, usize, usize, usize),
Codes(usize, usize, usize, Vec<&'a str>, usize),
}
/// Extract the noqa `Directive` from a line of Python source code.
pub fn extract_noqa_directive(line: &str) -> Directive {
match NOQA_LINE_REGEX.captures(line) {
Some(caps) => match caps.name("spaces") {
Some(spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
Some(codes) => {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.as_str().trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
Some(caps) => match caps.name("leading_spaces") {
Some(leading_spaces) => match caps.name("trailing_spaces") {
Some(trailing_spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
Some(codes) => {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.as_str().trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
}
Directive::Codes(
leading_spaces.as_str().chars().count(),
noqa.start(),
noqa.end(),
codes,
trailing_spaces.as_str().chars().count(),
)
}
Directive::Codes(
spaces.as_str().chars().count(),
None => Directive::All(
leading_spaces.as_str().chars().count(),
noqa.start(),
noqa.end(),
codes,
)
}
None => {
Directive::All(spaces.as_str().chars().count(), noqa.start(), noqa.end())
}
trailing_spaces.as_str().chars().count(),
),
},
None => Directive::None,
},
None => Directive::None,
},
@@ -111,7 +121,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
let needle = needle.noqa_code();
haystack
.iter()
@@ -120,20 +130,20 @@ pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
/// Returns `true` if the given [`Rule`] is ignored at the specified `lineno`.
pub fn rule_is_ignored(
code: &Rule,
code: Rule,
lineno: usize,
noqa_line_for: &IntMap<usize, usize>,
locator: &Locator,
) -> bool {
let noqa_lineno = noqa_line_for.get(&lineno).unwrap_or(&lineno);
let line = locator.slice(&Range::new(
let line = locator.slice(Range::new(
Location::new(*noqa_lineno, 0),
Location::new(noqa_lineno + 1, 0),
));
match extract_noqa_directive(line) {
Directive::None => false,
Directive::All(..) => true,
Directive::Codes(.., codes) => includes(code, &codes),
Directive::Codes(.., codes, _) => includes(code, &codes),
}
}
@@ -164,7 +174,7 @@ fn add_noqa_inner(
line_ending: &LineEnding,
) -> (usize, String) {
// Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line.
let mut matches_by_line: FxHashMap<usize, FxHashSet<&Rule>> = FxHashMap::default();
let mut matches_by_line: FxHashMap<usize, FxHashSet<Rule>> = FxHashMap::default();
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -172,7 +182,7 @@ fn add_noqa_inner(
// Codes that are globally exempted (within the current file).
let mut file_exemptions: Vec<NoqaCode> = vec![];
let lines: Vec<&str> = contents.lines().collect();
let lines: Vec<&str> = contents.universal_newlines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
@@ -214,7 +224,7 @@ fn add_noqa_inner(
Directive::All(..) => {
continue;
}
Directive::Codes(.., codes) => {
Directive::Codes(.., codes, _) => {
if includes(diagnostic.kind.rule(), &codes) {
continue;
}
@@ -234,7 +244,7 @@ fn add_noqa_inner(
Directive::All(..) => {
continue;
}
Directive::Codes(.., codes) => {
Directive::Codes(.., codes, _) => {
if includes(diagnostic.kind.rule(), &codes) {
continue;
}
@@ -254,7 +264,7 @@ fn add_noqa_inner(
let mut count: usize = 0;
let mut output = String::new();
for (lineno, line) in contents.lines().enumerate() {
for (lineno, line) in lines.into_iter().enumerate() {
match matches_by_line.get(&lineno) {
None => {
output.push_str(line);
@@ -270,7 +280,7 @@ fn add_noqa_inner(
output.push_str(" # noqa: ");
// Add codes.
push_codes(&mut output, rules.iter().map(|r| r.noqa_code()));
push_codes(&mut output, rules.iter().map(Rule::noqa_code));
output.push_str(line_ending);
count += 1;
}
@@ -279,7 +289,7 @@ fn add_noqa_inner(
output.push_str(line);
output.push_str(line_ending);
}
Directive::Codes(_, start_byte, _, existing) => {
Directive::Codes(_, start_byte, _, existing, _) => {
// Reconstruct the line based on the preserved rule codes.
// This enables us to tally the number of edits.
let mut formatted = String::with_capacity(line.len());
@@ -332,12 +342,13 @@ mod tests {
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::LineEnding;
use ruff_python_ast::types::Range;
use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX};
use crate::registry::Diagnostic;
use crate::rules::pycodestyle::rules::AmbiguousVariableName;
use crate::rules::pyflakes;
use crate::source_code::LineEnding;
#[test]
fn regex() {

View File

@@ -1,15 +1,12 @@
//! Registry of [`Rule`] to [`DiagnosticKind`] mappings.
//! Registry of all [`Rule`] implementations.
use ruff_macros::RuleNamespace;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter};
use crate::ast::types::Range;
use ruff_diagnostics::Violation;
use ruff_macros::RuleNamespace;
use crate::codes::{self, RuleCodePrefix};
use crate::fix::Fix;
use crate::rules;
use crate::violation::Violation;
ruff_macros::register_rules!(
// pycodestyle errors
@@ -53,16 +50,28 @@ ruff_macros::register_rules!(
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespace,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
rules::pycodestyle::rules::TabBeforeKeyword,
rules::pycodestyle::rules::MultipleImportsOnOneLine,
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
@@ -134,13 +143,22 @@ ruff_macros::register_rules!(
rules::pyflakes::rules::UnusedAnnotation,
rules::pyflakes::rules::RaiseNotImplemented,
// pylint
rules::pylint::rules::UselessReturn,
rules::pylint::rules::YieldInInit,
rules::pylint::rules::InvalidAllObject,
rules::pylint::rules::InvalidAllFormat,
rules::pylint::rules::InvalidEnvvarDefault,
rules::pylint::rules::InvalidEnvvarValue,
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::InvalidCharacterBackspace,
rules::pylint::rules::InvalidCharacterSub,
rules::pylint::rules::InvalidCharacterEsc,
rules::pylint::rules::InvalidCharacterNul,
rules::pylint::rules::InvalidCharacterZeroWidthSpace,
rules::pylint::rules::BadStrStripCall,
rules::pylint::rules::CollapsibleElseIf,
rules::pylint::rules::ContinueInFinally,
rules::pylint::rules::UselessImportAlias,
rules::pylint::rules::UnnecessaryDirectLambdaCall,
rules::pylint::rules::NonlocalWithoutBinding,
@@ -149,6 +167,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::PropertyWithParameters,
rules::pylint::rules::ReturnInInit,
rules::pylint::rules::ConsiderUsingFromImport,
rules::pylint::rules::CompareToEmptyString,
rules::pylint::rules::ComparisonOfConstant,
rules::pylint::rules::ConsiderMergingIsinstance,
rules::pylint::rules::ConsiderUsingSysExit,
@@ -173,6 +192,7 @@ ruff_macros::register_rules!(
rules::flake8_bugbear::rules::UnreliableCallableCheck,
rules::flake8_bugbear::rules::StripWithMultiCharacters,
rules::flake8_bugbear::rules::MutableArgumentDefault,
rules::flake8_bugbear::rules::NoExplicitStacklevel,
rules::flake8_bugbear::rules::UnusedLoopControlVariable,
rules::flake8_bugbear::rules::FunctionCallArgumentDefault,
rules::flake8_bugbear::rules::GetAttrWithConstant,
@@ -197,6 +217,7 @@ ruff_macros::register_rules!(
rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept,
rules::flake8_bugbear::rules::ZipWithoutExplicitStrict,
rules::flake8_bugbear::rules::ExceptWithEmptyTuple,
rules::flake8_bugbear::rules::ExceptWithNonExceptionClasses,
rules::flake8_bugbear::rules::UnintentionalTypeAnnotation,
// flake8-blind-except
rules::flake8_blind_except::rules::BlindExcept,
@@ -328,9 +349,10 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::FString,
rules::pyupgrade::rules::FunctoolsCache,
rules::pyupgrade::rules::ExtraneousParentheses,
rules::pyupgrade::rules::ImportReplacements,
rules::pyupgrade::rules::DeprecatedImport,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
// pydocstyle
rules::pydocstyle::rules::PublicModule,
rules::pydocstyle::rules::PublicClass,
@@ -474,6 +496,7 @@ ruff_macros::register_rules!(
rules::flake8_pyi::rules::DocstringInStub,
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
rules::flake8_pyi::rules::TypeCommentInStub,
// flake8-pytest-style
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
rules::flake8_pytest_style::rules::FixturePositionalArgs,
@@ -586,6 +609,7 @@ ruff_macros::register_rules!(
rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
rules::ruff::rules::AsyncioDanglingTask,
rules::ruff::rules::UnusedNOQA,
rules::ruff::rules::PairwiseOverZipped,
// flake8-django
rules::flake8_django::rules::NullableModelStringField,
rules::flake8_django::rules::LocalsInRenderFunction,
@@ -595,6 +619,24 @@ ruff_macros::register_rules!(
rules::flake8_django::rules::NonLeadingReceiverDecorator,
);
pub trait AsRule {
fn rule(&self) -> Rule;
}
impl Rule {
pub fn from_code(code: &str) -> Result<Self, FromCodeError> {
let (linter, code) = Linter::parse_code(code).ok_or(FromCodeError::Unknown)?;
let prefix: RuleCodePrefix = RuleCodePrefix::parse(&linter, code)?;
Ok(prefix.into_iter().next().unwrap())
}
}
#[derive(thiserror::Error, Debug)]
pub enum FromCodeError {
#[error("unknown rule code")]
Unknown,
}
#[derive(EnumIter, Debug, PartialEq, Eq, Clone, Hash, RuleNamespace)]
pub enum Linter {
/// [Pyflakes](https://pypi.org/project/pyflakes/)
@@ -776,7 +818,7 @@ impl Linter {
}
}
#[derive(is_macro::Is)]
#[derive(is_macro::Is, Copy, Clone)]
pub enum LintSource {
Ast,
Io,
@@ -791,9 +833,9 @@ pub enum LintSource {
impl Rule {
/// The source for the diagnostic (either the AST, the filesystem, or the
/// physical lines).
pub const fn lint_source(&self) -> &'static LintSource {
pub const fn lint_source(&self) -> LintSource {
match self {
Rule::UnusedNOQA => &LintSource::Noqa,
Rule::UnusedNOQA => LintSource::Noqa,
Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::DocLineTooLong
@@ -809,7 +851,7 @@ impl Rule {
| Rule::ShebangWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
| Rule::BlankLineContainsWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
@@ -819,6 +861,11 @@ impl Rule {
| Rule::BadQuotesMultilineString
| Rule::CommentedOutCode
| Rule::MultiLineImplicitStringConcatenation
| Rule::InvalidCharacterBackspace
| Rule::InvalidCharacterSub
| Rule::InvalidCharacterEsc
| Rule::InvalidCharacterNul
| Rule::InvalidCharacterZeroWidthSpace
| Rule::ExtraneousParentheses
| Rule::InvalidEscapeSequence
| Rule::SingleLineImplicitStringConcatenation
@@ -827,19 +874,26 @@ impl Rule {
| Rule::MultipleStatementsOnOneLineColon
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
| Rule::TrailingCommaProhibited
| Rule::TypeCommentInStub => LintSource::Tokens,
Rule::IOError => LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
| Rule::MissingWhitespace
| Rule::MissingWhitespaceAfterKeyword
| Rule::MissingWhitespaceAroundArithmeticOperator
| Rule::MissingWhitespaceAroundBitwiseOrShiftOperator
| Rule::MissingWhitespaceAroundModuloOperator
| Rule::MissingWhitespaceAroundOperator
| Rule::MissingWhitespaceAroundParameterEquals
| Rule::MultipleLeadingHashesForBlockComment
| Rule::MultipleSpacesAfterKeyword
| Rule::MultipleSpacesAfterOperator
| Rule::MultipleSpacesBeforeKeyword
| Rule::MultipleSpacesBeforeOperator
| Rule::MissingWhitespaceAfterKeyword
| Rule::NoIndentedBlock
| Rule::NoIndentedBlockComment
| Rule::NoSpaceAfterBlockComment
@@ -852,47 +906,16 @@ impl Rule {
| Rule::TooFewSpacesBeforeInlineComment
| Rule::UnexpectedIndentation
| Rule::UnexpectedIndentationComment
| Rule::UnexpectedSpacesAroundKeywordParameterEquals
| Rule::WhitespaceAfterOpenBracket
| Rule::WhitespaceBeforeCloseBracket
| Rule::UnexpectedSpacesAroundKeywordParameterEquals
| Rule::MissingWhitespaceAroundParameterEquals
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
_ => &LintSource::Ast,
| Rule::WhitespaceBeforeParameters
| Rule::WhitespaceBeforePunctuation => LintSource::LogicalLines,
_ => LintSource::Ast,
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Diagnostic {
pub kind: DiagnosticKind,
pub location: Location,
pub end_location: Location,
pub fix: Option<Fix>,
pub parent: Option<Location>,
}
impl Diagnostic {
pub fn new<K: Into<DiagnosticKind>>(kind: K, range: Range) -> Self {
Self {
kind: kind.into(),
location: range.location,
end_location: range.end_location,
fix: None,
parent: None,
}
}
pub fn amend(&mut self, fix: Fix) -> &mut Self {
self.fix = Some(fix);
self
}
pub fn parent(&mut self, parent: Location) -> &mut Self {
self.parent = Some(parent);
self
}
}
/// Pairs of checks that shouldn't be enabled together.
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
(
@@ -911,6 +934,7 @@ pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
#[cfg(test)]
mod tests {
use std::mem::size_of;
use strum::IntoEnumIterator;
use super::{Linter, Rule, RuleNamespace};
@@ -956,4 +980,9 @@ mod tests {
assert_eq!(code, format!("{}{rest}", linter.common_prefix()));
}
}
#[test]
fn rule_size() {
assert_eq!(2, size_of::<Rule>());
}
}

View File

@@ -10,6 +10,7 @@ use ignore::{DirEntry, WalkBuilder, WalkState};
use itertools::Itertools;
use log::debug;
use path_absolutize::path_dedot;
use ruff_python_stdlib::path::is_python_file;
use rustc_hash::FxHashSet;
use crate::fs;
@@ -126,7 +127,8 @@ pub fn resolve_configuration(
}
// Resolve the current path.
let options = pyproject::load_options(&path)?;
let options = pyproject::load_options(&path)
.map_err(|err| anyhow!("Failed to parse `{}`: {}", path.display(), err))?;
let project_root = relativity.resolve(&path);
let configuration = Configuration::from_options(options, &project_root)?;
@@ -191,20 +193,9 @@ fn match_exclusion(file_path: &str, file_basename: &str, exclusion: &globset::Gl
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
}
/// Return `true` if the [`Path`] appears to be that of a Python file.
fn is_python_path(path: &Path) -> bool {
path.extension()
.map_or(false, |ext| ext == "py" || ext == "pyi")
}
/// Return `true` if the [`Path`] appears to be that of a Python interface definition file (`.pyi`).
pub fn is_interface_definition_path(path: &Path) -> bool {
path.extension().map_or(false, |ext| ext == "pyi")
}
/// Return `true` if the [`DirEntry`] appears to be that of a Python file.
pub fn is_python_entry(entry: &DirEntry) -> bool {
is_python_path(entry.path())
is_python_file(entry.path())
&& !entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
@@ -430,28 +421,13 @@ mod tests {
use crate::fs;
use crate::resolver::{
is_file_excluded, is_python_path, match_exclusion, resolve_settings_with_processor,
NoOpProcessor, PyprojectDiscovery, Relativity, Resolver,
is_file_excluded, match_exclusion, resolve_settings_with_processor, NoOpProcessor,
PyprojectDiscovery, Relativity, Resolver,
};
use crate::settings::pyproject::find_settings_toml;
use crate::settings::types::FilePattern;
use crate::test::test_resource_path;
#[test]
fn inclusions() {
let path = Path::new("foo/bar/baz.py").absolutize().unwrap();
assert!(is_python_path(&path));
let path = Path::new("foo/bar/baz.pyi").absolutize().unwrap();
assert!(is_python_path(&path));
let path = Path::new("foo/bar/baz.js").absolutize().unwrap();
assert!(!is_python_path(&path));
let path = Path::new("foo/bar/baz").absolutize().unwrap();
assert!(!is_python_path(&path));
}
fn make_exclusion(file_pattern: FilePattern) -> GlobSet {
let mut builder = globset::GlobSetBuilder::new();
file_pattern.add_to(&mut builder).unwrap();

View File

@@ -18,9 +18,7 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// The following are here because we don't yet have the many-to-one mapping enabled.
("SIM111", "SIM110"),
// The following are deprecated.
("C", "C4"),
("C9", "C90"),
("T", "T10"),
("T1", "T10"),
("T2", "T20"),
// TODO(charlie): Remove by 2023-02-01.

View File

@@ -15,9 +15,17 @@ use crate::rule_redirects::get_redirect;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RuleSelector {
/// All rules
/// Select all rules.
All,
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
/// via a single selector.
C,
/// Legacy category to select both the `flake8-debugger` and `flake8-print` linters
/// via a single selector.
T,
/// Select all rules for a given linter.
Linter(Linter),
/// Select all rules for a given linter with a given prefix.
Prefix {
prefix: RuleCodePrefix,
redirected_from: Option<&'static str>,
@@ -36,6 +44,10 @@ impl FromStr for RuleSelector {
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "ALL" {
Ok(Self::All)
} else if s == "C" {
Ok(Self::C)
} else if s == "T" {
Ok(Self::T)
} else {
let (s, redirected_from) = match get_redirect(s) {
Some((from, target)) => (target, Some(from)),
@@ -51,7 +63,7 @@ impl FromStr for RuleSelector {
Ok(Self::Prefix {
prefix: RuleCodePrefix::parse(&linter, code)
.map_err(|_| ParseError::Unknown(code.to_string()))?,
.map_err(|_| ParseError::Unknown(s.to_string()))?,
redirected_from,
})
}
@@ -60,7 +72,7 @@ impl FromStr for RuleSelector {
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("Unknown rule selector `{0}`")]
#[error("Unknown rule selector: `{0}`")]
// TODO(martin): tell the user how to discover rule codes via the CLI once such a command is
// implemented (but that should of course be done only in ruff_cli and not here)
Unknown(String),
@@ -70,6 +82,8 @@ impl RuleSelector {
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
match self {
RuleSelector::All => ("", "ALL"),
RuleSelector::C => ("", "C"),
RuleSelector::T => ("", "T"),
RuleSelector::Prefix { prefix, .. } => {
(prefix.linter().common_prefix(), prefix.short_code())
}
@@ -138,6 +152,16 @@ impl IntoIterator for &RuleSelector {
fn into_iter(self) -> Self::IntoIter {
match self {
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
RuleSelector::C => RuleSelectorIter::Chain(
Linter::Flake8Comprehensions
.into_iter()
.chain(Linter::McCabe.into_iter()),
),
RuleSelector::T => RuleSelectorIter::Chain(
Linter::Flake8Debugger
.into_iter()
.chain(Linter::Flake8Print.into_iter()),
),
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.into_iter()),
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.into_iter()),
}
@@ -146,6 +170,7 @@ impl IntoIterator for &RuleSelector {
pub enum RuleSelectorIter {
All(RuleIter),
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
Vec(std::vec::IntoIter<Rule>),
}
@@ -155,13 +180,14 @@ impl Iterator for RuleSelectorIter {
fn next(&mut self) -> Option<Self::Item> {
match self {
RuleSelectorIter::All(iter) => iter.next(),
RuleSelectorIter::Chain(iter) => iter.next(),
RuleSelectorIter::Vec(iter) => iter.next(),
}
}
}
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
// to let us keep the fields of RuleSelector private.
/// to let us keep the fields of [`RuleSelector`] private.
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
@@ -177,13 +203,27 @@ impl JsonSchema for RuleSelector {
"RuleSelector".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
std::iter::once("ALL".to_string())
.chain(
RuleCodePrefix::iter()
.filter(|p| {
// Once logical lines are active by default, please remove this.
// This is here because generate-all output otherwise depends on
// the feature sets which makes the test running with
// `--all-features` fail
!Rule::from_code(&format!(
"{}{}",
p.linter().common_prefix(),
p.short_code()
))
.unwrap()
.lint_source()
.is_logical_lines()
})
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
@@ -207,6 +247,8 @@ impl RuleSelector {
pub(crate) fn specificity(&self) -> Specificity {
match self {
RuleSelector::All => Specificity::All,
RuleSelector::T => Specificity::LinterGroup,
RuleSelector::C => Specificity::LinterGroup,
RuleSelector::Linter(..) => Specificity::Linter,
RuleSelector::Prefix { prefix, .. } => {
let prefix: &'static str = prefix.short_code();
@@ -226,6 +268,7 @@ impl RuleSelector {
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Specificity {
All,
LinterGroup,
Linter,
Code1Char,
Code2Chars,
@@ -234,6 +277,7 @@ pub(crate) enum Specificity {
Code5Chars,
}
#[cfg(feature = "clap")]
mod clap_completion {
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use strum::IntoEnumIterator;
@@ -273,9 +317,7 @@ mod clap_completion {
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(
std::iter::once(PossibleValue::new("ALL").help("all rules")).chain(
Linter::iter()

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