Compare commits

...

248 Commits

Author SHA1 Message Date
Tom Kuson
7cc205b5d6 Change iteration-over-set to flag set literals only (#4907) 2023-06-06 21:06:46 +00:00
Charlie Marsh
e1df2b1400 Set default Rust version when testing sdist (#4908) 2023-06-06 16:59:04 -04:00
Charlie Marsh
2a6d7cd71c Avoid no-op fix for nested with expressions (#4906) 2023-06-06 20:15:21 +00:00
Charlie Marsh
2b5fb70482 Bump version to 0.0.271 (#4890) 2023-06-06 15:11:48 -04:00
Charlie Marsh
8c048b463c Track symbol deletions separately from bindings (#4888) 2023-06-06 18:49:36 +00:00
Micha Reiser
19abee086b Introduce AnyFunctionDefinition Node (#4898) 2023-06-06 20:37:46 +02:00
Addison Crump
1ed5d7e437 mark f522 as sometimes fixable (#4893) 2023-06-06 09:14:23 -04:00
Micha Reiser
3f032cf09d Format binary expressions (#4862)
* Format Binary Expressions

* Extract NeedsParentheses trait
2023-06-06 08:34:53 +00:00
konstin
775326790e Fix pre-commit typos action (#4876)
The typos pre-commit action would also edit test fixtures and snapshots. Unfortunately the pre-commit action also doesn't respect _typos.toml and typos action doesn't allow for an exclude key, so i've added a top level exclude key. I have confirmed that this does stop typos from rewriting my fixtures and snapshots
2023-06-06 08:06:48 +02:00
Charlie Marsh
7b0fb1a3b4 Respect noqa directives on ImportFrom parents for type-checking rules (#4889) 2023-06-06 02:37:07 +00:00
Charlie Marsh
c2a3e97b7f Avoid early-exit in explicit-f-string-type-conversion (#4886) 2023-06-06 00:52:11 +00:00
Charlie Marsh
805b2eb0b7 Respect shadowed exports in __all__ (#4885) 2023-06-05 20:48:53 -04:00
Charlie Marsh
0c7ea800af Remove destructive fixes for F523 (#4883) 2023-06-06 00:44:30 +00:00
Charlie Marsh
c67029ded9 Move duplicate-value rule to flake8-bugbear (#4882) 2023-06-05 21:43:47 +00:00
Charlie Marsh
a70afa7de7 Remove ToString prefixes (#4881) 2023-06-05 21:11:19 +00:00
Charlie Marsh
d1b8fe6af2 Fix round-tripping of nested functions (#4875) 2023-06-05 16:13:08 -04:00
Micha Reiser
913b9d1fcf Normalize newlines in verbatim_text (#4850) 2023-06-05 19:30:28 +00:00
Justin Prieto
f9e82f2578 [flake8-pyi] Implement PYI029 (#4851) 2023-06-05 19:21:16 +00:00
Charlie Marsh
79ae1840af Remove unused lifetime from UnusedImport type alias (#4874) 2023-06-05 19:09:27 +00:00
Charlie Marsh
8938b2d555 Use qualified_name terminology in more structs for consistency (#4873) 2023-06-05 19:06:48 +00:00
Micha Reiser
33434fcb9c Add Formatter benchmark (#4860) 2023-06-05 21:05:42 +02:00
Charlie Marsh
8a3a269eef Avoid index-out-of-bands panic for positional placeholders (#4872) 2023-06-05 18:31:47 +00:00
Charlie Marsh
d31eb87877 Extract shared simple AST node inference utility (#4871) 2023-06-05 18:23:37 +00:00
Ryan Yang
72245960a1 implement E307 for pylint invalid str return type (#4854) 2023-06-05 17:54:15 +00:00
Charlie Marsh
e6b00f0c4e Avoid running RUF100 rules when code contains syntax errors (#4869) 2023-06-05 17:32:06 +00:00
Charlie Marsh
f952bef1ad Mark F523 as "sometimes" fixable (#4868) 2023-06-05 16:55:28 +00:00
Allison Karlitskaya
dc223fd3ca Add some exceptions for FBT003 (#3247) (#4867) 2023-06-05 16:44:49 +00:00
konstin
209aaa5add Ensure type_ignores for Module are empty (#4861)
According to https://docs.python.org/3/library/ast.html#ast-helpers, we expect type_ignores to be always be empty, so this adds a debug assert.

Test plan: I confirmed that the assertion holdes for the file below and for all the black tests which include a number of `type: ignore` comments.
```python
# type: ignore

if 1:
    print("1")  # type: ignore
    # elsebranch

# type: ignore

else:  # type: ignore
    print("2")  # type: ignore

while 1:
    print()

# type: ignore
```
2023-06-05 11:38:08 +02:00
konstin
ff37d7af23 Implement module formatting using JoinNodesBuilder (#4808)
* Implement module formatting using JoinNodesBuilder

This uses JoinNodesBuilder to implement module formatting for #4800

See the snapshots for the changed behaviour. See one PR up for a CLI that i used to verify the trailing new line behaviour
2023-06-05 08:35:05 +00:00
Micha Reiser
c65f47d7c4 Format while Statement (#4810) 2023-06-05 08:24:00 +00:00
konstin
d1d06960f0 Add a formatter CLI for debugging (#4809)
* Add a formatter CLI for debugging

This adds a ruff_python_formatter cli modelled aber `rustfmt` that i use for debugging

* clippy

* Add print IR and print comments options

Tested with `cargo run --bin ruff_python_formatter -- --print-ir --print-comments scratch.py`
2023-06-05 07:33:33 +00:00
konstin
576e0c7b80 Abstract stylist to libcst style conversion (#4749)
* Abstract codegen with stylist into a CodegenStylist trait

Replace all duplicate invocations of

```rust
let mut state = CodegenState {
    default_newline: &stylist.line_ending(),
    default_indent: stylist.indentation(),
    ..CodegenState::default()
}
tree.codegen(&mut state);
state.to_string()
```

with

```rust
tree.codegen_stylist(&stylist);
```

No functional changes.
2023-06-05 07:22:43 +00:00
Charlie Marsh
1fba98681e Remove codes import from rule_selector.rs (#4856) 2023-06-05 02:31:30 +00:00
Evan Rittenhouse
95e61987d1 Change fixable_set to include RuleSelector::All/Nursery (#4852) 2023-06-04 22:25:00 -04:00
Charlie Marsh
a0721912a4 Invert structure of Scope#shadowed_bindings (#4855) 2023-06-05 02:03:21 +00:00
Micha Reiser
694bf7f5b8 Upgrade to Rust 1.70 (#4848) 2023-06-04 17:51:47 +00:00
Charlie Marsh
466719247b Invert parent-shadowed bindings map (#4847) 2023-06-04 00:18:46 -04:00
Charlie Marsh
3fa4440d87 Modify semantic model API to push bindings upon creation (#4846) 2023-06-04 02:28:25 +00:00
Zanie Adkins
14e06f9f8b Rename ruff_formatter::builders::BestFitting to FormatBestFitting (#4841) 2023-06-04 00:13:51 +02:00
Zanie Adkins
e7a2e0f437 Remove unused mutable variables (#4839) 2023-06-03 17:31:06 -04:00
Evan Rittenhouse
67b43ab72a Make FLY002 autofix into a constant string instead of an f-string if all join() arguments are strings (#4834) 2023-06-03 20:35:06 +00:00
Zanie Adkins
5ae4667fd5 Upgrade criterion to 0.5.1 (#4838) 2023-06-03 21:33:44 +01:00
Charlie Marsh
d8a6109b69 Fix min-index offset rewrites in F523 (#4837) 2023-06-03 20:11:48 +00:00
Charlie Marsh
fcacd3cd95 Preserve quotes in F523 fixer (#4836) 2023-06-03 19:53:57 +00:00
Charlie Marsh
42c071d302 Respect mixed variable assignment in RET504 (#4835) 2023-06-03 15:39:11 -04:00
Charlie Marsh
c14896b42c Move Binding initialization into SemanticModel (#4819) 2023-06-03 15:26:55 -04:00
Charlie Marsh
935094c2ff Move import-name matching into methods on BindingKind (#4818) 2023-06-03 15:01:27 -04:00
Micha Reiser
2c41c54e0c Format ExprName (#4803) 2023-06-03 16:06:14 +02:00
Micha Reiser
d6daa61563 Handle trailing end-of-line comments in-between-bodies (#4812)
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

And more custom logic around comments in bodies... uff. 

Let's say we have the following code

```python
if x == y:
    pass # trailing comment of pass
else: # trailing comment of `else`
    print("I have no comments")
```

Right now, the formatter attaches the `# trailing comment of `else` as a trailing comment of `pass` because it doesn't "see" that there's an `else` keyword in between (because the else body is just a Vec and not a node). 

This PR adds custom logic that attaches the trailing comments after the `else` as dangling comments to the `if` statement. The if statement must then split the dangling comments by `comments.text_position()`:
* All comments up to the first end-of-line comment are leading comments of the `else` keyword.
* All end-of-line comments coming after are `trailing` comments for the `else` keyword.


## Test Plan

I added new unit tests.
2023-06-03 15:29:22 +02:00
Micha Reiser
cb6788ab5f Handle trailing body end-of-line comments (#4811)
### Summary

This PR adds custom logic to handle end-of-line comments of the last statement in a body. 

For example: 

```python
while True:
    if something.changed:
        do.stuff()  # trailing comment

b
```

The `# trailing comment` is a trailing comment of the `do.stuff()` expression statement. We incorrectly attached the comment as a trailing comment of the enclosing `while` statement  because the comment is between the end of the while statement (the `while` statement ends right after `do.stuff()`) and before the `b` statement. 


This PR fixes the placement to correctly attach these comments to the last statement in a body (recursively). 

## Test Plan

I reviewed the snapshots and they now look correct. This may appear odd because a lot comments have now disappeared. This is the expected result because we use `verbatim` formatting for the block statements (like `while`) and that means that it only formats the inner content of the block, but not any trailing comments. The comments were visible before, because they were associated with the block statement (e.g. `while`).
2023-06-03 15:17:33 +02:00
Justin Prieto
e82160a83a [flake8-pyi] Implement PYI035 (#4820) 2023-06-03 03:13:04 +00:00
Charlie Marsh
26b1dd0ca2 Remove name field from import binding kinds (#4817) 2023-06-02 23:02:47 -04:00
Charlie Marsh
fcfd6ad129 Rename outlier Pathlib rule (#4816) 2023-06-02 18:42:17 +00:00
Charlie Marsh
6a0cebdf7b Remove regex from partial-path rule (#4815) 2023-06-02 18:28:55 +00:00
Ville Skyttä
0a5dfcb26a Implement S609, linux_commands_wildcard_injection (#4504) 2023-06-02 18:19:02 +00:00
Charlie Marsh
3ff1f003f4 Omit internal and documentation changes from changelog (#4814) 2023-06-02 15:39:37 +00:00
Micha Reiser
ebdc4afc33 Suite formatting and JoinNodesBuilder (#4805) 2023-06-02 14:14:38 +00:00
Jonathan Plasse
03ee6033f9 Fix flake8-fixme architecture (#4807) 2023-06-02 09:15:44 -04:00
Micha Reiser
a401989b7a Format StmtExpr (#4788) 2023-06-02 12:52:38 +00:00
Micha Reiser
4cd4b37e74 Format the comment content (#4786) 2023-06-02 11:22:34 +00:00
konstin
602b4b3519 Merge registry into codes (#4651)
* Document codes.rs

* Refactor codes.rs before merging

Helper script:
```python
# %%

from pathlib import Path

codes = Path("crates/ruff/src/codes.rs").read_text().splitlines()
rules = Path("a.txt").read_text().strip().splitlines()
rule_map = {i.split("::")[-1]: i for i in rules}

# %%

codes_new = []
for line in codes:
    if ", Rule::" in line:
        left, right = line.split(", Rule::")
        right = right[:-2]
        line = left + ", " + rule_map[right] + "),"
    codes_new.append(line)

# %%

Path("crates/ruff/src/codes.rs").write_text("\n".join(codes_new))
```

Co-authored-by: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com>
2023-06-02 10:33:01 +00:00
konstin
c4fdbf8903 Switch PyFormatter lifetimes (#4804)
Stylistic change to have the input lifetime first and the output lifetime second. I'll rebase my other PR on top of this.

Test plan: `cargo clippy`
2023-06-02 12:26:39 +02:00
Micha Reiser
5d939222db Leading, Dangling, and Trailing comments formatting (#4785) 2023-06-02 09:26:36 +02:00
Evan Rittenhouse
b2498c576f Implement flake8_fixme and refactor TodoDirective (#4681) 2023-06-02 08:18:47 +02:00
Micha Reiser
c89d2f835e Add to AnyNode and AnyNodeRef conversion methods to AstNode (#4783) 2023-06-02 08:10:41 +02:00
Charlie Marsh
211d8e170d Ignore error calls with exc_info in TRY400 (#4797) 2023-06-02 04:59:45 +00:00
Charlie Marsh
b92be59ffe Remove some matches on Stmt (#4796) 2023-06-02 04:36:36 +00:00
Charlie Marsh
b030c70dda Move unused imports rule into its own module (#4795) 2023-06-02 04:27:23 +00:00
Charlie Marsh
10ba79489a Exclude function definition from too-many-statements rule (#4794) 2023-06-02 04:04:25 +00:00
Charlie Marsh
ea3cbcc362 Avoid enforcing native-literals rule within nested f-strings (#4488) 2023-06-02 04:00:31 +00:00
Charlie Marsh
b8f45c93b4 Use a separate fix-isolation group for every parent node (#4774) 2023-06-02 03:07:55 +00:00
Charlie Marsh
621718784a Replace deletion-tracking with enforced isolation levels (#4766) 2023-06-02 02:45:56 +00:00
qdegraaf
fcbf5c3fae Add PYI034 for flake8-pyi plugin (#4764) 2023-06-02 02:15:57 +00:00
Justin Prieto
c68686b1de [flake8-pyi] Implement PYI054 (#4775) 2023-06-02 01:21:27 +00:00
Justin Prieto
583411a29f [flake8-pyi] Implement PYI053 (#4770) 2023-06-01 23:00:15 +00:00
qdegraaf
6d94aa89e3 [flake8-pyi] Implement PYI025 (#4791) 2023-06-01 22:45:31 +00:00
Sladyn
8d5d34c6d1 Migrate flake8_pyi_rules from unspecified to suggested and automatic (#4750) 2023-06-01 22:35:47 +00:00
Jonathan Plasse
edadd7814f Add pyflakes.extend-generics setting (#4677) 2023-06-01 22:19:37 +00:00
Charlie Marsh
3180f9978a Avoid extra newline between diagnostics in grouped mode (#4776) 2023-06-01 21:33:29 +00:00
Tom Kuson
bdff4a66ac Add Pylint rule C0208 (use-sequence-for-iteration) as PLC0208 (iteration-over-set) (#4706) 2023-06-01 21:26:23 +00:00
Charlie Marsh
ab26f2dc9d Use saturating_sub in more token-walking methods (#4773) 2023-06-01 17:16:32 -04:00
Dhruv Manilawala
0099f9720f Add autofix for PLR1701 (repeated-isinstance-calls) (#4792) 2023-06-01 20:43:04 +00:00
Tom Kuson
d9fdcebfc1 Complete the Pyflakes documention (#4787) 2023-06-01 20:25:32 +00:00
Charlie Marsh
b7038cee13 Include ImportError in non-fixable try-catch imports (#4793) 2023-06-01 19:53:49 +00:00
Charlie Marsh
be740106e0 Remove some lexer usages from Insertion (#4763) 2023-06-01 19:45:43 +00:00
konstin
63d892f1e4 Implement basic module formatting (#4784)
* Add Format for Stmt

* Implement basic module formatting

This implements formatting each statement in a module with a hard line break in between, so that we can start formatting statements.

Basic testing is done by the snapshots
2023-06-01 15:25:50 +02:00
Micha Reiser
28aad95414 Remove collapsing space behaviour from Printer (#4782) 2023-06-01 13:38:42 +02:00
Micha Reiser
5f4bce6d2b Implement IntoFormat for &T (#4781) 2023-06-01 12:20:49 +02:00
Micha Reiser
4ea4fd1984 Introduce lines_before helper (#4780) 2023-06-01 11:56:43 +02:00
konstin
d4027d8b65 Use new formatter infrastructure in CLI and test (#4767)
* Use dummy verbatim formatter for all nodes

* Use new formatter infrastructure in CLI and test

* Expose the new formatter in the CLI

* Merge import blocks
2023-06-01 11:55:04 +02:00
konstin
9bf168c0a4 Use dummy verbatim formatter for all nodes (#4755) 2023-06-01 08:25:26 +00:00
Micha Reiser
59148344be Place comments of left and right binary expression operands (#4751) 2023-06-01 07:01:32 +00:00
konstin
0945803427 Generate FormatRule definitions (#4724)
* Generate FormatRule definitions

* Generate verbatim output

* pub(crate) everything

* clippy fix

* Update crates/ruff_python_formatter/src/lib.rs

Co-authored-by: Micha Reiser <micha@reiser.io>

* Update crates/ruff_python_formatter/src/lib.rs

Co-authored-by: Micha Reiser <micha@reiser.io>

* stub out with Ok(()) again

* Update crates/ruff_python_formatter/src/lib.rs

Co-authored-by: Micha Reiser <micha@reiser.io>

* PyFormatContext::{contents, locator} with `#[allow(unused)]`

* Can't leak private type

* remove commented code

* Fix ruff errors

* pub struct Format{node} due to rust rules

---------

Co-authored-by: Julian LaNeve <lanevejulian@gmail.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-06-01 08:38:53 +02:00
Micha Reiser
b7294b48e7 Handle positional-only-arguments separator comments (#4748) 2023-06-01 06:22:49 +00:00
Micha Reiser
be31d71849 Correctly associate own-line comments in bodies (#4671) 2023-06-01 08:12:53 +02:00
Charlie Marsh
46c3b3af94 Use ALL in fixable documentation (#4772) 2023-05-31 22:30:12 -04:00
Charlie Marsh
3d34d9298d Remove erroneous method calls in flake8-unused-arguments docs (#4771) 2023-06-01 02:23:59 +00:00
Charlie Marsh
1156c65be1 Add autofix to move runtime-imports out of type-checking blocks (#4743) 2023-05-31 18:09:04 +00:00
Charlie Marsh
1a53996f53 Add autofix for flake8-type-checking (#4742) 2023-05-31 17:53:36 +00:00
Charlie Marsh
4bd395a850 Apply edits in sorted order (#4762) 2023-05-31 17:26:31 +00:00
Charlie Marsh
bb4f3dedf4 Enable start-of-block insertions (#4741) 2023-05-31 17:08:43 +00:00
Jonathan Plasse
01470d9045 Add E201, E202, E203 auto-fix (#4723) 2023-05-31 16:53:47 +00:00
Charlie Marsh
0b471197dc Extract lower-level edit utility from autofix module (#4737) 2023-05-31 16:50:54 +00:00
Charlie Marsh
399eb84d5e Add a ruff_textwrap crate (#4731) 2023-05-31 16:35:23 +00:00
konstin
35cd57d0fc Make running ruff on ruff possible (#4760)
I was wondering why `pip install -U ruff && ruff .` in the ruff repo would result in only noise while the pre-commit ruff works. Turns out the pre-commit has an exclude for the resources directories.

This adds the excludes from pre-commit to ruff's own pyproject.toml so `ruff .` works on ruff itself
2023-05-31 18:19:15 +02:00
qdegraaf
2b2812c4f2 Add PYI024 for flake8-pyi plugin (#4756) 2023-05-31 16:07:04 +00:00
Charlie Marsh
9d0ffd33ca Move universal newline handling into its own crate (#4729) 2023-05-31 12:00:47 -04:00
Micha Reiser
e209b5fc5f Add reformat check (#4753) 2023-05-31 17:36:15 +02:00
Alex Fikl
c1286d61df Ignore __setattr__ in FBT003 (#4752) 2023-05-31 10:36:19 -04:00
Micha Reiser
6c1ff6a85f Upgrade RustPython (#4747) 2023-05-31 08:26:35 +00:00
Micha Reiser
06bcb85f81 formatter: Remove CST and old formatting (#4730) 2023-05-31 08:27:23 +02:00
Charlie Marsh
d7a4999915 Flag empty strings in flake8-errmsg rules (#4745) 2023-05-31 04:37:43 +00:00
Charlie Marsh
d4e54cff05 Make organize imports an automatic edit (#4744) 2023-05-31 04:29:04 +00:00
Charlie Marsh
e1b6f6e57e Refactor flake8-type-checking rules to take Checker (#4739) 2023-05-30 22:51:44 +00:00
Charlie Marsh
50053f60f3 Rename top-of-file to start-of-file (#4735) 2023-05-30 21:53:36 +00:00
Charlie Marsh
a4f73ea8c7 Remove unused getrandom dependency (#4734) 2023-05-30 14:34:20 -04:00
Charlie Marsh
04a95cb9ee Add Rust toolchain as documentation dependency in CONTRIBUTING.md (#4733) 2023-05-30 17:39:45 +00:00
Charlie Marsh
f9b3f10456 Clarify that [tool.ruff] must be omitted for ruff.toml (#4732) 2023-05-30 17:35:28 +00:00
Charlie Marsh
f47a517e79 Enable callers to specify import-style preferences in Importer (#4717) 2023-05-30 16:46:19 +00:00
Charlie Marsh
ea31229be0 Track TYPE_CHECKING blocks in Importer (#4593) 2023-05-30 16:18:10 +00:00
Charlie Marsh
0854543328 Use a custom error type for symbol-import results (#4688) 2023-05-30 09:19:31 -04:00
Vadim Suharnikov
0bc3d99298 Add devcontainer support (#4676) (#4678) 2023-05-30 14:49:51 +02:00
Micha Reiser
0cd453bdf0 Generic "comment to node" association logic (#4642) 2023-05-30 09:28:01 +00:00
Micha Reiser
84a5584888 Add Comments data structure (#4641) 2023-05-30 08:54:55 +00:00
Micha Reiser
6146b75dd0 Add MultiMap implementation for storing comments (#4639) 2023-05-30 09:51:25 +02:00
Micha Reiser
236074fdde testing_macros: Add missing full feature to syn dependency (#4722) 2023-05-30 07:42:06 +00:00
Charlie Marsh
e323bb015b Move fixable checks into patch blocks (#4721) 2023-05-30 02:09:30 +00:00
Charlie Marsh
80fa3f2bfa Add a convenience method to check if a name is bound (#4718) 2023-05-30 01:52:41 +00:00
Charlie Marsh
1846d90bbd Rename the flake8-future-annotations rules (#4716) 2023-05-29 23:00:08 +00:00
Aarni Koskela
0106bce02f [flake8-future-annotations] Implement FA102 (#4702) 2023-05-29 22:41:45 +00:00
Charlie Marsh
2695d0561a Add ability to generate snapshot tests on code snippets (#4714) 2023-05-29 18:36:12 -04:00
Charlie Marsh
5f715417e0 Remove redundant test descriptions from #test_case macros (#4713) 2023-05-29 18:23:56 -04:00
Jonathan Plasse
f7c2d25205 Remove enumerated plugins in rules page (#4715) 2023-05-29 22:20:41 +00:00
Charlie Marsh
6e096f216a Fix docs formatting for iter-method-returns-iterable (#4712) 2023-05-29 21:34:42 +00:00
Justin Prieto
d0ad4be20e [flake8-pyi] Implement PYI045 (#4700) 2023-05-29 21:27:13 +00:00
Julian LaNeve
6425fe8c12 Update option anchors to include group name (#4711) 2023-05-29 17:26:10 -04:00
Julian LaNeve
68db74b3c5 Add AIR001: task variable name should be same as task_id arg (#4687) 2023-05-29 03:25:06 +00:00
Charlie Marsh
9646bc7d7f Add docs to clarify project root heuristics (#4697) 2023-05-29 02:50:35 +00:00
Julian LaNeve
5756829344 markdownlint: enforce 100 char max length (#4698) 2023-05-28 22:45:56 -04:00
Julian LaNeve
cb45b25879 Add contributing docs specific to rule-testing patterns (#4690) 2023-05-28 22:52:49 +00:00
qdegraaf
0911ce4cbc [flake8-pyi] Add PYI032 rule with autofix (#4695) 2023-05-28 22:41:15 +00:00
Tom Kuson
51f04ee6ef Add more Pyflakes docs (#4689) 2023-05-28 22:29:03 +00:00
Charlie Marsh
dbeadd99a8 Remove impossible states from version-comparison representation (#4696) 2023-05-28 22:08:40 +00:00
Dhruv Manilawala
79b35fc3cc Handle dotted alias imports to check for implicit imports (#4685) 2023-05-27 23:58:03 -04:00
Jonathan Plasse
9f16ae354e Fix UP036 auto-fix error (#4679) 2023-05-28 03:37:22 +00:00
Charlie Marsh
9741f788c7 Remove globals table from Scope (#4686) 2023-05-27 22:35:20 -04:00
Jonathan Plasse
901060fa96 Fix PLW3301 false positive single argument nested min/max (#4683) 2023-05-27 15:34:55 -04:00
Charlie Marsh
f069eb9e3d Fix async for formatting (#4675) 2023-05-27 02:53:33 +00:00
Tom Kuson
fe72bde23c Add Pylint string formatting rule docs (#4638) 2023-05-27 02:47:14 +00:00
Chris Chan
1268ddca92 Implement Pylint's yield-inside-async-function rule (PLE1700) (#4668) 2023-05-27 01:14:41 +00:00
Charlie Marsh
af433ac14d Avoid using typing-imported symbols for runtime edits (#4649) 2023-05-26 20:36:37 -04:00
qdegraaf
ccca11839a Allow more immutable funcs for RUF009 (#4660) 2023-05-26 15:18:52 -04:00
konstin
12e45498e8 Improve token handling (#4653)
* Use release environment

* Use pypi trusted publishing

* typo
2023-05-26 09:52:24 +02:00
Micha Reiser
33a7ed058f Create PreorderVisitor trait (#4658) 2023-05-26 06:14:08 +00:00
qdegraaf
52deeb36ee Implement PYI048 for flake8-pyi plugin (#4645) 2023-05-25 20:04:14 +00:00
Charlie Marsh
0f610f2cf7 Remove dedicated ScopeKind structs in favor of AST nodes (#4648) 2023-05-25 19:31:02 +00:00
Evan Rittenhouse
741e180e2d Change TODO directive detection to work with multiple pound signs on the same line (#4558) 2023-05-25 16:51:45 +02:00
konstin
b6a382eeaf Lint pyproject.toml (#4496)
This adds a new rule `InvalidPyprojectToml` that lints pyproject.toml by checking if https://github.com/PyO3/pyproject-toml-rs can parse it. This means the linting is currently very basic, e.g. we don't check whether the name is actually a valid python project name or appropriately normalized. It does catch errors e.g. with invalid dependency requirements or problems withs the license specifications. It is open to be extended in the future (validate name, SPDX expressions, classifiers, ...), either in ruff or in pyproject-toml-rs.

Test plan:

```
scripts/ecosystem_all_check.sh check --select RUF200
```
This lead to a bunch of 
```
RUF200 Failed to parse pyproject.toml: missing field `name`
```
(e.g. https://github.com/amitsk/fastapi-todos/blob/main/pyproject.toml) which is indeed invalid (https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#specification).

Filtering those out, the following other problems were found by `cd target/ecosystem_all_results/ && rg RUF200`:
```
UCL-ARC:rred-reports.stdout.txt
1:pyproject.toml:27:16: RUF200 Failed to parse pyproject.toml: Version specifier `>='3.9'` doesn't match PEP 440 rules
EndlessTrax:python-start-project.stdout.txt
1:pyproject.toml:14:16: RUF200 Failed to parse pyproject.toml: Expected package name starting with an alphanumeric character, found '#'
redjax:gardening-api.stdout.txt
1:pyproject.toml:7:11: RUF200 Failed to parse pyproject.toml: Version `` doesn't match PEP 440 rules
ajslater:codex.stdout.txt
2:  3:17 RUF200 Failed to parse pyproject.toml: invalid type: sequence, expected a string
LDmitriy7:404_AvatarsBot.stdout.txt
1:pyproject.toml:3:11: RUF200 Failed to parse pyproject.toml: Version `` doesn't match PEP 440 rules
ajslater:comicbox.stdout.txt
1:pyproject.toml:3:17: RUF200 Failed to parse pyproject.toml: invalid type: sequence, expected a string
manueldevillena:forecast-earnings.stdout.txt
1:pyproject.toml:24:12: RUF200 Failed to parse pyproject.toml: Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `^`
redjax:ohio_utility_scraper.stdout.txt
1:pyproject.toml:11:11: RUF200 Failed to parse pyproject.toml: Version `` doesn't match PEP 440 rules
agronholm:typeguard.stdout.txt
1:pyproject.toml:40:8: RUF200 Failed to parse pyproject.toml: Expected a valid marker name, found 'python_implementation'
cyuss:decathlon-turnover.stdout.txt
1:pyproject.toml:7:12: RUF200 Failed to parse pyproject.toml: invalid type: string "Youcef", expected a table with 'name' and 'email' keys
ajslater:boilerplate.stdout.txt
1:pyproject.toml:3:17: RUF200 Failed to parse pyproject.toml: invalid type: sequence, expected a string
kaparoo:lightning-project-template.stdout.txt
1:pyproject.toml:56:16: RUF200 Failed to parse pyproject.toml: You can't mix a >= operator with a local version (`+cu117`)
dijital20:pytexas2023-decorators.stdout.txt
1:pyproject.toml:5:11: RUF200 Failed to parse pyproject.toml: Version `` doesn't match PEP 440 rules
pfouque:django-anymail-history.stdout.txt
1:pyproject.toml:137:12: RUF200 Failed to parse pyproject.toml: Version specifier `> = 1.2.0` doesn't match PEP 440 rules
pfouque:django-fakemessages.stdout.txt
1:pyproject.toml:130:12: RUF200 Failed to parse pyproject.toml: Version specifier `> = 1.2.0` doesn't match PEP 440 rules
pypa:build.stdout.txt
1:tests/packages/test-invalid-requirements/pyproject.toml:2:12: RUF200 Failed to parse pyproject.toml: Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `i`
4:tests/packages/test-no-requires/pyproject.toml:1:1: RUF200 Failed to parse pyproject.toml: missing field `requires`
UnoYakshi:FRAAND.stdout.txt
2:  3:11 RUF200 Failed to parse pyproject.toml: Version `` doesn't match PEP 440 rules
DHolmanCoding:python-template.stdout.txt
1:pyproject.toml:22:1: RUF200 Failed to parse pyproject.toml: missing field `requires`
```
Overall, this emitted errors in 43 out of 3408 projects (`rg -c RUF200 target/ecosystem_all_results/ | wc -l`)


Co-authored-by: Micha Reiser <micha@reiser.io>
2023-05-25 12:05:28 +00:00
qdegraaf
050350527c Add autofix for PYI010 (#4634) 2023-05-24 22:17:44 +00:00
Charlie Marsh
c9b39e31fc Use class name as range for B024 (#4647) 2023-05-24 22:16:13 +00:00
bersbersbers
28a5e607b4 Docs: mention task-tags option in two rules (#4644) 2023-05-24 16:31:41 -04:00
Micha Reiser
09c50c311c Testing Macros: Add extra-traits feature (#4643) 2023-05-24 17:14:58 +00:00
Charlie Marsh
252506f8ed Remove deprecated --universal2 flag (#4640) 2023-05-24 17:00:52 +00:00
Charlie Marsh
f4572fe40b Bump version to 0.0.270 (#4637) 2023-05-24 16:34:29 +00:00
Sladyn
8c9215489e Migrate flake8_bugbear rules to unspecified to suggested (#4616) 2023-05-24 16:16:33 +00:00
qdegraaf
dcd2bfaab7 Migrate flake8_pie autofix rules from unspecified to suggested and automatic (#4621) 2023-05-24 16:08:22 +00:00
Charlie Marsh
f0e173d9fd Use BindingId copies in lieu of &BindingId in semantic model methods (#4633) 2023-05-24 15:55:45 +00:00
Charlie Marsh
f4f1b1d0ee Only run the playground release job on release (#4636) 2023-05-24 11:48:36 -04:00
Micha Reiser
edc6c4058f Move shared_traits to ruff_formatter (#4632) 2023-05-24 17:38:11 +02:00
Jonathan Plasse
4233f6ec91 Update to the new rule architecture (#4589) 2023-05-24 11:30:40 -04:00
Charlie Marsh
fcdc7bdd33 Remove separate ReferenceContext enum (#4631) 2023-05-24 15:12:38 +00:00
Micha Reiser
86ced3516b Introduce SourceCodeSlice to reduce the size of FormatElement (#4622)
Introduce `SourceCodeSlice` to reduce the size of `FormatElement`
2023-05-24 15:04:52 +00:00
Micha Reiser
6943beee66 Remove source position from FormatElement::DynamicText (#4619) 2023-05-24 16:36:14 +02:00
Micha Reiser
85f094f592 Improve Message sorting performance (#4624) 2023-05-24 16:34:48 +02:00
konstin
17d938f078 Add Checker::any_enabled shortcut (#4630)
Add Checker::any_enabled shortcut

 ## Summary

 Akin to #4625, This is a refactoring that shortens a bunch of code by replacing `checker.settings.rules.any_enabled` with `checker.any_enabled`.

 ## Test Plan

 `cargo clippy`
2023-05-24 14:32:55 +00:00
Charlie Marsh
5cedf0f724 Remove ReferenceContext::Synthetic (#4612) 2023-05-24 14:30:35 +00:00
konstin
38297c08b4 Make ecosystem all check more generic (#4629)
* Don't assume unique repo names in ecosystem checks

This fixes a bug where previously repositories with the same name would have been overwritten.

I tested with `scripts/check_ecosystem.py -v --checkouts target/checkouts_main .venv/bin/ruff target/release/ruff` and ruff 0.0.267 that changes are shown. I confirmed with `scripts/ecosystem_all_check.sh check --select RUF008` (next PR) that the checkouts are now complete.

* Make ecosystem all check more generic

This allows passing arguments to the ecosystem all check script, e.g. you can now do `scripts/ecosystem_all_check.sh check --select RUF008`.

Tested with
```
$ cat target/ecosystem_all_results/*.stdout.txt | head
src/fi_parliament_tools/parsing/data_structures.py:33:17: RUF008 Do not use mutable default values for dataclass attributes
src/fi_parliament_tools/parsing/data_structures.py:76:17: RUF008 Do not use mutable default values for dataclass attributes
src/fi_parliament_tools/parsing/data_structures.py:178:17: RUF008 Do not use mutable default values for dataclass attributes
Found 3 errors.
braid_triggers/tasks.py:46:17: RUF008 Do not use mutable default values for dataclass attributes
Found 1 error.
src/boards/RaspberryPi3.py:15:22: RUF008 Do not use mutable default values for dataclass attributes
src/boards/board.py:21:26: RUF008 Do not use mutable default values for dataclass attributes
src/boards/board.py:22:32: RUF008 Do not use mutable default values for dataclass attributes
src/boards/board.py:23:37: RUF008 Do not use mutable default values for dataclass attributes
$ cat target/ecosystem_all_results/*.stdout.txt | wc -l
115
```
2023-05-24 16:26:23 +02:00
konstin
30e90838d0 Don't assume unique repo names in ecosystem checks (#4628)
This fixes a bug where previously repositories with the same name would have been overwritten.

I tested with `scripts/check_ecosystem.py -v --checkouts target/checkouts_main .venv/bin/ruff target/release/ruff` and ruff 0.0.267 that changes are shown. I confirmed with `scripts/ecosystem_all_check.sh check --select RUF008` (next PR) that the checkouts are now complete.
2023-05-24 16:26:12 +02:00
Charlie Marsh
040fb9cef4 Use a separate PrinterFlag for including fix diffs (#4615) 2023-05-24 10:22:37 -04:00
Charlie Marsh
8961d8eb6f Track all read references in semantic model (#4610) 2023-05-24 14:14:27 +00:00
Charlie Marsh
31bddef98f Visit TypeVar and NewType name arguments (#4627) 2023-05-24 10:10:15 -04:00
konstin
a59d252246 Add Checker::enabled shortcut (#4625)
This is a refactoring that shortens a bunch of code by replacing `checker.settings.rules.enabled` with `checker.enabled`
2023-05-24 14:56:41 +02:00
konstin
5b9d4f18ae Remove outdated feature flag from Dockerfile.ecosystem (#4620) 2023-05-24 08:19:08 +00:00
Jonathan Plasse
c6a760e298 Introduce tab-size to correcly calculate the line length with tabulations (#4167) 2023-05-24 08:37:24 +02:00
konstin
3644695bf2 Include hidden ecosystem_ci option to show fixes without feature (#4528) 2023-05-23 22:22:23 -04:00
Evan Rittenhouse
b1d01b1950 Add a PR template (#4582) 2023-05-24 02:17:38 +00:00
Sladyn
4e84e8a8e2 Migrate some rules from Fix::unspecified (#4587) 2023-05-23 22:10:58 -04:00
Hoël Bagard
a256fdb9f4 Extend RUF005 to recursive and literal-literal concatenations (#4557) 2023-05-24 01:26:34 +00:00
Tom Kuson
7479dfd815 Add Pyflakes docs (#4588) 2023-05-24 00:45:32 +00:00
Charlie Marsh
ba4c0a21fa Rename ContextFlags to SemanticModelFlags (#4611) 2023-05-23 17:47:07 -04:00
konstin
73e179ffab Update maturin to 1.0 (#4605)
* Refactor and fix task trigger for dependent jobs in other repos

I have confirmed (https://github.com/konstin/ruff-pre-commit/actions/runs/5056928280/jobs/9075029868) that this does dispatch the workflow when running with act, `owner: 'konstin'`, `needs` commented out and personal access token. I can't properly test the actual release workflow, and i'm unsure how to best handle the next release after this was merged (should we do a beta release or will this break everything that assumes we only do stable releases?)

The command for act is
```
act -j update-dependents -s RUFF_PRE_COMMIT_PAT=<...>
```

* delete old file

* Update maturin to 1.0

A 1.0 release for maturin 🎉
2023-05-23 20:55:52 +02:00
konstin
3cbaaa4795 Refactor and fix task trigger for dependent jobs in other repos (#4598)
* Refactor and fix task trigger for dependent jobs in other repos

I have confirmed (https://github.com/konstin/ruff-pre-commit/actions/runs/5056928280/jobs/9075029868) that this does dispatch the workflow when running with act, `owner: 'konstin'`, `needs` commented out and personal access token. I can't properly test the actual release workflow, and i'm unsure how to best handle the next release after this was merged (should we do a beta release or will this break everything that assumes we only do stable releases?)

The command for act is
```
act -j update-dependents -s RUFF_PRE_COMMIT_PAT=<...>
```

* delete old file
2023-05-23 20:55:35 +02:00
Micha Reiser
2681c0e633 Add missing nodes to AnyNodeRef and AnyNode (#4608) 2023-05-23 18:30:27 +02:00
Charlie Marsh
f3bdd2e7be Make B007 fix relevance stricter (#4607) 2023-05-23 15:43:59 +00:00
Micha Reiser
652c644c2a Introduce ruff_index crate (#4597) 2023-05-23 17:40:35 +02:00
konstin
04d273bcc7 Add a script to update the schemastore (#4574)
* Add a script to update the schemastore

Hacked this together, it clones astral-sh/schemastore, updated the schema and pushes the changes
to a new branch tagged with the ruff git hash. You can see the URL to create the PR
to schemastore in the CLI. The script is separated into three blocks so you can rerun
the schema generation in the middle before committing.

* Use tempdir for schemastore

* Add comments
2023-05-23 10:41:56 +00:00
Micha Reiser
154439728a Add AnyNode and AnyNodeRef unions (#4578) 2023-05-23 08:53:22 +02:00
Jonathan Plasse
1ddc577204 Rework CST matchers (#4536)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-05-23 06:26:51 +00:00
Charlie Marsh
74effb40b9 Rename index to binding_id in a few iterators (#4594) 2023-05-23 03:56:00 +00:00
Charlie Marsh
6c3724ab98 Move get_or_import_symbol onto Importer (#4591) 2023-05-23 01:33:00 +00:00
Christopher Covington
3b8121379d Name ambiguous characters (#4448) 2023-05-22 17:16:57 +00:00
qdegraaf
5ba47c3302 Add autofix for PYI009 (#4583) 2023-05-22 16:41:18 +00:00
Charlie Marsh
b613460fe5 Fix # isort: split comment detection in nested blocks (#4584) 2023-05-22 12:31:59 -04:00
Micha Reiser
daadd24bde Include decorators in Function and Class definition ranges (#4467) 2023-05-22 17:50:42 +02:00
Charlie Marsh
9308e939f4 Avoid infinite loop for required imports with isort: off (#4581) 2023-05-22 15:49:03 +00:00
Charlie Marsh
04c9348de0 Make ambiguous-unicode detection sensitive to 'word' context (#4552) 2023-05-22 14:42:25 +00:00
Tom Kuson
2d3766d928 Add flake8-boolean-trap docs (#4572) 2023-05-22 14:11:14 +00:00
konstin
550b643e33 Add script for ecosystem wide checks of all rules and fixes (#4326)
* Add script for ecosystem wide checks of all rules and fixes

This adds my personal script for checking an entire checkout of ~2.1k packages for
panics, autofix errors and similar problems. It's not really meant to be used by anybody else but i thought it's better if it lives in the repo than if it doesn't.

For reference, this is the current output of failing autofixes: https://gist.github.com/konstin/c3fada0135af6cacec74f166adf87a00. Trimmed down to the useful information: https://gist.github.com/konstin/c864f4c300c7903a24fdda49635c5da9

* Keep github template intact

* Remove the need for ripgrep

* sort output
2023-05-22 15:23:25 +02:00
Micha Reiser
cbe344f4d5 Rename Checker::model to semantic_model (#4573) 2023-05-22 15:14:30 +02:00
Micha Reiser
063431cb0f Upgrade RustPython (#4576) 2023-05-22 14:50:49 +02:00
Evan Rittenhouse
c6e5fed658 Replace token iteration with Indexer/Locator lookups for relevant rules (#4513) 2023-05-22 09:56:19 +02:00
Charlie Marsh
f73b398776 Reduce visibility of more functions, structs, and fields (#4570) 2023-05-22 03:36:48 +00:00
Charlie Marsh
55c4020ba9 Remove regex for noqa code splitting (#4569) 2023-05-21 23:20:49 -04:00
Charlie Marsh
d70f899f71 Use SemanticModel in lieu of Checker in more methods (#4568) 2023-05-22 02:58:47 +00:00
Charlie Marsh
19c4b7bee6 Rename ruff_python_semantic's Context struct to SemanticModel (#4565) 2023-05-22 02:35:03 +00:00
Jonathan Plasse
3238743a7b Fix Flake8Todo typo (#4566) 2023-05-21 16:32:13 -04:00
Charlie Marsh
f22c269ccf Point LSP, VS Code, and pre-commut URLs to Astral org (#4562) 2023-05-21 15:27:35 -04:00
Arne de Laat
8ca3977602 Fix false-positive for TRY302 if exception cause is given (#4559) 2023-05-21 11:49:53 -04:00
Jacob Coffee
6db05d8cc6 Starlite -> Litestar (#4554) 2023-05-21 09:55:26 -04:00
Jonathan Plasse
fc63c6f2e2 Fix PLE01310 typo (#4550) 2023-05-20 19:34:03 +00:00
Jonathan Plasse
f7f5bc9085 Fix SIM401 snapshot (#4547) 2023-05-20 14:18:19 -04:00
Charlie Marsh
6b85430a14 Ignore #region code folding marks in eradicate rules (#4546) 2023-05-20 16:45:49 +00:00
Jonathan Plasse
a68c865010 Fix SIM110 and SIM111 ranges (#4545) 2023-05-20 12:40:35 -04:00
Charlie Marsh
fe7f2e2e4d Move submodule alias resolution into Context (#4543) 2023-05-20 16:34:10 +00:00
Felipe Peter
0a3cf8ba11 Fix typos in docs (#4540) 2023-05-20 07:23:17 -04:00
Charlie Marsh
bf5b463c0d Include empty success test in JUnit output (#4537) 2023-05-20 03:38:51 +00:00
Charlie Marsh
6aa9900c03 Improve handling of __qualname__, __module__, and __class__ (#4512) 2023-05-20 03:03:45 +00:00
Charlie Marsh
9e21414294 Improve reference resolution for deferred-annotations-within-classes (#4509) 2023-05-20 02:54:18 +00:00
Charlie Marsh
bb4e674415 Move reference-resolution into Context (#4510) 2023-05-20 02:47:15 +00:00
Charlie Marsh
b42ff08612 Parenthesize more sub-expressions in f-string conversion (#4535) 2023-05-19 19:41:30 +00:00
Jonathan Plasse
03fb62c174 Fix RUF010 auto-fix with parenthesis (#4524) 2023-05-19 19:05:51 +00:00
Jonathan Plasse
2dfc645ea9 Fix UP032 auto-fix with integers (#4525) 2023-05-19 18:53:50 +00:00
Hoël Bagard
fe8e2bb237 [pylint] Add named_expr_without_context (W0131) (#4531) 2023-05-19 18:00:01 +00:00
Tom Kuson
a9ed8d5391 Add Pylint docs (#4530) 2023-05-19 17:40:18 +00:00
Aaron Cunningham
41a681531d Support new extend-per-file-ignores setting (#4265) 2023-05-19 12:24:04 -04:00
Justin Prieto
837e70677b [flake8-pyi] Implement PYI013 (#4517) 2023-05-19 15:39:55 +00:00
Hoël Bagard
7ebe372122 [pylint] Add duplicate-value (W0130) (#4515) 2023-05-19 15:03:47 +00:00
konstin
625849b846 Ecosystem CI: Optionally diff fixes (#4193)
* Generate fixes when using --show-fixes

Example command: `cargo run --bin ruff -- --no-cache --select F401
--show-source --show-fixes
crates/ruff/resources/test/fixtures/pyflakes/F401_9.py`

Before, `--show-fixes` was ignored:

```
crates/ruff/resources/test/fixtures/pyflakes/F401_9.py:4:22: F401 [*] `foo.baz` imported but unused
  |
4 | __all__ = ("bar",)
5 | from foo import bar, baz
  |                      ^^^ F401
  |
  = help: Remove unused import: `foo.baz`

Found 1 error.
[*] 1 potentially fixable with the --fix option.
```

After:

```
crates/ruff/resources/test/fixtures/pyflakes/F401_9.py:4:22: F401 [*] `foo.baz` imported but unused
  |
4 | __all__ = ("bar",)
5 | from foo import bar, baz
  |                      ^^^ F401
  |
  = help: Remove unused import: `foo.baz`

ℹ Suggested fix
1 1 | """Test: late-binding of `__all__`."""
2 2 |
3 3 | __all__ = ("bar",)
4   |-from foo import bar, baz
  4 |+from foo import bar

Found 1 error.
[*] 1 potentially fixable with the --fix option.
```

Also fixes git clone
2023-05-19 09:49:57 +00:00
konstin
32f1edc555 Create dummy format CLI (#4453)
* Create dummy format CLI

* Hide format from clap, too

Missed that this is a separate option from `#[doc(hidden)]`

* Remove cargo feature and replace with warning

* No-alloc files parameter matching

* beta warning: warn -> warn_user_once

* Rephrase warning
2023-05-19 11:45:52 +02:00
Micha Reiser
2f35099f81 Remove regex dependency from ruff_python_ast (#4518) 2023-05-19 06:44:18 +00:00
Hoël Bagard
ce8fd31a8f Updated contributing documentation (#4516) 2023-05-19 08:39:15 +02:00
Ville Skyttä
fdb241cad2 [flake8-bandit] Implement paramiko-call (S601) (#4500) 2023-05-19 03:40:50 +00:00
Charlie Marsh
ab303f4e09 Gate schemars skip under feature flag (#4514) 2023-05-19 03:01:31 +00:00
Charlie Marsh
15cb21a6f4 Implement --extend-fixable option (#4297) 2023-05-18 22:20:19 -04:00
Ville Skyttä
2e2ba2cb16 Avoid some false positives in dunder variable assigments (#4508) 2023-05-19 02:11:20 +00:00
Charlie Marsh
d4c0a41b00 Bump version to 0.0.269 (#4506) 2023-05-18 19:45:20 +00:00
Charlie Marsh
8702b5a40a Bump version to 0.0.268 (#4501) 2023-05-18 15:35:46 -04:00
figsoda
bab818e801 Update RustPython dependencies (#4503) 2023-05-18 15:28:13 -04:00
1232 changed files with 52317 additions and 27511 deletions

View File

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

View File

@@ -0,0 +1,46 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
{
"name": "Ruff",
"image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye",
"mounts": [
{
"source": "devcontainer-cargo-cache-${devcontainerId}",
"target": "/usr/local/cargo",
"type": "volume"
}
],
"customizations": {
"codespaces": {
"openFiles": [
"CONTRIBUTING.md"
]
},
"vscode": {
"extensions": [
"ms-python.python",
"rust-lang.rust-analyzer",
"serayuzgur.crates",
"tamasfe.even-better-toml",
"Swellaby.vscode-rust-test-adapter",
"charliermarsh.ruff"
],
"settings": {
"rust-analyzer.updates.askBeforeDownload": false
}
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/python": {
"installTools": false
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
"postCreateCommand": ".devcontainer/post-create.sh"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

8
.devcontainer/post-create.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
rustup default < rust-toolchain
rustup component add clippy rustfmt
cargo install cargo-insta
cargo fetch
pip install maturin pre-commit

View File

@@ -14,4 +14,7 @@ indent_size = 2
indent_size = 4
[*.snap]
trim_trailing_whitespace = false
trim_trailing_whitespace = false
[*.md]
max_line_length = 100

15
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,15 @@
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->
## Summary
<!-- What's the purpose of the change? What does it do, and why? -->
## Test Plan
<!-- How was it tested? -->

5
.github/release.yml vendored
View File

@@ -1,5 +1,9 @@
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
changelog:
exclude:
labels:
- internal
- documentation
categories:
- title: Breaking Changes
labels:
@@ -11,6 +15,7 @@ changelog:
- title: Settings
labels:
- configuration
- cli
- title: Bug Fixes
labels:
- bug

View File

@@ -183,18 +183,8 @@ jobs:
- name: "Install cargo-udeps"
uses: taiki-e/install-action@cargo-udeps
- name: "Run cargo-udeps"
run: |
unused_dependencies=$(cargo +nightly-2023-03-30 udeps > unused.txt && cat unused.txt | cut -d $'\n' -f 2-)
if [ -z "$unused_dependencies" ]; then
echo "No unused dependencies found" > $GITHUB_STEP_SUMMARY
exit 0
else
echo "Found unused dependencies" > $GITHUB_STEP_SUMMARY
echo '```console' >> $GITHUB_STEP_SUMMARY
echo "$unused_dependencies" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
run: cargo +nightly-2023-03-30 udeps
python-package:
name: "python package"

View File

@@ -1,9 +1,9 @@
name: mkdocs
on:
release:
types: [published]
workflow_dispatch:
release:
types: [ published ]
jobs:
mkdocs:

View File

@@ -52,7 +52,7 @@ jobs:
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
with:
args: --release --universal2 --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
args: --release --target universal2-apple-darwin --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- name: "Install built wheel - universal2"
run: |
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall

View File

@@ -2,8 +2,8 @@ name: "[Playground] Release"
on:
workflow_dispatch:
push:
branches: [main]
release:
types: [ published ]
env:
CARGO_INCREMENTAL: 0

View File

@@ -3,7 +3,7 @@ name: "[ruff] Release"
on:
workflow_dispatch:
release:
types: [published]
types: [ published ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -34,6 +34,7 @@ jobs:
args: --out dist
- name: "Test sdist"
run: |
rustup default $(cat rust-toolchain)
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
ruff --help
python -m ruff --help
@@ -94,7 +95,7 @@ jobs:
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
with:
args: --release --universal2 --out dist
args: --release --target universal2-apple-darwin --out dist
- name: "Test wheel - universal2"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
@@ -394,21 +395,22 @@ jobs:
- musllinux
- musllinux-cross
if: "startsWith(github.ref, 'refs/tags/')"
environment:
name: release
permissions:
# For pypi trusted publishing
id-token: write
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
- uses: actions/setup-python@v4
path: wheels
- name: "Publish to PyPi"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
run: |
pip install --upgrade twine
twine upload --skip-existing *
- name: "Update pre-commit mirror"
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.RUFF_PRE_COMMIT_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/charliermarsh/ruff-pre-commit/dispatches --data '{"event_type": "pypi_release"}'
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
packages-dir: wheels
verbose: true
- uses: actions/download-artifact@v3
with:
name: binaries
@@ -417,3 +419,22 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: binaries/*
# After the release has been published, we update downstream repositories
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
update-dependents:
name: Release
runs-on: ubuntu-latest
needs: release
steps:
- name: "Update pre-commit mirror"
uses: actions/github-script@v6
with:
github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
script: |
github.rest.actions.createWorkflowDispatch({
owner: 'astral-sh',
repo: 'ruff-pre-commit',
workflow_id: 'main.yml',
ref: 'main',
})

5
.gitignore vendored
View File

@@ -1,10 +1,11 @@
# Local cache
.ruff_cache
crates/ruff/resources/test/cpython
mkdocs.yml
.overrides
ruff-old
github_search*.jsonl
schemastore
.venv*
scratch.py
###
# Rust.gitignore

14
.markdownlint.yaml Normal file
View File

@@ -0,0 +1,14 @@
# default to true for all rules
default: true
# MD033/no-inline-html
MD033: false
# MD041/first-line-h1
MD041: false
# MD013/line-length
MD013:
line_length: 100
code_blocks: false
ignore_code_blocks: true

View File

@@ -1,4 +1,12 @@
fail_fast: true
exclude: |
(?x)^(
crates/ruff/resources/.*|
crates/ruff_python_formatter/resources/.*|
crates/ruff_python_formatter/src/snapshots/.*
)$
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.12.1
@@ -17,14 +25,9 @@ repos:
rev: v0.33.0
hooks:
- id: markdownlint-fix
args:
- --disable
- MD013 # line-length
- MD033 # no-inline-html
- --
- repo: https://github.com/crate-ci/typos
rev: v1.14.8
rev: v1.14.12
hooks:
- id: typos

View File

@@ -86,7 +86,8 @@ 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

View File

@@ -8,6 +8,7 @@ Welcome! We're happy to have you here. Thank you in advance for your contributio
- [Project Structure](#project-structure)
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
- [Rule naming convention](#rule-naming-convention)
- [Rule testing: fixtures and snapshots](#rule-testing-fixtures-and-snapshots)
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [MkDocs](#mkdocs)
- [Release Process](#release-process)
@@ -93,9 +94,11 @@ At time of writing, the repository includes the following crates:
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g.,
`cargo dev generate-all`).
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of
standard library modules by version).
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
### Example: Adding a new lint rule
@@ -103,14 +106,20 @@ 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).
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 `#[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/mod.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).
1. Add a test fixture.
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `E402`).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/mod.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).
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
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
@@ -125,18 +134,8 @@ collecting diagnostics as it goes.
If you need to inspect the AST, you can run `cargo dev print-ast` with a Python file. Grep
for the `Check::new` invocations to understand how other, similar rules are implemented.
To add a test fixture, create a file under `crates/ruff/resources/test/fixtures/[linter]`, named to match
the code you defined earlier (e.g., `crates/ruff/resources/test/fixtures/pycodestyle/E402.py`). This file should
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
of your lint rule.
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
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`.
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.
Once you're satisfied with your code, add tests for your rule. See [rule testing](#rule-testing-fixtures-and-snapshots)
for more details.
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
@@ -148,12 +147,44 @@ This implies that rule names:
- 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 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.
#### Rule testing: fixtures and snapshots
To test rules, Ruff uses snapshots of Ruff's output for a given file (fixture). Generally, there
will be one file per rule (e.g., `E402.py`), and each file will contain all necessary examples of
both violations and non-violations. `cargo insta review` will generate a snapshot file containing
Ruff's output for each fixture, which you can then commit alongside your changes.
Once you've completed the code for the rule itself, you can define tests with the following steps:
1. Add a Python file to `crates/ruff/resources/test/fixtures/[linter]` that contains the code you
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
examples of both violations and non-violations.
1. Run Ruff locally against your file and verify the output is as expected. Once you're satisfied
with the output (you see the violations you expect, and no others), proceed to the next step.
For example, if you're adding a new rule named `E402`, you would run:
```shell
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
```
1. Add the test to the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. If you're contributing
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
1. Run `cargo test`. Your test will fail, but you'll be prompted to follow-up
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
then commit the snapshot file alongside the rest of your changes.
1. Run `cargo test` again to ensure that your test passes.
### Example: Adding a new configuration option
Ruff's user-facing settings live in a few different places.
@@ -184,6 +215,8 @@ Finally, regenerate the documentation and generated code with `cargo dev generat
To preview any changes to the documentation locally:
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
1. Install MkDocs and Material for MkDocs with:
```shell

412
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ members = ["crates/*"]
[workspace.package]
edition = "2021"
rust-version = "1.69"
rust-version = "1.70"
homepage = "https://beta.ruff.rs/docs/"
documentation = "https://beta.ruff.rs/docs/"
repository = "https://github.com/charliermarsh/ruff"
@@ -11,7 +11,7 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.2.1" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
clap = { version = "4.1.8", features = ["derive"] }
colored = { version = "2.0.0" }
@@ -24,17 +24,21 @@ is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
log = { version = "0.4.17" }
memchr = "2.5.0"
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
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" }
ruff_text_size = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73" }
rustpython-format = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73" }
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73" }
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "e820928f11a2453314ad4d5ce23f066d1d3faf73", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] }
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" }
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd", default-features = false, features = ["all-nodes-with-ranges"]}
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" }
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" }
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93", features = ["preserve_order"] }
@@ -45,7 +49,6 @@ strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
[profile.release]

View File

@@ -24,17 +24,18 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/charliermarsh/ruff-vscode) and [more](https://github.com/charliermarsh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🛠️ `pyproject.toml` support
- 🤝 Python 3.11 compatibility
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface.
@@ -84,7 +85,8 @@ of [Conda](https://docs.conda.io/en/latest/):
[**Timothy Crosley**](https://twitter.com/timothycrosley/status/1606420868514877440),
creator of [isort](https://github.com/PyCQA/isort):
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe it was working till I intentionally introduced some errors.
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe
> it was working till I intentionally introduced some errors.
[**Tim Abbott**](https://github.com/charliermarsh/ruff/issues/465#issuecomment-1317400028), lead
developer of [Zulip](https://github.com/zulip/zulip):
@@ -135,15 +137,15 @@ ruff check path/to/code/to/file.py # Lint `file.py`
Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.267'
rev: v0.0.271
hooks:
- id: ruff
```
Ruff can also be used as a [VS Code extension](https://github.com/charliermarsh/ruff-vscode) or
alongside any other editor through the [Ruff LSP](https://github.com/charliermarsh/ruff-lsp).
Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or
alongside any other editor through the [Ruff LSP](https://github.com/astral-sh/ruff-lsp).
Ruff can also be used as a [GitHub Action](https://github.com/features/actions) via
[`ruff-action`](https://github.com/chartboost/ruff-action):
@@ -242,6 +244,8 @@ stylistic rules made obsolete by the use of an autoformatter, like
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
<!-- End section: Rules -->
Beyond the defaults, Ruff re-implements some of the most popular Flake8 plugins and related code
quality tools, including:
@@ -291,12 +295,11 @@ quality tools, including:
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
- [pylint-airflow](https://pypi.org/project/pylint-airflow/)
- [pyupgrade](https://pypi.org/project/pyupgrade/)
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)
<!-- End section: Rules -->
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
## Contributing
@@ -352,7 +355,9 @@ Ruff is used by a number of major open-source projects and companies, including:
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Gradio](https://github.com/gradio-app/gradio)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- Hugging Face ([Transformers](https://github.com/huggingface/transformers), [Datasets](https://github.com/huggingface/datasets), [Diffusers](https://github.com/huggingface/diffusers))
- Hugging Face ([Transformers](https://github.com/huggingface/transformers),
[Datasets](https://github.com/huggingface/datasets),
[Diffusers](https://github.com/huggingface/diffusers))
- [Hatch](https://github.com/pypa/hatch)
- [Home Assistant](https://github.com/home-assistant/core)
- [Ibis](https://github.com/ibis-project/ibis)
@@ -364,7 +369,9 @@ Ruff is used by a number of major open-source projects and companies, including:
- Modern Treasury ([Python SDK](https://github.com/Modern-Treasury/modern-treasury-python-sdk))
- Mozilla ([Firefox](https://github.com/mozilla/gecko-dev))
- [MegaLinter](https://github.com/oxsecurity/megalinter)
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel), [ONNX Runtime](https://github.com/microsoft/onnxruntime), [LightGBM](https://github.com/microsoft/LightGBM))
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel),
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
[LightGBM](https://github.com/microsoft/LightGBM))
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
- [Neon](https://github.com/neondatabase/neon)
- [ONNX](https://github.com/onnx/onnx)
@@ -388,7 +395,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [SciPy](https://github.com/scipy/scipy)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
- [Starlite](https://github.com/starlite-api/starlite)
- [Litestar](https://litestar.dev/)
- [The Algorithms](https://github.com/TheAlgorithms/Python)
- [Vega-Altair](https://github.com/altair-viz/altair)
- WordPress ([Openverse](https://github.com/WordPress/openverse))

View File

@@ -1,5 +1,5 @@
[files]
extend-exclude = ["snapshots", "black"]
extend-exclude = ["resources", "snapshots"]
[default.extend-words]
trivias = "trivias"

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.267"
version = "0.0.271"
edition = { workspace = true }
rust-version = { workspace = true }

View File

@@ -26,7 +26,7 @@ requires-python = ">=3.7"
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
[build-system]
requires = ["maturin>=0.15.2,<0.16"]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[tool.maturin]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.267"
version = "0.0.271"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
@@ -17,11 +17,13 @@ name = "ruff"
ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
ruff_macros = { path = "../ruff_macros" }
ruff_newlines = { path = "../ruff_newlines" }
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
ruff_python_semantic = { path = "../ruff_python_semantic" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_rustpython = { path = "../ruff_rustpython" }
ruff_text_size = { workspace = true }
ruff_textwrap = { path = "../ruff_textwrap" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
@@ -41,8 +43,8 @@ libcst = { workspace = true }
log = { workspace = true }
natord = { version = "1.0.9" }
nohash-hasher = { workspace = true }
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
num-bigint = { workspace = true }
num-traits = { workspace = true }
once_cell = { workspace = true }
path-absolutize = { workspace = true, features = [
"once_cell_cache",
@@ -50,6 +52,7 @@ path-absolutize = { workspace = true, features = [
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.1", features = ["serde"] }
pyproject-toml = { version = "0.6.0" }
quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
@@ -65,11 +68,11 @@ shellexpand = { workspace = true }
smallvec = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
textwrap = { workspace = true }
thiserror = { version = "1.0.38" }
toml = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = { version = "0.1.10" }
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
[dev-dependencies]
insta = { workspace = true }
@@ -82,4 +85,3 @@ colored = { workspace = true, features = ["no-color"] }
default = []
schemars = ["dep:schemars"]
jupyter_notebook = []
ecosystem_ci = []

View File

@@ -0,0 +1,16 @@
from airflow.operators import PythonOperator
def my_callable():
pass
my_task = PythonOperator(task_id="my_task", callable=my_callable)
my_task_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
incorrect_name = PythonOperator(task_id="my_task")
incorrect_name_2 = PythonOperator(callable=my_callable, task_id="my_task_2")
from my_module import MyClass
incorrect_name = MyClass(task_id="my_task")

View File

@@ -0,0 +1,3 @@
import paramiko
paramiko.exec_command('something; really; unsafe')

View File

@@ -0,0 +1,8 @@
import os
import subprocess
os.popen("chmod +w foo*")
subprocess.Popen("/bin/chown root: *", shell=True)
subprocess.Popen(["/usr/local/bin/rsync", "*", "some_where:"], shell=True)
subprocess.Popen("/usr/local/bin/rsync * no_injection_here:")
os.system("tar cf foo.tar bar/*")

View File

@@ -57,12 +57,16 @@ dict.fromkeys(("world",), True)
{}.deploy(True, False)
getattr(someobj, attrname, False)
mylist.index(True)
bool(False)
int(True)
str(int(False))
cfg.get("hello", True)
cfg.getint("hello", True)
cfg.getfloat("hello", True)
cfg.getboolean("hello", True)
os.set_blocking(0, False)
g_action.set_enabled(True)
settings.set_enable_developer_extras(True)
class Registry:
@@ -80,3 +84,6 @@ class Registry:
# FBT001: Boolean positional arg in function definition
def foo(self, value: bool) -> None:
pass
def foo(self) -> None:
object.__setattr__(self, "flag", True)

View File

@@ -1,6 +1,7 @@
import collections
import datetime as dt
from decimal import Decimal
from fractions import Fraction
import logging
import operator
from pathlib import Path
@@ -158,12 +159,37 @@ def float_infinity_literal(value=float("1e999")):
pass
# But don't allow standard floats
def float_int_is_wrong(value=float(3)):
# Allow standard floats
def float_int_okay(value=float(3)):
pass
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
def float_str_not_inf_or_nan_okay(value=float("3.14")):
pass
# Allow immutable str() value
def str_okay(value=str("foo")):
pass
# Allow immutable bool() value
def bool_okay(value=bool("bar")):
pass
# Allow immutable int() value
def int_okay(value=int("12")):
pass
# Allow immutable complex() value
def complex_okay(value=complex(1,2)):
pass
# Allow immutable Fraction() value
def fraction_okay(value=Fraction(1,2)):
pass

View File

@@ -73,7 +73,18 @@ def f():
def f():
# Fixable.
# Unfixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
else:
bar = 1
print(bar)
def f():
# Unfixable (false negative) due to usage of `bar` outside of loop.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
@@ -85,4 +96,4 @@ def f():
# Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable
# variable name).
for line_ in range(self.header_lines):
fp.readline()
fp.readline()

View File

@@ -120,3 +120,11 @@ class AbstractClass(ABC):
@abstractmethod
def empty_1(self, foo: Union[str, int, list, float]):
...
from dataclasses import dataclass
@dataclass
class Foo(ABC): # noqa: B024
...

View File

@@ -0,0 +1,11 @@
###
# Errors.
###
incorrect_set = {"value1", 23, 5, "value1"}
incorrect_set = {1, 1}
###
# Non-errors.
###
correct_set = {"value1", 23, 5}
correct_set = {5, "5"}

View File

@@ -9,6 +9,10 @@ def f_a_short():
raise RuntimeError("Error")
def f_a_empty():
raise RuntimeError("")
def f_b():
example = "example"
raise RuntimeError(f"This is an {example} exception")

View File

@@ -0,0 +1,8 @@
# TODO: todo
# todo: todo
# XXX: xxx
# xxx: xxx
# HACK: hack
# hack: hack
# FIXME: fixme
# fixme: fixme

View File

@@ -0,0 +1,65 @@
class OneAttributeClass:
value: int
...
class OneAttributeClass2:
...
value: int
class TwoEllipsesClass:
...
...
class DocstringClass:
"""
My body only contains an ellipsis.
"""
...
class NonEmptyChild(Exception):
value: int
...
class NonEmptyChild2(Exception):
...
value: int
class NonEmptyWithInit:
value: int
...
def __init__():
pass
class EmptyClass:
...
class EmptyEllipsis:
...
class Dog:
eyes: int = 2
class WithInit:
value: int = 0
def __init__():
...
def function():
...
...

View File

@@ -0,0 +1,56 @@
# Violations of PYI013
class OneAttributeClass:
value: int
... # Error
class OneAttributeClass2:
... # Error
value: int
class MyClass:
...
value: int
class TwoEllipsesClass:
...
... # Error
class DocstringClass:
"""
My body only contains an ellipsis.
"""
... # Error
class NonEmptyChild(Exception):
value: int
... # Error
class NonEmptyChild2(Exception):
... # Error
value: int
class NonEmptyWithInit:
value: int
... # Error
def __init__():
pass
# Not violations
class EmptyClass: ...
class EmptyEllipsis: ...
class Dog:
eyes: int = 2
class WithInit:
value: int = 0
def __init__(): ...
def function(): ...
...

View File

@@ -0,0 +1,9 @@
import collections
person: collections.namedtuple # OK
from collections import namedtuple
person: namedtuple # OK
person = namedtuple("Person", ["name", "age"]) # OK

View File

@@ -0,0 +1,11 @@
import collections
person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
from collections import namedtuple
person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
person = namedtuple(
"Person", ["name", "age"]
) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"

View File

@@ -0,0 +1,19 @@
from collections.abc import Set as AbstractSet # Ok
from collections.abc import Set # Ok
from collections.abc import (
Container,
Sized,
Set, # Ok
ValuesView
)
from collections.abc import (
Container,
Sized,
Set as AbstractSet, # Ok
ValuesView
)

View File

@@ -0,0 +1,19 @@
from collections.abc import Set as AbstractSet # Ok
from collections.abc import Set # PYI025
from collections.abc import (
Container,
Sized,
Set, # PYI025
ValuesView
)
from collections.abc import (
Container,
Sized,
Set as AbstractSet,
ValuesView # Ok
)

View File

@@ -0,0 +1,57 @@
import builtins
from abc import abstractmethod
def __repr__(self) -> str:
...
def __str__(self) -> builtins.str:
...
def __repr__(self, /, foo) -> str:
...
def __repr__(self, *, foo) -> str:
...
class ShouldRemoveSingle:
def __str__(self) -> builtins.str:
...
class ShouldRemove:
def __repr__(self) -> str:
...
def __str__(self) -> builtins.str:
...
class NoReturnSpecified:
def __str__(self):
...
def __repr__(self):
...
class NonMatchingArgs:
def __str__(self, *, extra) -> builtins.str:
...
def __repr__(self, /, extra) -> str:
...
class MatchingArgsButAbstract:
@abstractmethod
def __str__(self) -> builtins.str:
...
@abstractmethod
def __repr__(self) -> str:
...

View File

@@ -0,0 +1,28 @@
import builtins
from abc import abstractmethod
def __repr__(self) -> str: ...
def __str__(self) -> builtins.str: ...
def __repr__(self, /, foo) -> str: ...
def __repr__(self, *, foo) -> str: ...
class ShouldRemoveSingle:
def __str__(self) -> builtins.str: ... # Error: PYI029
class ShouldRemove:
def __repr__(self) -> str: ... # Error: PYI029
def __str__(self) -> builtins.str: ... # Error: PYI029
class NoReturnSpecified:
def __str__(self): ...
def __repr__(self): ...
class NonMatchingArgs:
def __str__(self, *, extra) -> builtins.str: ...
def __repr__(self, /, extra) -> str: ...
class MatchingArgsButAbstract:
@abstractmethod
def __str__(self) -> builtins.str: ...
@abstractmethod
def __repr__(self) -> str: ...

View File

@@ -0,0 +1,24 @@
from typing import Any
import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Fine because not a stub file
def __ne__(self, other: typing.Any) -> typing.Any: ... # Fine because not a stub file
class Good:
def __eq__(self, other: object) -> bool: ...
def __ne__(self, obj: object) -> int: ...
class WeirdButFine:
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
def __ne__(self, *, kw_only_other: Any) -> bool: ...
class Unannotated:
def __eq__(self) -> Any: ...
def __ne__(self) -> bool: ...

View File

@@ -0,0 +1,24 @@
from typing import Any
import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Y032
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
class Good:
def __eq__(self, other: object) -> bool: ...
def __ne__(self, obj: object) -> int: ...
class WeirdButFine:
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
def __ne__(self, *, kw_only_other: Any) -> bool: ...
class Unannotated:
def __eq__(self) -> Any: ...
def __ne__(self) -> bool: ...

View File

@@ -0,0 +1,280 @@
# flags: --extend-ignore=Y023
import abc
import builtins
import collections.abc
import typing
from abc import abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from typing import Any, overload
import typing_extensions
from _typeshed import Self
from typing_extensions import final
class Bad(
object
): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
def __repr__(self) -> str:
... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __str__(self) -> builtins.str:
... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __eq__(self, other: Any) -> bool:
... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
def __ne__(self, other: typing.Any) -> typing.Any:
... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
def __enter__(self) -> Bad:
... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
async def __aenter__(self) -> Bad:
... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
def __iadd__(self, other: Bad) -> Bad:
... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
class AlsoBad(int, builtins.object):
... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
class Good:
def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self:
...
@abstractmethod
def __str__(self) -> str:
...
@abc.abstractmethod
def __repr__(self) -> str:
...
def __eq__(self, other: object) -> bool:
...
def __ne__(self, obj: object) -> int:
...
def __enter__(self: Self) -> Self:
...
async def __aenter__(self: Self) -> Self:
...
def __ior__(self: Self, other: Self) -> Self:
...
class Fine:
@overload
def __new__(cls, foo: int) -> FineSubclass:
...
@overload
def __new__(cls, *args: Any, **kwargs: Any) -> Fine:
...
@abc.abstractmethod
def __str__(self) -> str:
...
@abc.abstractmethod
def __repr__(self) -> str:
...
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any:
...
def __ne__(self, *, kw_only_other: Any) -> bool:
...
def __enter__(self) -> None:
...
async def __aenter__(self) -> bool:
...
class FineSubclass(Fine):
...
class StrangeButAcceptable(str):
@typing_extensions.overload
def __new__(cls, foo: int) -> StrangeButAcceptableSubclass:
...
@typing_extensions.overload
def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable:
...
def __str__(self) -> StrangeButAcceptable:
...
def __repr__(self) -> StrangeButAcceptable:
...
class StrangeButAcceptableSubclass(StrangeButAcceptable):
...
class FineAndDandy:
def __str__(self, weird_extra_arg) -> str:
...
def __repr__(self, weird_extra_arg_with_default=...) -> str:
...
@final
class WillNotBeSubclassed:
def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed:
...
def __enter__(self) -> WillNotBeSubclassed:
...
async def __aenter__(self) -> WillNotBeSubclassed:
...
# we don't emit an error for these; out of scope for a linter
class InvalidButPluginDoesNotCrash:
def __new__() -> InvalidButPluginDoesNotCrash:
...
def __enter__() -> InvalidButPluginDoesNotCrash:
...
async def __aenter__() -> InvalidButPluginDoesNotCrash:
...
class BadIterator1(Iterator[int]):
def __iter__(self) -> Iterator[int]:
... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator2(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(self) -> Iterator[int]:
... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator3(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(self) -> collections.abc.Iterator[int]:
... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator4(Iterator[int]):
# Note: *Iterable*, not *Iterator*, returned!
def __iter__(self) -> Iterable[int]:
... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
class IteratorReturningIterable:
def __iter__(self) -> Iterable[str]:
... # Y045 "__iter__" methods should return an Iterator, not an Iterable
class BadAsyncIterator(collections.abc.AsyncIterator[str]):
def __aiter__(self) -> typing.AsyncIterator[str]:
... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
class AsyncIteratorReturningAsyncIterable:
def __aiter__(self) -> AsyncIterable[str]:
... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
class Abstract(Iterator[str]):
@abstractmethod
def __iter__(self) -> Iterator[str]:
...
@abstractmethod
def __enter__(self) -> Abstract:
...
@abstractmethod
async def __aenter__(self) -> Abstract:
...
class GoodIterator(Iterator[str]):
def __iter__(self: Self) -> Self:
...
class GoodAsyncIterator(AsyncIterator[int]):
def __aiter__(self: Self) -> Self:
...
class DoesNotInheritFromIterator:
def __iter__(self) -> DoesNotInheritFromIterator:
...
class Unannotated:
def __new__(cls, *args, **kwargs):
...
def __iter__(self):
...
def __aiter__(self):
...
async def __aenter__(self):
...
def __repr__(self):
...
def __str__(self):
...
def __eq__(self):
...
def __ne__(self):
...
def __iadd__(self):
...
def __ior__(self):
...
def __repr__(self) -> str:
...
def __str__(self) -> str:
...
def __eq__(self, other: Any) -> bool:
...
def __ne__(self, other: Any) -> bool:
...
def __imul__(self, other: Any) -> list[str]:
...

View File

@@ -0,0 +1,188 @@
# flags: --extend-ignore=Y023
import abc
import builtins
import collections.abc
import typing
from abc import abstractmethod
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from typing import Any, overload
import typing_extensions
from _typeshed import Self
from typing_extensions import final
class Bad(
object
): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
def __new__(
cls, *args: Any, **kwargs: Any
) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
def __repr__(
self,
) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __str__(
self,
) -> builtins.str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
def __eq__(
self, other: Any
) -> bool: ... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods
def __ne__(
self, other: typing.Any
) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
def __enter__(
self,
) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
async def __aenter__(
self,
) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
def __iadd__(
self, other: Bad
) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
class AlsoBad(
int, builtins.object
): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
class Good:
def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ...
@abstractmethod
def __str__(self) -> str: ...
@abc.abstractmethod
def __repr__(self) -> str: ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, obj: object) -> int: ...
def __enter__(self: Self) -> Self: ...
async def __aenter__(self: Self) -> Self: ...
def __ior__(self: Self, other: Self) -> Self: ...
class Fine:
@overload
def __new__(cls, foo: int) -> FineSubclass: ...
@overload
def __new__(cls, *args: Any, **kwargs: Any) -> Fine: ...
@abc.abstractmethod
def __str__(self) -> str: ...
@abc.abstractmethod
def __repr__(self) -> str: ...
def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ...
def __ne__(self, *, kw_only_other: Any) -> bool: ...
def __enter__(self) -> None: ...
async def __aenter__(self) -> bool: ...
class FineSubclass(Fine): ...
class StrangeButAcceptable(str):
@typing_extensions.overload
def __new__(cls, foo: int) -> StrangeButAcceptableSubclass: ...
@typing_extensions.overload
def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable: ...
def __str__(self) -> StrangeButAcceptable: ...
def __repr__(self) -> StrangeButAcceptable: ...
class StrangeButAcceptableSubclass(StrangeButAcceptable): ...
class FineAndDandy:
def __str__(self, weird_extra_arg) -> str: ...
def __repr__(self, weird_extra_arg_with_default=...) -> str: ...
@final
class WillNotBeSubclassed:
def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed: ...
def __enter__(self) -> WillNotBeSubclassed: ...
async def __aenter__(self) -> WillNotBeSubclassed: ...
# we don't emit an error for these; out of scope for a linter
class InvalidButPluginDoesNotCrash:
def __new__() -> InvalidButPluginDoesNotCrash: ...
def __enter__() -> InvalidButPluginDoesNotCrash: ...
async def __aenter__() -> InvalidButPluginDoesNotCrash: ...
class BadIterator1(Iterator[int]):
def __iter__(
self,
) -> Iterator[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator2(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(
self,
) -> Iterator[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator3(
typing.Iterator[int]
): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
def __iter__(
self,
) -> collections.abc.Iterator[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
class BadIterator4(Iterator[int]):
# Note: *Iterable*, not *Iterator*, returned!
def __iter__(
self,
) -> Iterable[
int
]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
class IteratorReturningIterable:
def __iter__(
self,
) -> Iterable[
str
]: ... # Y045 "__iter__" methods should return an Iterator, not an Iterable
class BadAsyncIterator(collections.abc.AsyncIterator[str]):
def __aiter__(
self,
) -> typing.AsyncIterator[
str
]: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
class AsyncIteratorReturningAsyncIterable:
def __aiter__(
self,
) -> AsyncIterable[
str
]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
class Abstract(Iterator[str]):
@abstractmethod
def __iter__(self) -> Iterator[str]: ...
@abstractmethod
def __enter__(self) -> Abstract: ...
@abstractmethod
async def __aenter__(self) -> Abstract: ...
class GoodIterator(Iterator[str]):
def __iter__(self: Self) -> Self: ...
class GoodAsyncIterator(AsyncIterator[int]):
def __aiter__(self: Self) -> Self: ...
class DoesNotInheritFromIterator:
def __iter__(self) -> DoesNotInheritFromIterator: ...
class Unannotated:
def __new__(cls, *args, **kwargs): ...
def __iter__(self): ...
def __aiter__(self): ...
async def __aenter__(self): ...
def __repr__(self): ...
def __str__(self): ...
def __eq__(self): ...
def __ne__(self): ...
def __iadd__(self): ...
def __ior__(self): ...
def __repr__(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: Any) -> bool: ...
def __ne__(self, other: Any) -> bool: ...
def __imul__(self, other: Any) -> list[str]: ...

View File

@@ -0,0 +1,15 @@
__all__: list[str]
__all__: list[str] = ["foo"]
class Foo:
__all__: list[str]
__match_args__: tuple[str, ...]
__slots__: tuple[str, ...]
class Bar:
__all__: list[str] = ["foo"]
__match_args__: tuple[str, ...] = (1,)
__slots__: tuple[str, ...] = "foo"

View File

@@ -0,0 +1,13 @@
__all__: list[str] # Error: PYI035
__all__: list[str] = ["foo"]
class Foo:
__all__: list[str]
__match_args__: tuple[str, ...] # Error: PYI035
__slots__: tuple[str, ...] # Error: PYI035
class Bar:
__all__: list[str] = ["foo"]
__match_args__: tuple[str, ...] = (1,)
__slots__: tuple[str, ...] = "foo"

View File

@@ -0,0 +1,85 @@
import collections.abc
import typing
from collections.abc import Iterator, Iterable
class NoReturn:
def __iter__(self):
...
class TypingIterableTReturn:
def __iter__(self) -> typing.Iterable[int]:
...
def not_iter(self) -> typing.Iterable[int]:
...
class TypingIterableReturn:
def __iter__(self) -> typing.Iterable:
...
def not_iter(self) -> typing.Iterable:
...
class CollectionsIterableTReturn:
def __iter__(self) -> collections.abc.Iterable[int]:
...
def not_iter(self) -> collections.abc.Iterable[int]:
...
class CollectionsIterableReturn:
def __iter__(self) -> collections.abc.Iterable:
...
def not_iter(self) -> collections.abc.Iterable:
...
class IterableReturn:
def __iter__(self) -> Iterable:
...
class IteratorReturn:
def __iter__(self) -> Iterator:
...
class IteratorTReturn:
def __iter__(self) -> Iterator[int]:
...
class TypingIteratorReturn:
def __iter__(self) -> typing.Iterator:
...
class TypingIteratorTReturn:
def __iter__(self) -> typing.Iterator[int]:
...
class CollectionsIteratorReturn:
def __iter__(self) -> collections.abc.Iterator:
...
class CollectionsIteratorTReturn:
def __iter__(self) -> collections.abc.Iterator[int]:
...
class TypingAsyncIterableTReturn:
def __aiter__(self) -> typing.AsyncIterable[int]:
...
class TypingAsyncIterableReturn:
def __aiter__(self) -> typing.AsyncIterable:
...

View File

@@ -0,0 +1,49 @@
import collections.abc
import typing
from collections.abc import Iterator, Iterable
class NoReturn:
def __iter__(self): ...
class TypingIterableTReturn:
def __iter__(self) -> typing.Iterable[int]: ... # Error: PYI045
def not_iter(self) -> typing.Iterable[int]: ...
class TypingIterableReturn:
def __iter__(self) -> typing.Iterable: ... # Error: PYI045
def not_iter(self) -> typing.Iterable: ...
class CollectionsIterableTReturn:
def __iter__(self) -> collections.abc.Iterable[int]: ... # Error: PYI045
def not_iter(self) -> collections.abc.Iterable[int]: ...
class CollectionsIterableReturn:
def __iter__(self) -> collections.abc.Iterable: ... # Error: PYI045
def not_iter(self) -> collections.abc.Iterable: ...
class IterableReturn:
def __iter__(self) -> Iterable: ... # Error: PYI045
class IteratorReturn:
def __iter__(self) -> Iterator: ...
class IteratorTReturn:
def __iter__(self) -> Iterator[int]: ...
class TypingIteratorReturn:
def __iter__(self) -> typing.Iterator: ...
class TypingIteratorTReturn:
def __iter__(self) -> typing.Iterator[int]: ...
class CollectionsIteratorReturn:
def __iter__(self) -> collections.abc.Iterator: ...
class CollectionsIteratorTReturn:
def __iter__(self) -> collections.abc.Iterator[int]: ...
class TypingAsyncIterableTReturn:
def __aiter__(self) -> typing.AsyncIterable[int]: ... # Error: PYI045
class TypingAsyncIterableReturn:
def __aiter__(self) -> typing.AsyncIterable: ... # Error: PYI045

View File

@@ -0,0 +1,19 @@
def bar(): # OK
...
def oof(): # OK, docstrings are handled by another rule
"""oof"""
print("foo")
def foo(): # Ok not in Stub file
"""foo"""
print("foo")
print("foo")
def buzz(): # Ok not in Stub file
print("fizz")
print("buzz")
print("test")

View File

@@ -0,0 +1,20 @@
def bar():
... # OK
def oof(): # OK, docstrings are handled by another rule
"""oof"""
print("foo")
def foo(): # ERROR PYI048
"""foo"""
print("foo")
print("foo")
def buzz(): # ERROR PYI048
print("fizz")
print("buzz")
print("test")

View File

@@ -0,0 +1,38 @@
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None:
...
def f2(x: str = "51 character stringgggggggggggggggggggggggggggggggg") -> None:
...
def f3(x: str = "50 character stringggggggggggggggggggggggggggggg\U0001f600") -> None:
...
def f4(x: str = "51 character stringgggggggggggggggggggggggggggggg\U0001f600") -> None:
...
def f5(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg") -> None:
...
def f6(x: bytes = b"51 character byte stringgggggggggggggggggggggggggg") -> None:
...
def f7(x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff") -> None:
...
def f8(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg\xff") -> None:
...
foo: str = "50 character stringggggggggggggggggggggggggggggggg"
bar: str = "51 character stringgggggggggggggggggggggggggggggggg"
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg"
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff"

View File

@@ -0,0 +1,30 @@
def f1(x: str = "50 character stringggggggggggggggggggggggggggggggg") -> None: ... # OK
def f2(
x: str = "51 character stringgggggggggggggggggggggggggggggggg", # Error: PYI053
) -> None: ...
def f3(
x: str = "50 character stringgggggggggggggggggggggggggggggg\U0001f600", # OK
) -> None: ...
def f4(
x: str = "51 character stringggggggggggggggggggggggggggggggg\U0001f600", # Error: PYI053
) -> None: ...
def f5(
x: bytes = b"50 character byte stringgggggggggggggggggggggggggg", # OK
) -> None: ...
def f6(
x: bytes = b"51 character byte stringgggggggggggggggggggggggggg", # Error: PYI053
) -> None: ...
def f7(
x: bytes = b"50 character byte stringggggggggggggggggggggggggg\xff", # OK
) -> None: ...
def f8(
x: bytes = b"51 character byte stringgggggggggggggggggggggggggg\xff", # Error: PYI053
) -> None: ...
foo: str = "50 character stringggggggggggggggggggggggggggggggg" # OK
bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053

View File

@@ -0,0 +1,20 @@
field01: int = 0xFFFFFFFF
field02: int = 0xFFFFFFFFF
field03: int = -0xFFFFFFFF
field04: int = -0xFFFFFFFFF
field05: int = 1234567890
field06: int = 12_456_890
field07: int = 12345678901
field08: int = -1234567801
field09: int = -234_567_890
field10: float = 123.456789
field11: float = 123.4567890
field12: float = -123.456789
field13: float = -123.567_890
field14: complex = 1e1234567j
field15: complex = 1e12345678j
field16: complex = -1e1234567j
field17: complex = 1e123456789j

View File

@@ -0,0 +1,20 @@
field01: int = 0xFFFFFFFF
field02: int = 0xFFFFFFFFF # Error: PYI054
field03: int = -0xFFFFFFFF
field04: int = -0xFFFFFFFFF # Error: PYI054
field05: int = 1234567890
field06: int = 12_456_890
field07: int = 12345678901 # Error: PYI054
field08: int = -1234567801
field09: int = -234_567_890 # Error: PYI054
field10: float = 123.456789
field11: float = 123.4567890 # Error: PYI054
field12: float = -123.456789
field13: float = -123.567_890 # Error: PYI054
field14: complex = 1e1234567j
field15: complex = 1e12345678j # Error: PYI054
field16: complex = -1e1234567j
field17: complex = 1e123456789j # Error: PYI054

View File

@@ -272,3 +272,34 @@ def str_to_bool(val):
if isinstance(val, bool):
return some_obj
return val
# Mixed assignments
def function_assignment(x):
def f(): ...
return f
def class_assignment(x):
class Foo: ...
return Foo
def mixed_function_assignment(x):
if x:
def f(): ...
else:
f = 42
return f
def mixed_class_assignment(x):
if x:
class Foo: ...
else:
Foo = 42
return Foo

View File

@@ -86,9 +86,16 @@ while x > 0:
):
print("Bad module!")
# SIM102
if node.module:
if node.module == "multiprocessing" or node.module.startswith(
# SIM102 (auto-fixable)
if node.module012345678:
if node.module == "multiprocß9💣2" or node.module.startswith(
"multiprocessing."
):
print("Bad module!")
# SIM102 (not auto-fixable)
if node.module0123456789:
if node.module == "multiprocß9💣2" or node.module.startswith(
"multiprocessing."
):
print("Bad module!")

View File

@@ -80,17 +80,25 @@ else:
# SIM108
if a:
b = cccccccccccccccccccccccccccccccccccc
b = "cccccccccccccccccccccccccccccccccß"
else:
b = ddddddddddddddddddddddddddddddddddddd
b = "ddddddddddddddddddddddddddddddddd💣"
# OK (too long)
if True:
if a:
b = cccccccccccccccccccccccccccccccccccc
b = ccccccccccccccccccccccccccccccccccc
else:
b = ddddddddddddddddddddddddddddddddddddd
b = ddddddddddddddddddddddddddddddddddd
# OK (too long with tabs)
if True:
if a:
b = ccccccccccccccccccccccccccccccccccc
else:
b = ddddddddddddddddddddddddddddddddddd
# SIM108 (without fix due to trailing comment)

View File

@@ -155,3 +155,19 @@ def f():
if check(x):
return False
return True
def f():
# SIM110
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2":
if x.isdigit():
return True
return False
def f():
# OK (too long)
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29":
if x.isdigit():
return True
return False

View File

@@ -171,3 +171,19 @@ def f():
if x > y:
return False
return True
def f():
# SIM111
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9":
if x.isdigit():
return False
return True
def f():
# OK (too long)
for x in "012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß90":
if x.isdigit():
return False
return True

View File

@@ -90,3 +90,13 @@ with (
D() as d,
):
print("hello")
# SIM117 (auto-fixable)
with A("01ß9💣28901ß9💣28901ß9💣289") as a:
with B("01ß9💣28901ß9💣28901ß9💣289") as b:
print("hello")
# SIM117 (not auto-fixable too long)
with A("01ß9💣28901ß9💣28901ß9💣2890") as a:
with B("01ß9💣28901ß9💣28901ß9💣289") as b:
print("hello")

View File

@@ -14,7 +14,7 @@ if key not in a_dict:
else:
var = a_dict[key]
# SIM401 (default with a complex expression)
# OK (default contains effect)
if key in a_dict:
var = a_dict[key]
else:
@@ -36,12 +36,18 @@ else:
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "default"
vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789"
###
# Negative cases
###
# OK (too long)
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789ß"
# OK (false negative)
if not key in a_dict:
var = "default"

View File

@@ -6,3 +6,4 @@
# T001 - errors
# XXX (evanrittenhouse): this is not fine
# FIXME (evanrittenhouse): this is not fine
# foo # XXX: this isn't fine either

View File

@@ -5,3 +5,4 @@
# TODO: this has no author
# FIXME: neither does this
# TODO : and neither does this
# foo # TODO: this doesn't either

View File

@@ -26,4 +26,6 @@ def foo(x):
# TODO: followed by a new TODO with an issue link
# TDO-3870
# foo # TODO: no link!
# TODO: here's a TODO on the last line with no link

View File

@@ -4,3 +4,5 @@
# TODO this has no colon
# TODO(evanrittenhouse 😀) this has no colon
# FIXME add a colon
# foo # TODO add a colon
# TODO this has a colon but it doesn't terminate the tag, so this should throw. https://www.google.com

View File

@@ -4,3 +4,4 @@
# TODO(evanrittenhouse):
# TODO(evanrittenhouse)
# FIXME
# foo # TODO

View File

@@ -3,3 +3,4 @@
# TDO006 - error
# ToDo (evanrittenhouse): invalid capitalization
# todo (evanrittenhouse): another invalid capitalization
# foo # todo: invalid capitalization

View File

@@ -6,3 +6,5 @@
# TODO (evanrittenhouse):this doesn't either
# TODO:neither does this
# FIXME:and lastly neither does this
# foo # TODO:this is really the last one
# TODO this colon doesn't terminate the tag, so don't check it. https://www.google.com

View File

@@ -146,3 +146,21 @@ def f():
import pandas as pd
x = dict[pd.DataFrame, pd.DataFrame]
def f():
import pandas as pd
def f():
from pandas import DataFrame # noqa: TCH002
x: DataFrame = 2
def f():
from pandas import ( # noqa: TCH002
DataFrame,
)
x: DataFrame = 2

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
def f():
# Even in strict mode, this shouldn't rase an error, since `pkg` is used at runtime,
# Even in strict mode, this shouldn't raise an error, since `pkg` is used at runtime,
# and implicitly imports `pkg.bar`.
import pkg
import pkg.bar
@@ -12,7 +12,7 @@ def f():
def f():
# Even in strict mode, this shouldn't rase an error, since `pkg.bar` is used at
# Even in strict mode, this shouldn't raise an error, since `pkg.bar` is used at
# runtime, and implicitly imports `pkg`.
import pkg
import pkg.bar
@@ -22,7 +22,7 @@ def f():
def f():
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
import pkg
from pkg import A
@@ -31,7 +31,7 @@ def f():
def f():
# In un-strict mode, this shouldn't rase an error, since `pkg` is used at runtime.
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
from pkg import A, B
def test(value: A):
@@ -39,7 +39,7 @@ def f():
def f():
# Even in strict mode, this shouldn't rase an error, since `pkg.baz` is used at
# Even in strict mode, this shouldn't raise an error, since `pkg.baz` is used at
# runtime, and implicitly imports `pkg.bar`.
import pkg.bar
import pkg.baz
@@ -49,9 +49,56 @@ def f():
def f():
# In un-strict mode, this _should_ rase an error, since `pkg` is used at runtime.
# In un-strict mode, this _should_ raise an error, since `pkg.bar` isn't used at runtime
import pkg
from pkg.bar import A
def test(value: A):
return pkg.B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg.bar` is used at runtime.
import pkg
import pkg.bar as B
def test(value: pkg.A):
return B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg.foo.bar` is used at runtime.
import pkg.foo as F
import pkg.foo.bar as B
def test(value: F.Foo):
return B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg.foo.bar` is used at runtime.
import pkg
import pkg.foo.bar as B
def test(value: pkg.A):
return B()
def f():
# In un-strict mode, this _should_ raise an error, since `pkg` isn't used at runtime.
# Note that `pkg` is a prefix of `pkgfoo` which are both different modules. This is
# testing the implementation.
import pkg
import pkgfoo.bar as B
def test(value: pkg.A):
return B()
def f():
# In un-strict mode, this shouldn't raise an error, since `pkg` is used at runtime.
import pkg.bar as B
import pkg.foo as F
def test(value: F.Foo):
return B.Bar()

View File

@@ -2,3 +2,7 @@ import a
# Don't take this comment into account when determining whether the next import can fit on one line.
from b import c
from d import e # Do take this comment into account when determining whether the next import can fit on one line.
# The next import fits on one line.
from f import g # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣2
# The next import doesn't fit on one line.
from h import i # 012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29012ß9💣29

View File

@@ -0,0 +1,4 @@
# isort: off
x = 1
# isort: on

View File

@@ -6,7 +6,16 @@ import f
import c
import d
# isort: split
# isort: split
import a
import b
if True:
import C
import A
# isort: split
import D
import B

View File

@@ -19,7 +19,7 @@ if x > 0:
else:
import e
y = x + 1
__some__magic = 1
import f

View File

@@ -0,0 +1,11 @@
a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
a = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
b = """ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
c = """24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""
d = """💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A67ß9💣24A6"""

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""Here's a top-level ß9💣2ing that's over theß9💣2."""
def f1():
"""Here's a ß9💣2ing that's also over theß9💣2."""
x = 1 # Here's a comment that's over theß9💣2, but it's not standalone.
# Here's a standalone comment that's over theß9💣2.
x = 2
# Another standalone that is preceded by a newline and indent toke and is over theß9💣2.
print("Here's a string that's over theß9💣2, but it's not a ß9💣2ing.")
"This is also considered a ß9💣2ing, and is over theß9💣2."
def f2():
"""Here's a multi-line ß9💣2ing.
It's over theß9💣2 on this line, which isn't the first line in the ß9💣2ing.
"""
def f3():
"""Here's a multi-line ß9💣2ing.
It's over theß9💣2 on this line, which isn't the first line in the ß9💣2ing."""

View File

@@ -0,0 +1,10 @@
"""Test: module bindings are preferred over local bindings, for deferred annotations."""
from __future__ import annotations
import datetime
from typing import Optional
class Class:
datetime: Optional[datetime.datetime]

View File

@@ -0,0 +1,12 @@
"""Test: module bindings are preferred over local bindings, for deferred annotations."""
from __future__ import annotations
from typing import TypeAlias, List
class Class:
List: TypeAlias = List
def bar(self) -> List:
pass

View File

@@ -0,0 +1,8 @@
"""Test: module bindings are preferred over local bindings, for deferred annotations."""
import datetime
from typing import Optional
class Class:
datetime: "Optional[datetime.datetime]"

View File

@@ -0,0 +1,9 @@
from typing import TYPE_CHECKING
from django.db.models import ForeignKey
if TYPE_CHECKING:
from pathlib import Path
class Foo:
var = ForeignKey["Path"]()

View File

@@ -0,0 +1,15 @@
"""Test that `__all__` exports are respected even with multiple declarations."""
import random
def some_dependency_check():
return random.uniform(0.0, 1.0) > 0.49999
if some_dependency_check():
import math
__all__ = ["math"]
else:
__all__ = []

View File

@@ -2,3 +2,6 @@
"{bar}{}".format(1, bar=2, spam=3) # F522
"{bar:{spam}}".format(bar=2, spam=3) # No issues
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522
# Not fixable
(''
.format(x=2))

View File

@@ -17,3 +17,17 @@
"{0}{1}".format(1, *args) # No issues
"{0}{1}".format(1, 2, *args) # No issues
"{0}{1}".format(1, 2, 3, *args) # F523
# With nested quotes
"''1{0}".format(1, 2, 3) # F523
"\"\"{1}{0}".format(1, 2, 3) # F523
'""{1}{0}'.format(1, 2, 3) # F523
# With modified indexes
"{1}{2}".format(1, 2, 3) # F523, # F524
"{1}{3}".format(1, 2, 3, 4) # F523, # F524
"{1} {8}".format(0, 1) # F523, # F524
# Not fixable
(''
.format(2))

View File

@@ -4,3 +4,4 @@
"{0} {bar}".format(1) # F524
"{0} {bar}".format() # F524
"{bar} {0}".format() # F524
"{1} {8}".format(0, 1)

View File

@@ -0,0 +1,4 @@
"""Test that shadowing an explicit re-export produces a warning."""
import foo as foo
import bar as foo

View File

@@ -0,0 +1,5 @@
"""Test that shadowing a `__future__` import does not produce a warning."""
from __future__ import annotations
import annotations

View File

@@ -83,6 +83,11 @@ def f():
pass
def f():
with (Nested(m)) as (cm):
pass
def f():
toplevel = tt = lexer.get_token()
if not tt:

View File

@@ -0,0 +1,28 @@
class Str:
def __str__(self):
return 1
class Float:
def __str__(self):
return 3.05
class Int:
def __str__(self):
return 0
class Bool:
def __str__(self):
return False
class Str2:
def __str__(self):
x = "ruff"
return x
# TODO fixme once Ruff has better type checking
def return_int():
return 3
class ComplexReturn:
def __str__(self):
return return_int()

View File

@@ -0,0 +1,38 @@
# Errors
for item in {"apples", "lemons", "water"}: # flags in-line set literals
print(f"I like {item}.")
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
# Non-errors
items = {"apples", "lemons", "water"}
for item in items: # only complains about in-line sets (as per Pylint)
print(f"I like {item}.")
for item in ["apples", "lemons", "water"]: # lists are fine
print(f"I like {item}.")
for item in ("apples", "lemons", "water"): # tuples are fine
print(f"I like {item}.")
numbers_list = [i for i in [1, 2, 3]] # lists in comprehensions are fine
numbers_set = {i for i in (1, 2, 3)} # tuples in comprehensions are fine
numbers_dict = {str(i): i for i in [1, 2, 3]} # lists in dict comprehensions are fine
numbers_gen = (i for i in (1, 2, 3)) # tuples in generator expressions are fine
for item in set(("apples", "lemons", "water")): # set constructor is fine
print(f"I like {item}.")
for number in {i for i in range(10)}: # set comprehensions are fine
print(number)

View File

@@ -0,0 +1,19 @@
# Errors
(a := 42)
if True:
(b := 1)
class Foo:
(c := 1)
# OK
if a := 42:
print("Success")
a = 0
while (a := a + 1) < 10:
print("Correct")
a = (b := 1)

View File

@@ -25,3 +25,14 @@ min(1, min(a))
min(1, min(i for i in range(10)))
max(1, max(a))
max(1, max(i for i in range(10)))
tuples_list = [
(1, 2),
(2, 3),
(3, 4),
(4, 5),
(5, 6),
]
min(min(tuples_list))
max(max(tuples_list))

View File

@@ -17,3 +17,30 @@ def f():
def f():
nonlocal y
def f():
x = 1
def g():
nonlocal x
del x
def f():
def g():
nonlocal x
del x
def f():
try:
pass
except Exception as x:
pass
def g():
nonlocal x
x = 2

View File

@@ -0,0 +1,8 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from sys import exit as bar
def main():
exit(0)

View File

@@ -0,0 +1,7 @@
async def success():
yield 42
async def fail():
l = (1, 2, 3)
yield from l

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
import typing
if typing.TYPE_CHECKING:
from collections import defaultdict
def f(x: typing.DefaultDict[str, str]) -> None:
...

View File

@@ -0,0 +1,8 @@
import typing
if typing.TYPE_CHECKING:
from collections import defaultdict
def f(x: typing.DefaultDict[str, str]) -> None:
...

View File

@@ -0,0 +1,8 @@
import typing
if typing.TYPE_CHECKING:
from collections import defaultdict
def f(x: "typing.DefaultDict[str, str]") -> None:
...

View File

@@ -15,6 +15,7 @@ bytes("foo", **a)
bytes(b"foo"
b"bar")
bytes("foo")
f"{f'{str()}'}"
# These become string or byte literals
str()

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