Compare commits

..

211 Commits

Author SHA1 Message Date
Charlie Marsh
6e97c9438a Merge branch 'main' into github-2923 2023-03-02 15:55:55 -05:00
Charlie Marsh
187104e396 Flag out-of-date docs on CI (#3309) 2023-03-02 15:55:39 -05:00
Charlie Marsh
ea86edf12e Rename, etc. 2023-03-02 15:50:10 -05:00
Martin Kagamino Lehoux
2558384817 Replace tuples with type union in isinstance or issubclass calls (#2923) 2023-03-02 21:07:00 +01:00
Charlie Marsh
3ed539d50e Add a CLI flag to force-ignore noqa directives (#3296) 2023-03-01 22:28:13 -05:00
Charlie Marsh
4a70a4c323 Ignore unused imports in ModuleNotFoundError blocks (#3288) 2023-03-01 18:08:37 -05:00
Charlie Marsh
310f13c7db Redirect RUF004 to B026 (#3293) 2023-03-01 13:00:02 -05:00
konstin
2168404fc2 flake8-pyi PYI006 bad version info comparison (#3291)
Implement PYI006 "bad version info comparison"

## What it does

Ensures that you only `<` and `>=` for version info comparisons with
`sys.version_info` in `.pyi` files. All other comparisons such as
`<`, `<=` and `==` are banned.

## Why is this bad?

```python
>>> import sys
>>> print(sys.version_info)
sys.version_info(major=3, minor=8, micro=10, releaselevel='final', serial=0)
>>> print(sys.version_info > (3, 8))
True
>>> print(sys.version_info == (3, 8))
False
>>> print(sys.version_info <= (3, 8))
False
>>> print(sys.version_info in (3, 8))
False
```

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-03-01 18:58:57 +01:00
Charlie Marsh
a032b66c2e Avoid PEP 585 rewrites when builtins are shadowed (#3286) 2023-02-28 23:25:42 +00:00
Charlie Marsh
af5f7dbd83 Avoid pluralization for single --add-noqa result (#3282) 2023-02-28 15:41:18 -05:00
Charlie Marsh
8066607ea3 Add a preliminary tutorial (#3281) 2023-02-28 20:31:27 +00:00
Andy Freeland
0ed9fccce9 Upgrade RustPython (#3277)
Fixes #3207.
2023-02-28 12:21:28 -05:00
Carlos Gonçalves
074a343a63 feat(E251,E252): add rules (#3274) 2023-02-28 12:02:36 -05:00
Charlie Marsh
c7e09b54b0 Use expression span for yoda-conditions fixes (#3276) 2023-02-28 16:59:02 +00:00
Charlie Marsh
67d1f74587 Avoid raising TRY200 violations within new scopes (#3275) 2023-02-28 11:56:29 -05:00
Matthew Lloyd
1c79dff3bd Improve the message for PLW2901: use "outer" and "inner" judiciously (#3263) 2023-02-28 16:33:01 +00:00
Charlie Marsh
f5f09b489b Introduce dedicated CST tokens for other operator kinds (#3267) 2023-02-27 23:54:57 -05:00
Charlie Marsh
061495a9eb Make BoolOp its own located token (#3265) 2023-02-28 03:43:28 +00:00
Charlie Marsh
470e1c1754 Preserve comments on non-defaulted arguments (#3264) 2023-02-27 23:41:40 +00:00
Charlie Marsh
16be691712 Enable more non-panicking formatter tests (#3262) 2023-02-27 18:21:53 -05:00
Charlie Marsh
270015865b Don't flag keyword-based logging format strings (#3261) 2023-02-27 23:11:13 +00:00
Charlie Marsh
ccfa9d5b20 Deduplicate SIM116 errors (#3260) 2023-02-27 18:08:45 -05:00
Charlie Marsh
2261e194a0 Create dedicated Body nodes in the formatter CST (#3223) 2023-02-27 22:55:05 +00:00
Ville Skyttä
cd6413ca09 Match non-lowercase with S105 again (#3258) 2023-02-27 16:38:23 -05:00
Charlie Marsh
c65585e14a Use identifier_range for a few more rules (#3254) 2023-02-27 18:23:33 +00:00
Charlie Marsh
d2a6ed7be6 Upgrade RustPython (#3252) 2023-02-27 18:21:06 +00:00
Charlie Marsh
16e2dae0c2 Handle empty NamedTuple and TypedDict conversions (#3251) 2023-02-27 11:18:34 -05:00
Carlos Gonçalves
e8ba9c9e21 feat(W191): add indentation_contains_tabs (#3249) 2023-02-27 10:36:03 -05:00
Jonathan Plasse
d285f5c90a Run automatically format code blocks with Black (#3191) 2023-02-27 10:14:05 -05:00
Charlie Marsh
386ca7c9a1 Bump version to 0.0.253 (#3245) 2023-02-26 23:10:04 -05:00
Charlie Marsh
40c5abf16e Avoid flagging Pylint logging rules with starred arguments (#3244) 2023-02-26 22:58:24 -05:00
Matthew Lloyd
7e7aec7d74 Expand the range of the COM812 autofix to include the preceding token (#3241)
This prevents the UP034 autofix simultaneously stripping the
parentheses from generators in the same linter pass, which causes
a SyntaxError.

Closes #3234.

With this fix:

```python
$ cat test.py
the_first_one = next(
    (i for i in range(10) if i // 2 == 0)
)

$ cargo run --bin ruff check test.py --no-cache --select UP034,COM812 --fix
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/ruff check test.py --no-cache --select UP034,COM812 --fix`
Found 1 error (1 fixed, 0 remaining).

$ cat test.py
the_first_one = next(
    i for i in range(10) if i // 2 == 0
)
```

* Use format

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-02-27 03:47:07 +00:00
Ivan Gozali
4b5538f74e [pylint] W0603: global-statement (#3227)
Implements pylint rule [W0603: global-statement](https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/global-statement.html).

Currently checks for global statement usage in a few StmtKinds (as tested in the `pylint` `global-statement` test case [here](b70d2abd7f/tests/functional/g/globals.py)):

* Assign
* AugAssign
* ClassDef
* FunctionDef | AsyncFunctionDef
* Import
* ImportFrom
* Delete
2023-02-26 23:40:24 +00:00
Charlie Marsh
36d134fd41 Bump lint rule count to 500+ (#3240) 2023-02-26 18:10:09 -05:00
Chris Chan
0b7d6b9097 Implement pylint's else-if-used rule (PLR5501) (#3231)
Attempt to implement else-if-used
https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/else-if-used.html

Issue #970
2023-02-26 22:42:33 +00:00
Nick Pope
994e2e0903 Rename some flake8-simplify rules (#2915)
Renames the following rules that stood out to me at a glance as needing better names:

- `or-true` to `expr-or-true`
- `and-false` to `expr-and-false`
- `a-or-not-a` to `expr-or-not-expr`
- `a-and-not-a` to `expr-and-not-expr`

Related to #2902.
2023-02-26 22:35:15 +00:00
Luc Khai Hai
bc79f540e4 [flake8-django] DJ003, DJ006, DJ007 (#3236)
Implements [flake8-django](https://github.com/rocioar/flake8-django) rules:
- DJ03
- DJ06
- DJ07
2023-02-26 22:29:42 +00:00
Steve Dignam
3a78b59314 [flake8-pyi]: PYI011, PYI014 (#3238)
Implement PYI011 and PYI014 with the latest changes:

https://github.com/PyCQA/flake8-pyi/pull/326
https://github.com/PyCQA/flake8-pyi/issues/316

rel: https://github.com/charliermarsh/ruff/issues/848
rel: 4212bec43d/pyi.py (L718)
2023-02-26 22:11:58 +00:00
Edgar R. M
5f83851329 [pydocstyle]: Implement ignore-decorators (#3229) 2023-02-26 21:40:01 +00:00
Carlos Gonçalves
484ce7b8fc feat(E275): add Missing whitespace after keyword (#3225) 2023-02-26 21:36:05 +00:00
Charlie Marsh
1c75071136 Implement basic rendering of remaining AST nodes (#3233) 2023-02-26 05:05:56 +00:00
Charlie Marsh
51bca19c1d Add builders for common comment rendering (#3232) 2023-02-26 04:16:24 +00:00
Steve Dignam
a8a312e862 [flake8-pyi]: PYI009, PYI010, PYI021 (#3230)
PYI009 and PYI010 are very similar, always use `...` in function and class bodies in stubs.

PYI021 bans doc strings in stubs.

I think all of these rules should be relatively straightforward to implement auto fixes for but can do that later once we get all the other rules added.

rel: https://github.com/charliermarsh/ruff/issues/848
2023-02-25 22:29:04 -05:00
Ran Benita
33c31cda27 Add noqa_row to diagnostics JSON format (#3228)
In ruff-lsp (https://github.com/charliermarsh/ruff-lsp/pull/76) we want to add a "Disable \<rule\> for this line" quickfix. However, finding the correct line into which the `noqa` comment should be inserted is non-trivial (multi-line strings for example).

Ruff already has this info, so expose it in the JSON output for use by ruff-lsp.
2023-02-25 18:13:16 -05:00
Edgar R. M
cd9fbeb560 [bandit]: Do not treat "passed" as "password" for S105/S106/S107 (#3222) 2023-02-25 15:32:53 -05:00
Jeong YunWon
84e96cdcd9 More enum work (#3212) 2023-02-25 11:40:16 -05:00
Charlie Marsh
248590224a Avoid flagging logging-too-few-args with no arguments (#3220) 2023-02-24 21:57:49 -05:00
Charlie Marsh
bbc55cdb04 Allow ruff.toml file to be dot-prefixed (as .ruff.toml) (#3221) 2023-02-24 23:14:26 +00:00
Charlie Marsh
2792439eac Add TextMate to editor-integrations.md (#3219) 2023-02-24 22:57:33 +00:00
Charlie Marsh
0694aee1b6 Avoid EXE001 and EXE002 errors from stdin input (#3218) 2023-02-24 22:55:32 +00:00
Charlie Marsh
a17b5c134a Avoid rewriting any PEP 604 runtime annotations (#3217)
Following `pyupgrade`, we'll just _never_ touch these.

Closes #2981.

Closes #3215.
2023-02-24 22:39:58 +00:00
Charlie Marsh
42f61535b5 Normalize treatment of aliased and unaliased imports (#3216) 2023-02-24 22:11:03 +00:00
Carlos Gonçalves
1c01b3c934 fix(docs): broken links inside Configuration.md (#3205) 2023-02-24 18:55:33 +00:00
Jonathan Plasse
39b9a1637f Fix Markdown errors in docs (#3187) 2023-02-24 13:06:48 -05:00
Jonathan Plasse
2c692e3acf Remove --all from cargo fmt|test (#3208) 2023-02-24 12:52:59 -05:00
monosans
24add5f56c Avoid boolean-trap rules for ConfigParser get() methods (#3209) 2023-02-24 12:52:33 -05:00
Samuel Cormier-Iijima
0b7736ad79 [flake8-tidy-imports] fix autofix for relative imports (#3197) 2023-02-23 23:40:28 -05:00
Charlie Marsh
eef85067c8 Exclude globsets for --show-settings (#3201) 2023-02-24 04:23:00 +00:00
Jeong YunWon
da98fab4ae Adapt is-macro for a few enums (#3182) 2023-02-24 04:06:56 +00:00
Charlie Marsh
0f37a98d91 Use then-some pattern for sometimes-fixable rules (#3199) 2023-02-24 03:57:14 +00:00
Charlie Marsh
f38624824d Avoid autofixing some PT violations when comments are present (#3198) 2023-02-24 03:48:41 +00:00
Charlie Marsh
159422071e Handle end-of-line comments on excepthandler and alias (#3196) 2023-02-23 22:35:39 -05:00
Charlie Marsh
6eaacf96be Introduce a new CST element for slice segments (#3195) 2023-02-24 00:49:41 +00:00
Charlie Marsh
eb15371453 Make Locator available in AST-to-CST conversion pass (#3194) 2023-02-23 19:43:03 -05:00
Matt Nawara
198b301baf [pycodestyle] trailing-whitespace, blank-line-contains-whitespace (W291, W293) (#3122) 2023-02-23 19:04:45 -05:00
Jeong YunWon
c8c575dd43 Adapt BoolLike to flags (#3175) 2023-02-23 16:31:46 -05:00
Jonathan Plasse
6e54cd8233 Normalize relative markdown links (#3190) 2023-02-23 16:24:31 -05:00
Jonathan Plasse
a688a237d7 Add black to pre-commit (#3192) 2023-02-23 16:24:23 -05:00
Charlie Marsh
bda2a0007a Parenthesize numbers during attribute accesses (#3189) 2023-02-23 14:57:23 -05:00
Charlie Marsh
32d165b7ad Implement complex literal formatting (#3186) 2023-02-23 19:09:33 +00:00
Charlie Marsh
ac79bf4ee9 Implement float literal formatting (#3184) 2023-02-23 14:02:23 -05:00
Charlie Marsh
376eab3a53 Implement integer literal formatting (#3183) 2023-02-23 18:31:56 +00:00
Charlie Marsh
08be7bd285 Add a TODO to string_literal (#3181) 2023-02-23 12:46:20 -05:00
Charlie Marsh
f5241451d8 Use writeln with --show-settings (#3180) 2023-02-23 17:23:31 +00:00
Charlie Marsh
c9fe0708cb Run cargo update (#3179) 2023-02-23 12:09:36 -05:00
Charlie Marsh
09f8c487ea Update RustPython to support *tuple annotations (#3178) 2023-02-23 16:58:38 +00:00
Charlie Marsh
1e7233a8eb Add support for reformatting byte strings (#3176) 2023-02-23 16:50:24 +00:00
Charlie Marsh
f967f344fc Add support for basic Constant::Str formatting (#3173)
This PR enables us to apply the proper quotation marks, including support for escapes. There are some significant TODOs, especially around implicit concatenations like:

```py
(
  "abc"
  "def"
)
```

Which are represented as a single AST node, which requires us to tokenize _within_ the formatter to identify all the individual string parts.
2023-02-23 16:23:10 +00:00
Charlie Marsh
095f005bf4 Move RustPython vendored and helper code into its own crate (#3171) 2023-02-23 14:14:16 +00:00
Charlie Marsh
0f04aa2a5f Bind star patterns in match statements (#3169) 2023-02-23 12:39:03 +00:00
Jonathan Plasse
ad7ba77fff Fix ExceptionGroup F821 false positive (#3167) 2023-02-23 12:36:11 +00:00
Jeong YunWon
77d43795f8 Replace Autofix::is_enabled to result_like::BoolLike (#3165) 2023-02-23 07:29:13 -05:00
Jeong YunWon
4357f2be0f Add Autofix::is_enabled() to remove repeative patterns (#3159) 2023-02-22 23:52:07 -05:00
Charlie Marsh
e5c1f95545 Check-in updated snapshot (#3161) 2023-02-23 03:42:27 +00:00
Charlie Marsh
227ff62a4e Don't touch tuple brackets after in (#3160) 2023-02-23 03:10:24 +00:00
Charlie Marsh
d8e4902516 Un-modify tupleassign and function2 tests (#3158)
I manually changed these in #3080 and #3083 to get the tests passing (with notes around the deviations) -- but that's no longer necessary, now that we have proper testing that takes deviations into account.
2023-02-23 02:37:25 +00:00
Matthew Lloyd
e66739884f Add note about prioritizing naming convention over preservation (#3157) 2023-02-23 02:32:46 +00:00
Charlie Marsh
5fd827545b Add a trailing newline to all .py.expect files (#3156)
This just re-formats all the `.py.expect` files with Black, both to add a trailing newline and be doubly-certain that they're correctly formatted.

I also ensured that we add a hard line break after each statement, and that we avoid including an extra newline in the generated Markdown (since the code should contain the exact expected newlines).
2023-02-23 02:29:27 +00:00
Matthew Lloyd
c1ddcb8a60 [flake8-pie] Unnecessary list comprehension, with autofix (PIE802) (#3149) 2023-02-22 20:58:45 -05:00
Charlie Marsh
48a317d5f6 Change via to using (#3155) 2023-02-23 01:47:15 +00:00
Charlie Marsh
74e18b6cff Split up some docs sections (#3154) 2023-02-22 20:18:10 -05:00
Charlie Marsh
21d02cd51f Omit non-.py[i] files from module naming rules (#3153) 2023-02-23 00:38:46 +00:00
Charlie Marsh
049e77b939 Follow-up with some small doc changes (#3152) 2023-02-23 00:35:22 +00:00
Charlie Marsh
b9bfb81e36 Move configuration out of README and into permanent docs (#3150) 2023-02-22 19:25:53 -05:00
Charlie Marsh
2d4fae45d9 Avoid flagging unfixable TypedDict and NamedTuple definitions (#3148) 2023-02-22 23:23:25 +00:00
Charlie Marsh
726adb7efc Avoid suggesting 'is' for constant literals (#3146) 2023-02-22 22:37:22 +00:00
Charlie Marsh
dbdfdeb0e1 Add pre-commit note to docs (#3145) 2023-02-22 17:22:47 -05:00
Charlie Marsh
1c41789c2a Bump version to 0.0.252 (#3142) 2023-02-22 14:50:14 -05:00
Charlie Marsh
2f9de335db Upgrade RustPython to match new flattened exports (#3141) 2023-02-22 19:36:13 +00:00
Ran Benita
ba61bb6a6c Fix isort no-lines-before preceded by an empty section (#3139)
Fix isort no-lines-before preceded by an empty section

Fix #3138.
2023-02-22 14:35:53 -05:00
Charlie Marsh
17ab71ff75 Include match in nested block check (#3137) 2023-02-22 14:32:08 -05:00
Charlie Marsh
4ad4e3e091 Avoid useless-else-on-loop for break within match (#3136) 2023-02-22 19:12:44 +00:00
Florian Best
6ced5122e4 refactor(use-from-import): build fixed variant via AST (#3132) 2023-02-22 13:17:37 -05:00
Marijn Valk
7d55b417f7 add delta-rs to list of users (#3133) 2023-02-22 13:07:58 -05:00
Charlie Marsh
f0e0efc46f Upgrade RustPython to handle trailing commas in map patterns (#3130) 2023-02-22 11:17:13 -05:00
Charlie Marsh
1efa2e07ad Avoid match statement misidentification in token rules (#3129) 2023-02-22 15:44:45 +00:00
Charlie Marsh
df3932f750 Use file-specific quote for C408 (#3128) 2023-02-22 15:26:46 +00:00
Rupert Tombs
817d0b4902 Fix =/== error in ManualDictLookup (#3117) 2023-02-22 15:14:30 +00:00
Micha Reiser
ffd8e958fc chore: Upgrade Rust to 1.67.0 (#3125) 2023-02-22 10:03:17 -05:00
Micha Reiser
ed33b75bad test(ruff_python_formatter): Run all Black tests (#2993)
This PR changes the testing infrastructure to run all black tests and:

* Pass if Ruff and Black generate the same formatting
* Fail and write a markdown snapshot that shows the input code, the differences between Black and Ruff, Ruffs output, and Blacks output

This is achieved by introducing a new `fixture` macro (open to better name suggestions) that "duplicates" the attributed test for every file that matches the specified glob pattern. Creating a new test for each file over having a test that iterates over all files has the advantage that you can run a single test, and that test failures indicate which case is failing. 

The `fixture` macro also makes it straightforward to e.g. setup our own spec tests that test very specific formatting by creating a new folder and use insta to assert the formatted output.
2023-02-22 09:25:06 -05:00
Micha Reiser
262e768fd3 refactor(ruff): Implement doc_lines_from_tokens as iterator (#3124)
This is a nit refactor... It implements the extraction of document lines as an iterator instead of a Vector to avoid the extra allocation.
2023-02-22 09:22:06 -05:00
Ran Benita
bc3a9ce003 Mark typing.assert_never as no return (#3121)
This function always raises, so RET503 shouldn't trigger for it.
2023-02-22 09:15:39 -05:00
Charlie Marsh
48005d87f8 Add missing backticks from rustdoc (#3112) 2023-02-22 05:03:06 +00:00
Charlie Marsh
e37e9c2ca3 Skip EXE001 and EXE002 rules on Windows (#3111) 2023-02-21 23:39:56 -05:00
Matthieu Devlin
8fde63b323 [pylint] Implement E1205 and E106 (#3084) 2023-02-21 22:53:11 -05:00
Matthew Lloyd
97338e4cd6 [pylint] redefined-loop-name (W2901) (#3022)
Slightly broadens W2901 to cover `with` statements too.

Closes #2972.
2023-02-22 03:23:47 +00:00
Charlie Marsh
9645790a8b Support shell expansion for --config argument (#3107) 2023-02-21 23:33:41 +00:00
Charlie Marsh
18800c6884 Include file permissions in cache key (#3104) 2023-02-21 18:20:06 -05:00
Charlie Marsh
fd638a2e54 Bump version to 0.0.251 (#3105) 2023-02-21 18:13:59 -05:00
Charlie Marsh
fa1459d56e Avoid prefer-list-builtin for lambdas with *args or **kwargs (#3102) 2023-02-21 17:44:37 -05:00
Charlie Marsh
d93c5811ea Create bindings for MatchAs patterns (#3098) 2023-02-21 22:04:09 +00:00
Charlie Marsh
06e426f509 Bump version to 0.0.250 (#3095) 2023-02-21 15:20:46 -05:00
Carlos Gonçalves
6eb014b3b2 feat(B032): add b032 flake8_bugbear (#3085) 2023-02-21 19:53:29 +00:00
Charlie Marsh
d9fd78d907 Ignore setters in flake8-boolean-trap (#3092) 2023-02-21 19:31:00 +00:00
Charlie Marsh
37df07d2e0 Re-add compatibility to README (#3091) 2023-02-21 18:57:47 +00:00
Charlie Marsh
d5c65b5f1b Add support for structural pattern matching (#3047) 2023-02-21 18:52:10 +00:00
Charlie Marsh
cdc4e86158 Add support for TryStar (#3089) 2023-02-21 13:42:20 -05:00
Charlie Marsh
50ec6d3b0f Use LibCST to fix chained assertions (#3087) 2023-02-21 13:10:31 -05:00
Charlie Marsh
a6eb60cdd5 Enable function2 test (#3083) 2023-02-21 04:37:50 +00:00
Charlie Marsh
90c04b9cff Enable tupleassign test (#3080) 2023-02-21 00:42:23 +00:00
Charlie Marsh
b701cca779 Enable some already-passing Black tests (#3079) 2023-02-21 00:10:35 +00:00
Charlie Marsh
ce8953442d Add support for trailing colons in slice expressions (#3077) 2023-02-20 23:24:32 +00:00
Charlie Marsh
7d4e513a82 Omit while-True loops from implicit return enforcement (#3076) 2023-02-20 18:22:28 -05:00
Charlie Marsh
35f7f7b66d Avoid boolean-trap rules for positional-only builtin calls (#3075) 2023-02-20 23:08:18 +00:00
Charlie Marsh
6e02405bd6 Add StmtKind::Try; fix trailing newlines (#3074) 2023-02-20 22:55:32 +00:00
Carlos Gonçalves
b657468346 feat(B029): Add B029 from flake8-bugbear (#3068) 2023-02-20 15:57:13 -05:00
Micha Reiser
f72ed255e5 chore: Use LF on all platforms (#3005)
I worked on #2993 and ran into issues that the formatter tests are failing on Windows because `writeln!` emits `\n` as line terminator on all platforms, but `git` on Windows converted the line endings in the snapshots to `\r\n`.

I then tried to replicate the issue on my Windows machine and was surprised that all linter snapshot tests are failing on my machine. I figured out after some time that it is due to my global git config keeping the input line endings rather than converting to `\r\n`. 

Luckily, I've been made aware of #2033 which introduced an "override" for the `assert_yaml_snapshot` macro that normalizes new lines, by splitting the formatted string using the platform-specific newline character. This is a clever approach and gives nice diffs for multiline fixes but makes assumptions about the setup contributors use and requires special care whenever we use line endings inside of tests. 

I recommend that we remove the special new line handling and use `.gitattributes` to enforce the use of `LF` on all platforms [guide](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings). This gives us platform agnostic tests without having to worry about line endings in our tests or different git configurations.

## Note

It may be necessary for Windows contributors to run the following command to update the line endings of their files

```bash
git rm --cached -r .
git reset --hard
```
2023-02-20 20:13:37 +00:00
Charlie Marsh
7e9dea0027 Change contributing suggestion (#3067) 2023-02-20 20:05:38 +00:00
Colin Delahunty
9545958ad8 [flake8-simplify]: Implement manual-dict-lookup (#2767) 2023-02-20 20:00:59 +00:00
Colin Delahunty
41faa335d1 [tryceratops]: Verbose Log Messages (#3036) 2023-02-20 18:21:04 +00:00
Charlie Marsh
4cfa350112 Bump version to 0.0.249 (#3063) 2023-02-20 13:11:29 -05:00
Charlie Marsh
41f163fc8d Avoid assert() to assert statement conversion in expressions (#3062) 2023-02-20 17:49:22 +00:00
Charlie Marsh
d21dd994e6 Increase expected size of FormatElement (#3049) 2023-02-20 12:47:35 -05:00
Josh Karpel
6f5a6b8c8b Do not autofix E731 in class bodies (#3050) 2023-02-20 12:38:42 -05:00
Jeong YunWon
35606d7b05 clean up to fix nightly clippy warnings and dedents (#3057) 2023-02-20 09:33:47 -05:00
Matthew Lloyd
3ad257cfea Add PDM to "Who's Using Ruff?" (#3048) 2023-02-20 03:58:22 +00:00
Charlie Marsh
b39f960cd1 Relax constraints on pep8-naming module validation (#3043) 2023-02-19 17:34:23 -05:00
Charlie Marsh
c297d46899 Remove unused AsFormat trait for Option<T> (#3041)
We should re-add this, but it's currently unused and doesn't compile under 1.66.0.

See: #3039.
2023-02-19 20:19:35 +00:00
Jonathan Plasse
d6a100028c Update docs and pre-commit after #3006 (#3038) 2023-02-19 14:23:01 -05:00
Jonathan Plasse
35d4e03f2a Fix ruff_dev regex workspace dependency (#3037) 2023-02-19 18:02:08 +00:00
Charlie Marsh
41e77bb01d Add some additional users to "Who's Using Ruff?" (#3035) 2023-02-19 16:30:01 +00:00
Charlie Marsh
2ff3dd5fbe Bump version to 0.0.248 (#3034) 2023-02-19 16:21:30 +00:00
Charlie Marsh
0f0e7a521a Avoid false-positives for break in with (#3032) 2023-02-19 11:17:04 -05:00
Jonathan Plasse
b75663be6d Add missing rust-version in crates (#3009) 2023-02-19 15:07:17 +00:00
Tomer Chachamu
4d3d04ee61 [PLE0101] error when __init__ returns a value (#3007) 2023-02-19 14:54:43 +00:00
Manuel Jacob
87422ba362 Add configuration option for C408 to allow dict calls with keyword arguments. (#2977)
When creating a dict with string keys, some prefer to call dict instead of writing a dict literal.
For example: `dict(a=1, b=2, c=3)` instead of `{"a": 1, "b": 2, "c": 3}`.
2023-02-19 14:47:03 +00:00
Jeremy Goh
c1d2976fff [docs] Add docs for flake8-implicit-str-concat rules (#3028) 2023-02-19 14:38:59 +00:00
Jeremy Goh
13281cd9ca [docs] Add some docs for flake8-simplify (#3027) 2023-02-19 14:26:56 +00:00
Jonathan Plasse
e53652779d Avoid raising B027 violations in .pyi files (#3016) 2023-02-19 14:21:33 +00:00
Jonathan Plasse
db4c611c6f Fix broken links and markdown style (#3017) 2023-02-19 08:46:49 -05:00
Charlie Marsh
c25be31eb1 Fix documentation-link detection in generate_mkdocs.py (#3030) 2023-02-19 12:20:44 +00:00
Micha Reiser
a7c533634d chore: Remove default_members from Cargo.toml (#3006)
This PR removes the `default_members` from the workspace configuration. 

## Why

I'm not familiar with the motivation for why the `default_members` setting was added initially, and I do not object to keeping it. I'll explain my motivation for removing it below. 

My main reason for removing the `default_members` override is that new contributors may not know that `cargo test`, `cargo build`, and other commands only run on a subset of crates. They may then be surprised that their PRs are failing in CI, but everything works locally. 

My guess why `default_members` was added is to speed up the development workflow. That's fair, but I question the value because `ruff` is the heaviest crate to build.
2023-02-19 07:18:47 -05:00
Simon Brugman
cfa6883431 docs(readme): add Diffusers (#3029) 2023-02-19 07:10:02 -05:00
Nyakku Shigure
216aa929af Remove duplicate underline in B007 autofix message (#3021) 2023-02-18 19:38:20 -05:00
Simon Brugman
9e45424ed6 [pycodestyle] autofix useless semicolons (#3001) 2023-02-17 18:52:42 -05:00
Charlie Marsh
db7f16e276 Support positional messages in assertion rewrites (#3002) 2023-02-17 23:44:13 +00:00
Charlie Marsh
a10a500a26 Ignore namedtuple methods in flake8-self (#2998) 2023-02-17 17:16:25 -05:00
Charlie Marsh
b9fef7cef7 Unlink flake8-bugbear in summary (#2997) 2023-02-17 15:58:33 -05:00
Charlie Marsh
34294ccc00 Deduplicate user list (#2996) 2023-02-17 20:07:42 +00:00
Simon Brugman
a934d01bdb [flake8-tidy-imports] extend autofix of relative imports (#2990)
This extends the autofix for TID252 to work with for relative imports without `module` (i.e. `from .. import`). Tested with `matplotlib` and `bokeh`.
(Previously it would panic on unwrap of the module) 

Note that pandas has [replaced](6057d7a93e) `absolufy-imports` with `ruff` now!
2023-02-17 19:35:28 +00:00
Simon Brugman
0dd590f137 Fix for F541 unescape f-string (#2971) 2023-02-17 14:27:01 -05:00
Charlie Marsh
909a5c3253 Avoid zero-indexed column for IOError (#2995) 2023-02-17 14:14:28 -05:00
Charlie Marsh
5c987874c4 Enforce D403 on methods (#2992) 2023-02-17 18:05:48 +00:00
Nyakku Shigure
0cfe4f9c69 Remove a whitespace in B004 message (#2991) 2023-02-17 12:37:08 -05:00
Charlie Marsh
6a369e4a30 Remove via from sentence in README (#2987) 2023-02-17 13:49:09 +00:00
Charlie Marsh
6f97e2c457 Split list of users into top-level and dedicated section (#2986) 2023-02-17 13:36:32 +00:00
Charlie Marsh
bebd412469 Adjust header depth in docs (#2985) 2023-02-17 13:19:55 +00:00
Charlie Marsh
cd1f57b713 Move FAQ into MkDocs (#2984) 2023-02-17 13:15:53 +00:00
Charlie Marsh
a0912deb2b Move editor integrations into MkDocs (#2983) 2023-02-17 13:12:20 +00:00
Charlie Marsh
50ee14a418 Fix references to specific settings in README.md (#2982) 2023-02-17 13:07:37 +00:00
Martin Fischer
f5adbbebc5 Fix table of contents enumeration 2023-02-17 07:55:50 -05:00
Martin Fischer
c88e05dc1b Merge Reference README section into Configuration section 2023-02-17 07:55:50 -05:00
Martin Fischer
d658bfc024 Remove options from README 2023-02-17 07:55:50 -05:00
Martin Fischer
b0d72c47b4 refactor: Move Top-level heading into ruff_dev 2023-02-17 07:55:50 -05:00
Martin Fischer
8195873cdf Remove rule tables from README 2023-02-17 07:55:50 -05:00
Martin Fischer
bf8108469f Remove auto-generated table of contents 2023-02-17 07:55:50 -05:00
Martin Fischer
a2277cfeba refactor: Move fix symbol legend into ruff_dev 2023-02-17 07:55:50 -05:00
Charlie Marsh
180541a924 Unify comment terminology with that of rome_formatter (#2979) 2023-02-17 03:02:25 +00:00
Simon Brugman
34664a0ca0 [numpy] numpy-legacy-random (#2960)
The new `Generator` in NumPy uses bits provided by [PCG64](https://numpy.org/doc/stable/reference/random/bit_generators/pcg64.html#numpy.random.PCG64) which has better statistical properties than the legacy [MT19937](https://numpy.org/doc/stable/reference/random/bit_generators/mt19937.html#numpy.random.MT19937) used in [RandomState](https://numpy.org/doc/stable/reference/random/legacy.html#numpy.random.RandomState). Global random functions can also be problematic with parallel processing.

This rule is probably quite useful for data scientists (perhaps in combination with `nbqa`)

References:
- [Legacy Random Generation](https://numpy.org/doc/stable/reference/random/legacy.html#legacy)
- [Random Sampling](https://numpy.org/doc/stable/reference/random/index.html#random-quick-start)
- [Using PyTorch + NumPy? You're making a mistake.](https://tanelp.github.io/posts/a-bug-that-plagues-thousands-of-open-source-ml-projects/)
2023-02-17 02:06:30 +00:00
Charlie Marsh
e081455b06 Add support for file-scoped noqa directives (#2978)
# Summary

This allows users to do things like:

```py
# ruff: noqa: F401
```

...to ignore all `F401` directives in a file. It's equivalent to `per-file-ignores`, but allows users to specify the behavior inline.

Note that Flake8 does _not_ support this, so we _don't_ respect `# flake8: noqa: F401`. (Flake8 treats that as equivalent to `# flake8: noqa`, so ignores _all_ errors in the file. I think all of [these usages](https://cs.github.com/?scopeName=All+repos&scope=&q=%22%23+flake8%3A+noqa%3A+%22) are probably mistakes!)

A couple notes on the details:

- If a user has `# ruff: noqa: F401` in the file, but also `# noqa: F401` on a line that would legitimately trigger an `F401` violation, we _do_ mark that as "unused" for `RUF100` purposes. This may be the wrong choice. The `noqa` is legitimately unused, but it's also not "wrong". It's just redundant.
- If a user has `# ruff: noqa: F401`, and runs `--add-noqa`, we _won't_ add `# noqa: F401` to any lines (which seems like the obvious right choice to me).

Closes #1054 (which has some extra pieces that I'll carve out into a separate issue).

Closes #2446.
2023-02-17 01:59:01 +00:00
Artem Mukhin
4f18fa6733 Add test case for '\u' prefix in B005 (#2976)
Based on #2958.
2023-02-16 19:45:29 -05:00
Charlie Marsh
6088a36cd3 Use line_suffix for end-of-line comments (#2975) 2023-02-16 18:37:40 -05:00
Charlie Marsh
66a162fa40 Handle non-from __future__ imports (#2974)
These are uncommon, but currently panic.

Closes #2967.
2023-02-16 22:56:03 +00:00
Mike Taves
e6722f92ed Add Rust Trove classifier (#2973) 2023-02-16 17:38:36 -05:00
Charlie Marsh
750c28868f Enable jemalloc on FreeBSD and NetBSD (#2965) 2023-02-16 15:21:34 -05:00
Charlie Marsh
5157f584ab Improve pow operator spacing (#2970)
Ensure that we add spaces to expressions like `foo.bar() ** 2`.
2023-02-16 15:17:32 -05:00
Charlie Marsh
1c01ec21cb Regenerate expected Black snapshots (#2968) 2023-02-16 19:39:17 +00:00
Manuel Jacob
879512742f Skip .pytype directory by default. (#2966)
Pytype stores .pyi files in .pytype that ruff shouldn’t check or touch.
2023-02-16 14:38:08 -05:00
Florian Best
a919041dda feat(isort): Implement isort.force_to_top (#2877) 2023-02-16 19:01:59 +00:00
Charlie Marsh
059601d968 Avoid trying to fix implicit returns with control flow (#2962) 2023-02-16 13:42:46 -05:00
Charlie Marsh
2ec1701543 Remove link in asyncio.create_task (#2963) 2023-02-16 17:50:56 +00:00
Charlie Marsh
370c3a5daf Remove mdcat dependency (#2959) 2023-02-16 12:09:37 -05:00
Charlie Marsh
fdcb78fd8c Avoid jemallocator on BSD (#2957) 2023-02-16 11:48:51 -05:00
Simon Brugman
2a744d24e5 docs: flake8-self remove unnecessary backticks (#2951) 2023-02-16 08:25:34 -05:00
Simon Brugman
cc30738148 Implement flake8-module-naming (#2855)
- Implement N999 (following flake8-module-naming) in pep8_naming
- Refactor pep8_naming: split rules.rs into file per rule
- Documentation for majority of the violations

Closes https://github.com/charliermarsh/ruff/issues/2734
2023-02-16 04:20:33 +00:00
Edgar R. M
147c6ff1db Exclude crates/ruff_python_formatter/resources from pre-commit check (#2947) 2023-02-15 22:56:42 -05:00
Charlie Marsh
036380e6a8 Fix add-required-import with multi-line offsets (#2946) 2023-02-16 03:24:55 +00:00
Charlie Marsh
b6587e51ee Use an enum to represent composition kind (#2945) 2023-02-15 22:14:00 -05:00
Simon Brugman
1bc37110d4 [flake8-pytest-style] autofix for composite-assertion (PT018) (#2732) 2023-02-16 00:36:07 +00:00
Lunarmagpie
28acdb76cf Add support for ensure_future for RUF006 (#2943) 2023-02-15 23:18:11 +00:00
Martin Fischer
7b09972c97 Merge convert-loop-to-any & convert-loop-to-all to reimplemented-builtin 2023-02-15 16:24:31 -05:00
946 changed files with 38047 additions and 14528 deletions

6
.gitattributes vendored Normal file
View File

@@ -0,0 +1,6 @@
* text=auto eol=lf
crates/ruff/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
crates/ruff/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
ruff.schema.json linguist-generated=true text=auto eol=lf

View File

@@ -29,7 +29,7 @@ jobs:
- 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
- run: git diff --exit-code -- README.md ruff.schema.json docs
cargo-fmt:
name: "cargo fmt"

View File

@@ -1,9 +1,18 @@
fail_fast: true
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
hooks:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.16
hooks:
- id: mdformat
additional_dependencies:
- mdformat-black
- black==23.1.0 # Must be the latest version of Black
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
hooks:
@@ -28,11 +37,15 @@ repos:
pass_filenames: false
- id: ruff
name: ruff
entry: cargo run -- --no-cache --fix
entry: cargo run -p ruff_cli -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: rust
types_or: [python, pyi]
require_serial: true
exclude: ^crates/ruff/resources
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*
)$
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
@@ -40,5 +53,16 @@ repos:
pass_filenames: false
exclude: target
# Black
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*
)$
ci:
skip: [cargo-fmt, clippy, dev-generate-all]

View File

@@ -20,8 +20,7 @@ the intention of adding a stable public API in the future.
### `select`, `extend-select`, `ignore`, and `extend-ignore` have new semantics ([#2312](https://github.com/charliermarsh/ruff/pull/2312))
Previously, the interplay between `select` and its related options could lead to unexpected
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore
ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
behavior. For example, `ruff --select E501 --ignore ALL` and `ruff --select E501 --extend-ignore ALL` behaved differently. (See [#2312](https://github.com/charliermarsh/ruff/pull/2312) for more
examples.)
When Ruff determines the enabled rule set, it has to reconcile `select` and `ignore` from a variety
@@ -74,7 +73,7 @@ ruff rule E402 --format json # Works! (And preferred.)
This change is largely backwards compatible -- most users should experience
no change in behavior. However, please note the following exceptions:
* Subcommands will now fail when invoked with unsupported arguments, instead
- Subcommands will now fail when invoked with unsupported arguments, instead
of silently ignoring them. For example, the following will now fail:
```console
@@ -83,16 +82,16 @@ no change in behavior. However, please note the following exceptions:
(the `clean` command doesn't support `--respect-gitignore`.)
* The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
- The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
For example, prior to this release, running `ruff rule` would run `ruff` over a file or
directory called `rule`. Now, `ruff rule` would invoke the `rule` subcommand. This should
only impact projects with files or directories named `rule`, `check`, `explain`, `clean`,
or `generate-shell-completion`.
* Scripts that invoke ruff should supply `--` before any positional arguments.
- Scripts that invoke ruff should supply `--` before any positional arguments.
(The semantics of `ruff -- <arg>` have not changed.)
* `--explain` previously treated `--format grouped` as a synonym for `--format text`.
- `--explain` previously treated `--format grouped` as a synonym for `--format text`.
This is no longer supported; instead, use `--format text`.
## 0.0.226

View File

@@ -29,23 +29,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -132,7 +132,7 @@ version 2.0, available [here](https://www.contributor-covenant.org/version/2/0/c
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the [FAQ](https://www.contributor-covenant.org/faq).
Translations are available [here](https://www.contributor-covenant.org/translations).
[homepage]: https://www.contributor-covenant.org

View File

@@ -29,13 +29,10 @@ If you're looking for a place to start, we recommend implementing a new lint rul
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
existing Python plugins, which can be used as a reference implementation.
As a concrete example: consider taking on one of the rules from the [`tryceratops`](https://github.com/charliermarsh/ruff/issues/2056)
plugin, and looking to the originating [Python source](https://github.com/guilatrova/tryceratops)
As a concrete example: consider taking on one of the rules from the [`flake8-pyi`](https://github.com/charliermarsh/ruff/issues/848)
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi)
for guidance.
Alternatively, we've started work on the [`flake8-pyi`](https://github.com/charliermarsh/ruff/issues/848)
plugin (see the [Python source](https://github.com/PyCQA/flake8-pyi)) -- another good place to start.
### Prerequisites
Ruff is written in Rust. You'll need to install the
@@ -52,16 +49,16 @@ cargo install cargo-insta
After cloning the repository, run Ruff locally with:
```shell
cargo run check /path/to/file.py --no-cache
cargo run -p ruff_cli -- check /path/to/file.py --no-cache
```
Prior to opening a pull request, ensure that your code has been auto-formatted,
and that it passes both the lint and test validation checks:
```shell
cargo fmt --all # Auto-formatting...
cargo fmt # Auto-formatting...
cargo clippy --fix --workspace --all-targets --all-features # Linting...
cargo test --all # Testing...
cargo test # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
@@ -106,15 +103,15 @@ At time of writing, the repository includes the following crates:
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).
2. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/abstract_base_class.rs`).
3. In that file, define a violation struct. You can grep for `define_violation!` to see examples.
4. Map the violation struct to a rule code in `crates/ruff/src/registry.rs` (e.g., `E402`).
5. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast.rs` (for AST-based
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. 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`
(for text-based checks), or `crates/ruff/src/checkers/filesystem.rs` (for filesystem-based
checks).
6. Add a test fixture.
7. Update the generated files (documentation and generated code).
1. Add a test fixture.
1. Update the generated files (documentation and generated code).
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
@@ -135,10 +132,10 @@ contain a variety of violations and non-violations designed to evaluate and demo
of your lint rule.
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
locally with (e.g.) `cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `crates/ruff/src/[linter]/mod.rs` file. Then, run `cargo test --all`.
`test_case` macro in the relevant `crates/ruff/src/[linter]/mod.rs` file. Then, run `cargo test`.
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
generated snapshot, then commit the snapshot file alongside the rest of your changes.
@@ -146,15 +143,18 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
#### Rule naming convention
The rule name should make sense when read as "allow *rule-name*" or "allow *rule-name* items".
The rule name should make sense when read as "allow _rule-name_" or "allow _rule-name_ items".
This implies that rule names:
* should state the bad thing being checked for
- should state the bad thing being checked for
* should not contain instructions on what you what you should use instead
- should not contain instructions on what you what you should use instead
(these belong in the rule documentation and the `autofix_title` for rules that have autofix)
When re-implementing rules from other linters, this convention is given more importance than
preserving the original rule name.
### Example: Adding a new configuration option
Ruff's user-facing settings live in a few different places.
@@ -186,14 +186,19 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
To preview any changes to the documentation locally:
1. Install MkDocs and Material for MkDocs with:
```shell
pip install -r docs/requirements.txt
```
2. Generate the MkDocs site with:
1. Generate the MkDocs site with:
```shell
python scripts/generate_mkdocs.py
```
3. Run the development server with:
1. Run the development server with:
```shell
mkdocs serve
```

961
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,21 @@
[workspace]
members = ["crates/*"]
default-members = ["crates/ruff", "crates/ruff_cli"]
[workspace.package]
edition = "2021"
rust-version = "1.67.0"
[workspace.dependencies]
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
is-macro = { version = "0.2.2" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
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" }

3954
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -4,3 +4,4 @@ extend-exclude = ["snapshots", "black"]
[default.extend-words]
trivias = "trivias"
hel = "hel"
whos = "whos"

View File

@@ -1,7 +1,8 @@
[package]
name = "flake8-to-ruff"
version = "0.0.247"
edition = "2021"
version = "0.0.253"
edition = { workspace = true }
rust-version = { workspace = true }
[dependencies]
anyhow = { workspace = true }

View File

@@ -84,7 +84,7 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
2. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)

View File

@@ -1,9 +1,9 @@
[package]
name = "ruff"
version = "0.0.247"
version = "0.0.253"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
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"
@@ -16,6 +16,10 @@ crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
ruff_rustpython = { path = "../ruff_rustpython" }
anyhow = { workspace = true }
bisection = { version = "0.1.0" }
bitflags = { version = "1.3.2" }
@@ -23,12 +27,14 @@ 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" }
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" }
is-macro = { workspace = true }
itertools = { workspace = true }
libcst = { workspace = true }
log = { version = "0.4.17" }
@@ -39,8 +45,7 @@ num-traits = "0.2.15"
once_cell = { workspace = true }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { workspace = true }
ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" }
result-like = "0.4.6"
rustc-hash = { workspace = true }
rustpython-common = { workspace = true }
rustpython-parser = { workspace = true }
@@ -57,7 +62,6 @@ titlecase = { version = "2.2.1" }
toml = { workspace = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack 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" }
@@ -66,9 +70,6 @@ serde-wasm-bindgen = { version = "0.4" }
js-sys = { version = "0.3.60" }
wasm-bindgen = { version = "0.2.83" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
is_executable = "1.0.1"
[dev-dependencies]
insta = { version = "1.19.0", features = ["yaml", "redactions"] }
test-case = { version = "2.2.2" }

View File

@@ -107,3 +107,7 @@ class Foo:
# ANN101
def foo(self, /, a: int, b: int) -> int:
pass
# OK
def f(*args: *tuple[int]) -> None: ...

View File

@@ -19,6 +19,8 @@ token = "s3cr3t"
secrete = "s3cr3t"
safe = password = "s3cr3t"
password = safe = "s3cr3t"
PASSWORD = "s3cr3t"
PassWord = "s3cr3t"
d["password"] = "s3cr3t"
d["pass"] = "s3cr3t"
@@ -61,3 +63,15 @@ if token == "3\t4":
if token == "5\r6":
pass
# These should not be flagged
passed_msg = "You have passed!"
compassion = "Please don't match!"
impassable = "You shall not pass!"
passwords = ""
PASSWORDS = ""
passphrases = ""
PassPhrases = ""
tokens = ""
secrets = ""

View File

@@ -57,6 +57,12 @@ dict.fromkeys(("world",), True)
{}.deploy(True, False)
getattr(someobj, attrname, False)
mylist.index(True)
int(True)
str(int(False))
cfg.get("hello", True)
cfg.getint("hello", True)
cfg.getfloat("hello", True)
cfg.getboolean("hello", True)
class Registry:
@@ -66,3 +72,11 @@ class Registry:
# FBT001: Boolean positional arg in function definition
def __setitem__(self, switch: Switch, value: bool) -> None:
self._switches[switch.value] = value
@foo.setter
def foo(self, value: bool) -> None:
pass
# FBT001: Boolean positional arg in function definition
def foo(self, value: bool) -> None:
pass

View File

@@ -20,6 +20,8 @@ s.rstrip(r"\n\t ") # warning
s.strip("a") # no warning
s.strip("") # no warning
s.strip("ああ") # warning
s.strip("\ufeff") # no warning
s.strip("\u0074\u0065\u0073\u0074") # warning
from somewhere import other_type, strip

View File

@@ -105,3 +105,25 @@ while True:
pass
finally:
break # warning
while True:
try:
pass
finally:
match *0, 1, *2:
case 0,:
y = 0
case 0, *x:
break # warning
while True:
try:
pass
finally:
match *0, 1, *2:
case 0,:
y = 0
case 0, *x:
pass # no warning

View File

@@ -0,0 +1,101 @@
"""
Should emit:
B027 - on lines 13, 16, 19, 23
"""
import abc
from abc import ABC
from abc import abstractmethod, abstractproperty
from abc import abstractmethod as notabstract
from abc import abstractproperty as notabstract_property
class AbstractClass(ABC):
def empty_1(self): # error
...
def empty_2(self): # error
pass
def empty_3(self): # error
"""docstring"""
...
def empty_4(self): # error
"""multiple ellipsis/pass"""
...
pass
...
pass
@notabstract
def abstract_0(self):
...
@abstractmethod
def abstract_1(self):
...
@abstractmethod
def abstract_2(self):
pass
@abc.abstractmethod
def abstract_3(self):
...
@abc.abstractproperty
def abstract_4(self):
...
@abstractproperty
def abstract_5(self):
...
@notabstract_property
def abstract_6(self):
...
def body_1(self):
print("foo")
...
def body_2(self):
self.body_1()
class NonAbstractClass:
def empty_1(self): # safe
...
def empty_2(self): # safe
pass
# ignore @overload, fixes issue #304
# ignore overload with other imports, fixes #308
import typing
import typing as t
import typing as anything
from typing import Union, overload
class AbstractClass(ABC):
@overload
def empty_1(self, foo: str):
...
@typing.overload
def empty_1(self, foo: int):
...
@t.overload
def empty_1(self, foo: list):
...
@anything.overload
def empty_1(self, foo: float):
...
@abstractmethod
def empty_1(self, foo: Union[str, int, list, float]):
...

View File

@@ -0,0 +1,14 @@
"""
Should emit:
B029 - on lines 8 and 13
"""
try:
pass
except ():
pass
try:
pass
except () as e:
pass

View File

@@ -0,0 +1,29 @@
"""
Should emit:
B032 - on lines 9, 10, 12, 13, 16-19
"""
# Flag these
dct = {"a": 1}
dct["b"]: 2
dct.b: 2
dct["b"]: "test"
dct.b: "test"
test = "test"
dct["b"]: test
dct["b"]: test.lower()
dct.b: test
dct.b: test.lower()
# Do not flag below
typed_dct: dict[str, int] = {"a": 1}
typed_dct["b"] = 2
typed_dct.b = 2
class TestClass:
def test_self(self):
self.test: int

View File

@@ -62,3 +62,11 @@ except Exception as e:
raise RuntimeError("boom!")
else:
raise RuntimeError("bang!")
try:
...
except Exception as e:
match 0:
case 0:
raise RuntimeError("boom!")

View File

@@ -626,3 +626,8 @@ result = function(
bar,
**{'ham': spam}
)
# Make sure the COM812 and UP034 rules don't autofix simultaneously and cause a syntax error.
the_first_one = next(
(i for i in range(10) if i // 2 == 0) # COM812 fix should include the final bracket
)

View File

@@ -0,0 +1,21 @@
from django.shortcuts import render
def test_view1(request):
return render(request, "index.html", locals())
def test_view2(request):
return render(request, "index.html", context=locals())
def test_view3(request):
return render(request, "index.html")
def test_view4(request):
return render(request, "index.html", {})
def test_view5(request):
return render(request, "index.html", context={})

View File

@@ -0,0 +1,11 @@
from django import forms
class TestModelForm1(forms.ModelForm):
class Meta:
exclude = ["bar"]
class TestModelForm2(forms.ModelForm):
class Meta:
fields = ["foo"]

View File

@@ -0,0 +1,16 @@
from django import forms
class TestModelForm1(forms.ModelForm):
class Meta:
fields = "__all__"
class TestModelForm2(forms.ModelForm):
class Meta:
fields = b"__all__"
class TestModelForm3(forms.ModelForm):
class Meta:
fields = ["foo"]

View File

@@ -0,0 +1,10 @@
# no error
all((x.id for x in bar))
all(x.id for x in bar)
all(x.id for x in bar)
any(x.id for x in bar)
any({x.id for x in bar})
# PIE 802
any([x.id for x in bar])
all([x.id for x in bar])

View File

@@ -18,3 +18,10 @@ class Foo:
class FooTable(BaseTable):
bar = fields.ListField(list)
lambda *args, **kwargs: []
lambda *args: []
lambda **kwargs: []

View File

@@ -0,0 +1,14 @@
import sys
from sys import version_info as python_version
if sys.version_info < (3, 9): ... # OK
if sys.version_info >= (3, 9): ... # OK
if sys.version_info == (3, 9): ... # OK
if sys.version_info <= (3, 10): ... # OK
if sys.version_info > (3, 10): ... # OK
if python_version > (3, 10): ... # OK

View File

@@ -0,0 +1,18 @@
import sys
from sys import version_info as python_version
if sys.version_info < (3, 9): ... # OK
if sys.version_info >= (3, 9): ... # OK
if sys.version_info == (3, 9): ... # OK
if sys.version_info == (3, 9): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info <= (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info <= (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if sys.version_info > (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons
if python_version > (3, 10): ... # Error: PYI006 Use only `<` and `>=` for version info comparisons

View File

@@ -0,0 +1,14 @@
def bar():
... # OK
def foo():
pass # OK, since we're not in a stub file
class Bar:
... # OK
class Foo:
pass # OK, since we're not in a stub file

View File

@@ -0,0 +1,8 @@
def bar(): ... # OK
def foo():
pass # ERROR PYI009, since we're in a stub file
class Bar: ... # OK
class Foo:
pass # ERROR PYI009, since we're in a stub file

View File

@@ -0,0 +1,18 @@
def bar():
... # OK
def foo():
"""foo""" # OK
def buzz():
print("buzz") # OK, not in stub file
def foo2():
123 # OK, not in a stub file
def bizz():
x = 123 # OK, not in a stub file

View File

@@ -0,0 +1,12 @@
def bar(): ... # OK
def foo():
"""foo""" # OK, strings are handled by another rule
def buzz():
print("buzz") # ERROR PYI010
def foo2():
123 # ERROR PYI010
def bizz():
x = 123 # ERROR PYI010

View File

@@ -0,0 +1,79 @@
def f12(
x,
y: str = os.pathsep, # OK
) -> None:
...
def f11(*, x: str = "x") -> None: # OK
...
def f13(
x: list[str] = [
"foo",
"bar",
"baz",
] # OK
) -> None:
...
def f14(
x: tuple[str, ...] = (
"foo",
"bar",
"baz",
) # OK
) -> None:
...
def f15(
x: set[str] = {
"foo",
"bar",
"baz",
} # OK
) -> None:
...
def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: # OK
...
def f17(
x: str = "foo" + "bar", # OK
) -> None:
...
def f18(
x: str = b"foo" + b"bar", # OK
) -> None:
...
def f19(
x: object = "foo" + 4, # OK
) -> None:
...
def f20(
x: int = 5 + 5, # OK
) -> None:
...
def f21(
x: complex = 3j - 3j, # OK
) -> None:
...
def f22(
x: complex = -42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -0,0 +1,63 @@
def f12(
x,
y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f11(*, x: str = "x") -> None: ... # OK
def f13(
x: list[
str
] = [ # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x: tuple[
str, ...
] = ( # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x: set[
str
] = { # Error PYI011 Only simple default values allowed for typed arguments
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(
x: frozenset[
bytes
] = frozenset( # Error PYI011 Only simple default values allowed for typed arguments
{b"foo", b"bar", b"baz"}
)
) -> None: ...
def f17(
x: str = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ "bar",
) -> None: ...
def f18(
x: str = b"foo" # Error PYI011 Only simple default values allowed for typed arguments
+ b"bar",
) -> None: ...
def f19(
x: object = "foo" # Error PYI011 Only simple default values allowed for typed arguments
+ 4,
) -> None: ...
def f20(
x: int = 5
+ 5, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f21(
x: complex = 3j
- 3j, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...

View File

@@ -0,0 +1,79 @@
def f12(
x,
y=os.pathsep, # OK
) -> None:
...
def f11(*, x="x") -> None:
... # OK
def f13(
x=[ # OK
"foo",
"bar",
"baz",
]
) -> None:
...
def f14(
x=( # OK
"foo",
"bar",
"baz",
)
) -> None:
...
def f15(
x={ # OK
"foo",
"bar",
"baz",
}
) -> None:
...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None:
... # OK
def f17(
x="foo" + "bar", # OK
) -> None:
...
def f18(
x=b"foo" + b"bar", # OK
) -> None:
...
def f19(
x="foo" + 4, # OK
) -> None:
...
def f20(
x=5 + 5, # OK
) -> None:
...
def f21(
x=3j - 3j, # OK
) -> None:
...
def f22(
x=-42.5j + 4.3j, # OK
) -> None:
...

View File

@@ -0,0 +1,45 @@
def f12(
x,
y=os.pathsep, # Error PYI014
) -> None: ...
def f11(*, x="x") -> None: ... # OK
def f13(
x=[ # Error PYI014
"foo",
"bar",
"baz",
]
) -> None: ...
def f14(
x=( # Error PYI014
"foo",
"bar",
"baz",
)
) -> None: ...
def f15(
x={ # Error PYI014
"foo",
"bar",
"baz",
}
) -> None: ...
def f16(x=frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Error PYI014
def f17(
x="foo" + "bar", # Error PYI014
) -> None: ...
def f18(
x=b"foo" + b"bar", # Error PYI014
) -> None: ...
def f19(
x="foo" + 4, # Error PYI014
) -> None: ...
def f20(
x=5 + 5, # Error PYI014
) -> None: ...
def f21(
x=3j - 3j, # Error PYI014
) -> None: ...
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...

View File

@@ -0,0 +1,14 @@
"""foo""" # OK, not in stub
def foo():
"""foo""" # OK, doc strings are allowed in non-stubs
class Bar:
"""bar""" # OK, doc strings are allowed in non-stubs
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -0,0 +1,11 @@
"""foo""" # ERROR PYI021
def foo():
"""foo""" # ERROR PYI021
class Bar:
"""bar""" # ERROR PYI021
def bar():
x = 1
"""foo""" # OK, not a doc string

View File

@@ -17,6 +17,12 @@ class Test(unittest.TestCase):
self.assertTrue(**{"expr": expr, "msg": msg}) # Error, unfixable
self.assertTrue(msg=msg, expr=expr, unexpected_arg=False) # Error, unfixable
self.assertTrue(msg=msg) # Error, unfixable
(
self.assertIsNotNone(value) # Error, unfixable
if expect_condition
else self.assertIsNone(value) # Error, unfixable
)
return self.assertEqual(True, False) # Error, unfixable
def test_assert_false(self):
self.assertFalse(True) # Error

View File

@@ -6,6 +6,8 @@ def test_ok():
assert something or something_else
assert something or something_else and something_third
assert not (something and something_else)
assert something, "something message"
assert something or something_else and something_third, "another message"
def test_error():
@@ -13,6 +15,27 @@ def test_error():
assert something and something_else and something_third
assert something and not something_else
assert something and (something_else or something_third)
assert not something and something_else
assert not (something or something_else)
assert not (something or something_else or something_third)
assert something and something_else == """error
message
"""
# recursive case
assert not (a or not (b or c))
assert not (a or not (b and c))
# detected, but no autofix for messages
assert something and something_else, "error message"
assert not (something or something_else and something_third), "with message"
# detected, but no autofix for mixed conditions (e.g. `a or b and c`)
assert not (something or something_else and something_third)
# detected, but no autofix for parenthesized conditions
assert (
something
and something_else
== """error
message
"""
)

View File

@@ -3,6 +3,8 @@ import os
import posix
from posix import abort
import sys as std_sys
import typing
import typing_extensions
import _thread
import _winapi
@@ -77,7 +79,7 @@ def x(y):
# last line in while loop
def x(y):
while True:
while i > 0:
if y > 0:
return 1
y += 1
@@ -211,6 +213,18 @@ def noreturn_sys_exit():
std_sys.exit(0)
def noreturn_typing_assert_never():
if x > 0:
return 1
typing.assert_never(0)
def noreturn_typing_extensions_assert_never():
if x > 0:
return 1
typing_extensions.assert_never(0)
def noreturn__thread_exit():
if x > 0:
return 1
@@ -251,3 +265,31 @@ def noreturn_pytest_xfail_2():
if x > 0:
return 1
py_xfail("oof")
def nested(values):
if not values:
return False
for value in values:
print(value)
def while_true():
while True:
if y > 0:
return 1
y += 1
# match
def x(y):
match y:
case 0:
return 1
case 1:
print() # error
def foo(baz: str) -> str:
return baz

View File

@@ -56,12 +56,6 @@ class Foo(metaclass=BazMeta):
foo = Foo()
print(foo.public_thing)
print(foo.public_func())
print(foo.__dict__)
print(foo.__str__())
print(foo().__class__)
print(foo._private_thing) # SLF001
print(foo.__really_private_thing) # SLF001
print(foo._private_func()) # SLF001
@@ -69,3 +63,10 @@ print(foo.__really_private_func(1)) # SLF001
print(foo.bar._private) # SLF001
print(foo()._private_thing) # SLF001
print(foo()._private_thing__) # SLF001
print(foo.public_thing)
print(foo.public_func())
print(foo.__dict__)
print(foo.__str__())
print(foo().__class__)
print(foo._asdict())

View File

@@ -0,0 +1,86 @@
# Errors
a = "hello"
# SIM116
if a == "foo":
return "bar"
elif a == "bar":
return "baz"
elif a == "boo":
return "ooh"
else:
return 42
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
# SIM116
if a == "hello 'sir'":
return (1, 2, 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == b"one":
return 1
elif a == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if a == "hello 'sir'":
return ("hello'", 'hi"', 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# OK
if a == "foo":
return "bar"
elif a == "bar":
return baz()
elif a == "boo":
return "ooh"
else:
return 42
# OK
if a == b"one":
return 1
elif b == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if func_name == "create":
return "A"
elif func_name == "modify":
return "M"
elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"

View File

@@ -11,6 +11,7 @@ YODA > age # SIM300
YODA >= age # SIM300
JediOrder.YODA == age # SIM300
0 < (number - 100) # SIM300
SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60) # SIM300
# OK
compare == "yoda"

View File

@@ -4,4 +4,5 @@ import attrs
from ..protocol import commands, definitions, responses
from ..server import example
from .. import server
from . import logger, models

View File

@@ -0,0 +1,15 @@
from foo import ( # Comment on `foo`
Member as Alias, # Comment on `Alias`
)
from bar import ( # Comment on `bar`
Member, # Comment on `Member`
)
from baz import ( # Comment on `baz`
Member as Alias # Comment on `Alias`
)
from bop import ( # Comment on `bop`
Member # Comment on `Member`
)

View File

@@ -0,0 +1,23 @@
import lib6
import lib2
import lib5
import lib1
import lib3
import lib4
import foo
import z
from foo import bar
from lib1 import foo
from lib2 import foo
from lib1.lib2 import foo
from foo.lib1.bar import baz
from lib4 import lib1
from lib5 import lib2
from lib4 import lib2
from lib5 import lib1
import lib3.lib4
import lib3.lib4.lib5
from lib3.lib4 import foo
from lib3.lib4.lib5 import foo

View File

@@ -1,2 +1,2 @@
from long_module_name import member_one, member_two, member_three, member_four, member_five
from long_module_name import member_one, member_two, member_three, member_four, member_five

View File

@@ -0,0 +1,3 @@
from __future__ import annotations
from typing import Any
from . import my_local_folder_object

View File

@@ -5,3 +5,4 @@ line-length = 88
lines-after-imports = 3
lines-between-types = 2
known-local-folder = ["ruff"]
force-to-top = ["lib1", "lib3", "lib5", "lib3.lib4", "z"]

View File

@@ -0,0 +1,4 @@
"""a
b"""
# b
import os

View File

@@ -0,0 +1,62 @@
# Do this (new version)
from numpy.random import default_rng
rng = default_rng()
vals = rng.standard_normal(10)
more_vals = rng.standard_normal(10)
numbers = rng.integers(high, size=5)
# instead of this (legacy version)
from numpy import random
vals = random.standard_normal(10)
more_vals = random.standard_normal(10)
numbers = random.integers(high, size=5)
import numpy
numpy.random.seed()
numpy.random.get_state()
numpy.random.set_state()
numpy.random.rand()
numpy.random.randn()
numpy.random.randint()
numpy.random.random_integers()
numpy.random.random_sample()
numpy.random.choice()
numpy.random.bytes()
numpy.random.shuffle()
numpy.random.permutation()
numpy.random.beta()
numpy.random.binomial()
numpy.random.chisquare()
numpy.random.dirichlet()
numpy.random.exponential()
numpy.random.f()
numpy.random.gamma()
numpy.random.geometric()
numpy.random.get_state()
numpy.random.gumbel()
numpy.random.hypergeometric()
numpy.random.laplace()
numpy.random.logistic()
numpy.random.lognormal()
numpy.random.logseries()
numpy.random.multinomial()
numpy.random.multivariate_normal()
numpy.random.negative_binomial()
numpy.random.noncentral_chisquare()
numpy.random.noncentral_f()
numpy.random.normal()
numpy.random.pareto()
numpy.random.poisson()
numpy.random.power()
numpy.random.rayleigh()
numpy.random.standard_cauchy()
numpy.random.standard_exponential()
numpy.random.standard_gamma()
numpy.random.standard_normal()
numpy.random.standard_t()
numpy.random.triangular()
numpy.random.uniform()
numpy.random.vonmises()
numpy.random.wald()
numpy.random.weibull()
numpy.random.zipf()

View File

@@ -0,0 +1,50 @@
#: E251 E251
def foo(bar = False):
'''Test function with an error in declaration'''
pass
#: E251
foo(bar= True)
#: E251
foo(bar =True)
#: E251 E251
foo(bar = True)
#: E251
y = bar(root= "sdasd")
#: E251:2:29
parser.add_argument('--long-option',
default=
"/rather/long/filesystem/path/here/blah/blah/blah")
#: E251:1:45
parser.add_argument('--long-option', default
="/rather/long/filesystem/path/here/blah/blah/blah")
#: E251:3:8 E251:3:10
foo(True,
baz=(1, 2),
biz = 'foo'
)
#: Okay
foo(bar=(1 == 1))
foo(bar=(1 != 1))
foo(bar=(1 >= 1))
foo(bar=(1 <= 1))
(options, args) = parser.parse_args()
d[type(None)] = _deepcopy_atomic
# Annotated Function Definitions
#: Okay
def munge(input: AnyStr, sep: AnyStr = None, limit=1000,
extra: Union[str, dict] = None) -> AnyStr:
pass
#: Okay
async def add(a: int = 0, b: int = 0) -> int:
return a + b
# Previously E251 four times
#: E271:1:6
async def add(a: int = 0, b: int = 0) -> int:
return a + b
#: E252:1:15 E252:1:16 E252:1:27 E252:1:36
def add(a: int=0, b: int =0, c: int= 0) -> int:
return a + b + c
#: Okay
def add(a: int = _default(name='f')):
return a

View File

@@ -54,3 +54,9 @@ def f(): ...
class C: ...; x = 1
#: E701:1:8 E702:1:13
class C: ...; ...
#: E701:2:12
match *0, 1, *2:
case 0,: y = 0
#:
class Foo:
match: Optional[Match] = None

View File

@@ -9,6 +9,9 @@ while False:
f = lambda: (yield 1)
#: E731
f = lambda: (yield from g())
#: E731
class F:
f = lambda x: 2 * x
f = object()
f.method = lambda: "Method"

View File

@@ -0,0 +1,145 @@
#: W191
if False:
print # indented with 1 tab
#:
#: W191
y = x == 2 \
or x == 3
#: E101 W191 W504
if (
x == (
3
) or
y == 4):
pass
#: E101 W191
if x == 2 \
or y > 1 \
or x == 3:
pass
#: E101 W191
if x == 2 \
or y > 1 \
or x == 3:
pass
#:
#: E101 W191 W504
if (foo == bar and
baz == bop):
pass
#: E101 W191 W504
if (
foo == bar and
baz == bop
):
pass
#:
#: E101 E101 W191 W191
if start[1] > end_col and not (
over_indent == 4 and indent_next):
return (0, "E121 continuation line over-"
"indented for visual indent")
#:
#: E101 W191
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
#: E101 W191 W504
if ((row < 0 or self.moduleCount <= row or
col < 0 or self.moduleCount <= col)):
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
#: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
if bar:
return (
start, 'E121 lines starting with a '
'closing bracket should be indented '
"to match that of the opening "
"bracket's line"
)
#
#: E101 W191 W504
# you want vertical alignment, so use a parens
if ((foo.bar("baz") and
foo.bar("bop")
)):
print "yes"
#: E101 W191 W504
# also ok, but starting to look like LISP
if ((foo.bar("baz") and
foo.bar("bop"))):
print "yes"
#: E101 W191 W504
if (a == 2 or
b == "abc def ghi"
"jkl mno"):
return True
#: E101 W191 W504
if (a == 2 or
b == """abc def ghi
jkl mno"""):
return True
#: W191:2:1 W191:3:1 E101:3:2
if length > options.max_line_length:
return options.max_line_length, \
"E501 line too long (%d characters)" % length
#
#: E101 W191 W191 W504
if os.path.exists(os.path.join(path, PEP8_BIN)):
cmd = ([os.path.join(path, PEP8_BIN)] +
self._pep8_options(targetfile))
#: W191
'''
multiline string with tab in it'''
#: E101 W191
'''multiline string
with tabs
and spaces
'''
#: Okay
'''sometimes, you just need to go nuts in a multiline string
and allow all sorts of crap
like mixed tabs and spaces
or trailing whitespace
or long long long long long long long long long long long long long long long long long lines
''' # nopep8
#: Okay
'''this one
will get no warning
even though the noqa comment is not immediately after the string
''' + foo # noqa
#
#: E101 W191
if foo is None and bar is "bop" and \
blah == 'yeah':
blah = 'yeahnah'
#
#: W191 W191 W191
if True:
foo(
1,
2)
#: W191 W191 W191 W191 W191
def test_keys(self):
"""areas.json - All regions are accounted for."""
expected = set([
u'Norrbotten',
u'V\xe4sterbotten',
])
#: W191
x = [
'abc'
]
#:

View File

@@ -0,0 +1,26 @@
#: Okay
# 情
#: W291:1:6
print
#: W293:2:1
class Foo(object):
bang = 12
#: W291:2:35
'''multiline
string with trailing whitespace'''
#: W291 W292 noeol
x = 1
#: W191 W292 noeol
if False:
pass # indented with tabs
#: W292:1:36 noeol
# This line doesn't have a linefeed
#: W292:1:5 E225:1:2 noeol
1+ 1
#: W292:1:27 E261:1:12 noeol
import this # no line feed
#: W292:3:22 noeol
class Test(object):
def __repr__(self):
return 'test'

View File

@@ -1,35 +1,35 @@
#: W605:1:10
regex = '\.png$'
#: W605:2:1
regex = '''
\.png$
'''
#: W605:2:6
f(
'\_'
)
#: W605:4:6
"""
multi-line
literal
with \_ somewhere
in the middle
"""
#: Okay
regex = r'\.png$'
regex = '\\.png$'
regex = r'''
\.png$
'''
regex = r'''
\\.png$
'''
s = '\\'
regex = '\w' # noqa
regex = '''
\w
''' # noqa
#: W605:1:10
regex = '\.png$'
#: W605:2:1
regex = '''
\.png$
'''
#: W605:2:6
f(
'\_'
)
#: W605:4:6
"""
multi-line
literal
with \_ somewhere
in the middle
"""
#: Okay
regex = r'\.png$'
regex = '\\.png$'
regex = r'''
\.png$
'''
regex = r'''
\\.png$
'''
s = '\\'
regex = '\w' # noqa
regex = '''
\w
''' # noqa

View File

@@ -16,21 +16,17 @@ if False == None: # E711, E712 (fix)
if None == False: # E711, E712 (fix)
pass
###
# Unfixable errors
###
if "abc" == None: # E711
pass
if None == "abc": # E711
pass
if "abc" == False: # E712
pass
if False == "abc": # E712
pass
###
# Non-errors
###
if "abc" == None:
pass
if None == "abc":
pass
if "abc" == False:
pass
if False == "abc":
pass
if "def" == "abc":
pass
if False is None:

View File

@@ -85,3 +85,10 @@ else:
CustomInt: TypeAlias = "np.int8 | np.int16"
# Test: match statements.
match *0, 1, *2:
case 0,:
import x
import y

View File

@@ -0,0 +1,10 @@
"""Test: imports within `ModuleNotFoundError` handlers."""
def check_orjson():
try:
import orjson
return True
except ModuleNotFoundError:
return False

View File

@@ -4,3 +4,5 @@ from __future__ import absolute_import
from collections import namedtuple
from __future__ import print_function
import __future__

View File

@@ -33,3 +33,11 @@ f"{f'{v:0.2f}'}"
# Errors
f"{v:{f'0.2f'}}"
f"{f''}"
f"{{test}}"
f'{{ 40 }}'
f"{{a {{x}}"
f"{{{{x}}}}"
# To be fixed
# Error: f-string: single '}' is not allowed at line 41 column 8
# f"\{{x}}"

View File

@@ -1,7 +1,7 @@
"""
Test that shadowing a global with a class attribute does not produce a
warning.
"""
Test that shadowing a global with a class attribute does not produce a
warning.
"""
import fu

View File

@@ -0,0 +1,4 @@
try:
pass
except ExceptionGroup:
pass

View File

@@ -0,0 +1,46 @@
"""Test: match statements."""
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
def f():
match Car("Toyota", "Corolla"):
case Car("Toyota", model):
print(model)
case Car(make, "Corolla"):
print(make)
def f(provided: int) -> int:
match provided:
case True:
return captured # F821
def f(provided: int) -> int:
match provided:
case captured:
return captured
def f(provided: int) -> int:
match provided:
case [captured, *_]:
return captured
def f(provided: int) -> int:
match provided:
case [*captured]:
return captured
def f(provided: int) -> int:
match provided:
case {**captured}:
return captured

View File

@@ -71,7 +71,7 @@ def f():
def connect():
return None, None
with (connect() as (connection, cursor)):
with connect() as (connection, cursor):
cursor.execute("SELECT * FROM users")
@@ -94,3 +94,30 @@ def f():
(exponential := (exponential * base_multiplier) % 3): i + 1 for i in range(2)
}
return hash_map
def f(x: int):
msg1 = "Hello, world!"
msg2 = "Hello, world!"
msg3 = "Hello, world!"
match x:
case 1:
print(msg1)
case 2:
print(msg2)
def f(x: int):
import enum
Foo = enum.Enum("Foo", "A B")
Bar = enum.Enum("Bar", "A B")
Baz = enum.Enum("Baz", "A B")
match x:
case (Foo.A):
print("A")
case [Bar.A, *_]:
print("A")
case y:
pass

View File

@@ -0,0 +1,49 @@
"""
Test for else-if-used
"""
def ok0():
"""Should not trigger on elif"""
if 1:
pass
elif 2:
pass
def ok1():
"""If the orelse has more than 1 item in it, shouldn't trigger"""
if 1:
pass
else:
print()
if 1:
pass
def ok2():
"""If the orelse has more than 1 item in it, shouldn't trigger"""
if 1:
pass
else:
if 1:
pass
print()
def not_ok0():
if 1:
pass
else:
if 2:
pass
def not_ok1():
if 1:
pass
else:
if 2:
pass
else:
pass

View File

@@ -0,0 +1,75 @@
# Adapted from:
# https://github.com/PyCQA/pylint/blob/b70d2abd7fabe9bfd735a30b593b9cd5eaa36194/tests/functional/g/globals.py
CONSTANT = 1
def FUNC():
pass
class CLASS:
pass
def fix_constant(value):
"""All this is ok, but try not to use `global` ;)"""
global CONSTANT # [global-statement]
print(CONSTANT)
CONSTANT = value
def global_with_import():
"""Should only warn for global-statement when using `Import` node"""
global sys # [global-statement]
import sys
def global_with_import_from():
"""Should only warn for global-statement when using `ImportFrom` node"""
global namedtuple # [global-statement]
from collections import namedtuple
def global_del():
"""Deleting the global name prevents `global-variable-not-assigned`"""
global CONSTANT # [global-statement]
print(CONSTANT)
del CONSTANT
def global_operator_assign():
"""Operator assigns should only throw a global statement error"""
global CONSTANT # [global-statement]
print(CONSTANT)
CONSTANT += 1
def global_function_assign():
"""Function assigns should only throw a global statement error"""
global CONSTANT # [global-statement]
def CONSTANT():
pass
CONSTANT()
def override_func():
"""Overriding a function should only throw a global statement error"""
global FUNC # [global-statement]
def FUNC():
pass
FUNC()
def override_class():
"""Overriding a class should only throw a global statement error"""
global CLASS # [global-statement]
class CLASS:
pass
CLASS()

View File

@@ -0,0 +1,24 @@
import logging
logging.warning("Hello %s %s", "World!") # [logging-too-few-args]
# do not handle calls with kwargs (like pylint)
logging.warning("Hello %s", "World!", "again", something="else")
logging.warning("Hello %s", "World!")
# do not handle calls without any args
logging.info("100% dynamic")
# do not handle calls with *args
logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
import warning
warning.warning("Hello %s %s", "World!")

View File

@@ -0,0 +1,20 @@
import logging
logging.warning("Hello %s", "World!", "again") # [logging-too-many-args]
logging.warning("Hello %s", "World!", "again", something="else")
logging.warning("Hello %s", "World!")
# do not handle calls with *args
logging.error("Example log %s, %s", "foo", "bar", "baz", *args)
# do not handle calls with **kwargs
logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
import warning
warning.warning("Hello %s", "World!", "again")

View File

@@ -0,0 +1,155 @@
# For -> for, variable reused
for i in []:
for i in []: # error
pass
# With -> for, variable reused
with None as i:
for i in []: # error
pass
# For -> with, variable reused
for i in []:
with None as i: # error
pass
# With -> with, variable reused
with None as i:
with None as i: # error
pass
# For -> for, different variable
for i in []:
for j in []: # ok
pass
# With -> with, different variable
with None as i:
with None as j: # ok
pass
# For -> for -> for, doubly nested variable reuse
for i in []:
for j in []:
for i in []: # error
pass
# For -> for -> for -> for, doubly nested variable reuse x2
for i in []:
for j in []:
for i in []: # error
for j in []: # error
pass
# For -> assignment
for i in []:
i = 5 # error
# For -> augmented assignment
for i in []:
i += 5 # error
# For -> annotated assignment
for i in []:
i: int = 5 # error
# Async for -> for, variable reused
async for i in []:
for i in []: # error
pass
# For -> async for, variable reused
for i in []:
async for i in []: # error
pass
# For -> for, outer loop unpacks tuple
for i, j in enumerate([]):
for i in []: # error
pass
# For -> for, inner loop unpacks tuple
for i in []:
for i, j in enumerate([]): # error
pass
# For -> for, both loops unpack tuple
for (i, (j, k)) in []:
for i, j in enumerate([]): # two errors
pass
# For else -> for, variable reused in else
for i in []:
pass
else:
for i in []: # no error
pass
# For -> for, ignore dummy variables
for _ in []:
for _ in []: # no error
pass
# For -> for, outer loop unpacks with asterisk
for i, *j in []:
for j in []: # error
pass
# For -> function definition
for i in []:
def f():
i = 2 # no error
# For -> class definition
for i in []:
class A:
i = 2 # no error
# For -> function definition -> for -> assignment
for i in []:
def f():
for i in []: # no error
i = 2 # error
# For -> class definition -> for -> for
for i in []:
class A:
for i in []: # no error
for i in []: # error
pass
# For -> use in assignment target without actual assignment; subscript
for i in []:
a[i] = 2 # no error
i[a] = 2 # no error
# For -> use in assignment target without actual assignment; attribute
for i in []:
a.i = 2 # no error
i.a = 2 # no error
# For target with subscript -> assignment
for a[0] in []:
a[0] = 2 # error
a[1] = 2 # no error
# For target with subscript -> assignment
for a['i'] in []:
a['i'] = 2 # error
a['j'] = 2 # no error
# For target with attribute -> assignment
for a.i in []:
a.i = 2 # error
a.j = 2 # no error
# For target with double nested attribute -> assignment
for a.i.j in []:
a.i.j = 2 # error
a.j.i = 2 # no error
# For target with attribute -> assignment with different spacing
for a.i in []:
a. i = 2 # error
for a. i in []:
a.i = 2 # error

View File

@@ -0,0 +1,41 @@
def a():
return
def __init__():
return
class A:
def __init__(self):
return
class B:
def __init__(self):
return 3
def gen(self):
return 5
class MyClass:
def __init__(self):
return 1
class MyClass2:
"""dummy class"""
def __init__(self):
return
class MyClass3:
"""dummy class"""
def __init__(self):
return None
class MyClass5:
"""dummy class"""
def __init__(self):
self.callable = lambda: (yield None)

View File

@@ -113,3 +113,25 @@ def test_break_in_if_orelse():
else:
return True
return False
def test_break_in_with():
"""no false positive for break in with"""
for name in ["demo"]:
with open(__file__) as f:
if name in f.read():
break
else:
return True
return False
def test_break_in_match():
"""no false positive for break in match"""
for name in ["demo"]:
match name:
case "demo":
break
else:
return True
return False

View File

@@ -28,3 +28,10 @@ def f(x: IList[str]) -> None:
def f(x: "List[str]") -> None:
...
list = "abc"
def f(x: List[str]) -> None:
...

View File

@@ -41,3 +41,8 @@ def f(x: Union["str", int]) -> None:
def f(x: Union[("str", "int"), float]) -> None:
...
def f() -> None:
x: Optional[str]
x = Optional[str]

View File

@@ -2,36 +2,42 @@ from typing import TypedDict, NotRequired, Literal
import typing
# dict literal
MyType1 = TypedDict("MyType1", {"a": int, "b": str})
MyType = TypedDict("MyType", {"a": int, "b": str})
# dict call
MyType2 = TypedDict("MyType2", dict(a=int, b=str))
MyType = TypedDict("MyType", dict(a=int, b=str))
# kwargs
MyType3 = TypedDict("MyType3", a=int, b=str)
MyType = TypedDict("MyType", a=int, b=str)
# Empty TypedDict
MyType4 = TypedDict("MyType4")
MyType = TypedDict("MyType")
# Literal values
MyType5 = TypedDict("MyType5", {"a": "hello"})
MyType6 = TypedDict("MyType6", a="hello")
MyType = TypedDict("MyType", {"a": "hello"})
MyType = TypedDict("MyType", a="hello")
# NotRequired
MyType7 = TypedDict("MyType7", {"a": NotRequired[dict]})
MyType = TypedDict("MyType", {"a": NotRequired[dict]})
# total
MyType8 = TypedDict("MyType8", {"x": int, "y": int}, total=False)
# invalid identifiers
MyType9 = TypedDict("MyType9", {"in": int, "x-y": int})
MyType = TypedDict("MyType", {"x": int, "y": int}, total=False)
# using Literal type
MyType10 = TypedDict("MyType10", {"key": Literal["value"]})
MyType = TypedDict("MyType", {"key": Literal["value"]})
# using namespace TypedDict
MyType11 = typing.TypedDict("MyType11", {"key": int})
MyType = typing.TypedDict("MyType", {"key": int})
# unpacking
# invalid identifiers (OK)
MyType = TypedDict("MyType", {"in": int, "x-y": int})
# unpacking (OK)
c = {"c": float}
MyType12 = TypedDict("MyType1", {"a": int, "b": str, **c})
MyType = TypedDict("MyType", {"a": int, "b": str, **c})
# Empty dict literal
MyType = TypedDict("MyType", {})
# Empty dict call
MyType = TypedDict("MyType", dict())

View File

@@ -2,21 +2,30 @@ from typing import NamedTuple
import typing
# with complex annotations
NT1 = NamedTuple("NT1", [("a", int), ("b", tuple[str, ...])])
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
# with default values as list
NT2 = NamedTuple(
"NT2",
MyType = NamedTuple(
"MyType",
[("a", int), ("b", str), ("c", list[bool])],
defaults=["foo", [True]],
)
# with namespace
NT3 = typing.NamedTuple("NT3", [("a", int), ("b", str)])
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
# with too many default values
NT4 = NamedTuple(
"NT4",
# too many default values (OK)
MyType = NamedTuple(
"MyType",
[("a", int), ("b", str)],
defaults=[1, "bar", "baz"],
)
# invalid identifiers (OK)
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
# no fields
MyType = typing.NamedTuple("MyType")
# empty fields
MyType = typing.NamedTuple("MyType", [])

View File

@@ -0,0 +1,7 @@
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

View File

@@ -1,15 +0,0 @@
def f(*args, **kwargs):
pass
a = (1, 2)
b = (3, 4)
c = (5, 6)
d = (7, 8)
f(a, b)
f(a, kw=b)
f(*a, kw=b)
f(kw=a, *b)
f(kw=a, *b, *c)
f(*a, kw=b, *c, kw1=d)

View File

@@ -6,6 +6,11 @@ def f():
asyncio.create_task(coordinator.ws_connect()) # Error
# Error
def f():
asyncio.ensure_future(coordinator.ws_connect()) # Error
# OK
def f():
background_tasks = set()
@@ -22,6 +27,22 @@ def f():
task.add_done_callback(background_tasks.discard)
# OK
def f():
background_tasks = set()
for i in range(10):
task = asyncio.ensure_future(some_coro(param=i))
# Add task to the set. This creates a strong reference.
background_tasks.add(task)
# To prevent keeping references to finished tasks forever,
# make each task remove its own reference from the set after
# completion:
task.add_done_callback(background_tasks.discard)
# OK
def f():
ctx.task = asyncio.create_task(make_request())

View File

@@ -0,0 +1,8 @@
# ruff: noqa: F401
import os
import foo
def f():
x = 1

View File

@@ -30,3 +30,11 @@ def good():
raise e # This is verbose violation, shouldn't trigger no cause
except Exception:
raise # Just re-raising don't need 'from'
def good():
try:
from mod import f
except ImportError:
def f():
raise MyException() # Raising within a new scope is fine

View File

@@ -20,6 +20,24 @@ def bad():
logger.exception("something failed")
def bad():
try:
a = process()
if not a:
raise MyException(a)
raise MyException(a)
try:
b = process()
if not b:
raise MyException(b)
except* Exception:
logger.exception("something failed")
except* Exception:
logger.exception("something failed")
def good():
try:
a = process() # This throws the exception now

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