Compare commits

..

400 Commits

Author SHA1 Message Date
konsti
e87032d57d Explain check_docs_formatted.py error message (#6125)
## Summary

This is an error message only change to lead an implementor of a new
rule that has an unformatted or invalid bad example to the
right code.

## Test Plan

n/a
2023-07-27 15:53:52 +02:00
Charlie Marsh
0c2abf8316 Avoid walking past root when resolving imports (#6126)
## Summary

Noticed in #5954: we walk _past_ the root rather than stopping _at_ the
root when attempting to traverse along the parent path. It's effectively
an off-by-one bug.
2023-07-27 13:38:51 +00:00
konsti
6e85e0010f Fix windows test warnings (#6124)
See
https://github.com/astral-sh/ruff/actions/runs/5679922286/job/15392998698.
These didn't fail CI because we run clippy on linux only.
2023-07-27 14:03:53 +02:00
Micha Reiser
2ea0a0b28a Respect indent when measuring with MeasureMode::AllLines (#6120) 2023-07-27 13:34:36 +02:00
konsti
10abc8fd92 Unbreak main (#6123)
This fixes main breaking due to two merges.
2023-07-27 11:32:55 +00:00
konsti
61195bc7b1 Don't format trailing comma for lambda arguments (#5946)
**Summary** lambda arguments don't have parentheses, so they shouldn't
get a magic trailing comma either. This fixes some unstable formatting

**Test Plan** Added a regression test.

89 (from previously 145) instances of unstable formatting remaining.

```
$ cargo run --bin ruff_dev --release -- format-dev --stability-check --error-file formatter-ecosystem-errors.txt --multi-project target/checkouts > formatter-ecosystem-progress.txt
$ rg "Unstable formatting" target/formatter-ecosystem-errors.txt | wc -l
89
```

Closes #5892
2023-07-27 12:32:10 +02:00
Micha Reiser
9040554be5 Merge pull request #6119 from astral-sh/preserve-history 2023-07-27 12:12:57 +02:00
Micha Reiser
06ba1cf0ad Move RustPython Files 2023-07-27 11:46:18 +02:00
Micha Reiser
7a63a8f45a Merge branch 'main' of ../parser-move into preserve-history 2023-07-27 11:39:22 +02:00
Micha Reiser
c1bc0dc83d Delete RustPython files 2023-07-27 11:39:14 +02:00
Micha Reiser
90573975f3 Delete more unused files 2023-07-27 11:32:12 +02:00
Micha Reiser
802df97d55 Fix clippy issues 2023-07-27 11:31:10 +02:00
Micha Reiser
6f4ee32807 Move files to common directory 2023-07-27 11:27:37 +02:00
Zanie Blue
7ec979d022 Fix TypeAliasName to store name instead of load (#42)
Copies the fix in https://github.com/RustPython/Parser/pull/99
2023-07-26 17:53:34 -05:00
Micha Reiser
fab9cc5294 Remove parser ast re-export (#41) 2023-07-26 14:54:29 +02:00
Micha Reiser
ff7bab589e Prepare for moving the parser into the Ruff monorepo (#40) 2023-07-26 12:28:43 +02:00
Micha Reiser
593b46be5e perf: Cursor based lexer (#38) 2023-07-26 07:50:45 +02:00
David Szotten
13196fc500 fix the ranges of constants inside f-strings (#33) 2023-07-25 19:17:06 +02:00
Dhruv Manilawala
5ef4ccd632 Add line magic stmt and expr AST nodes (#31)
This PR introduces two new nodes for the parser to recognize the
`MagicCommand` tokens:

* `StmtLineMagic` for statement position i.e., magic commands on their
own line:
	```python
	%matplotlib inline
	!pwd
	```

* `ExprLineMagic` for expression position i.e., magic commands in an
assignment statement:
	```python
	# Only `?` and `!` are valid in this position
	dir = !pwd
	```

Both nodes are identical in their structure as in it contains the magic
kind and the command value as `String`.
2023-07-24 21:20:31 +05:30
Dhruv Manilawala
e363fb860e Lex Jupyter Magic in assignment value position (#30)
Emit `MagicCommand` token when it is the assignment value[^1] i.e., on
the right side of an assignment statement.

Examples:
```python
pwd = !pwd
foo = %timeit a = b
bar = %timeit a % 3
baz = %matplotlib \
        inline"
```

[^1]: Only `%` and `!` are valid in that position, other magic kinds are
not valid
2023-07-24 17:44:03 +05:30
Chris Pryer
4888d800fb Fix BoolOp python.org link (#37)
Noticed https://docs.python.org/3/library/ast.html#ast.boolop wasn't
working.
2023-07-22 14:10:59 -04:00
konsti
4d03b9b5b2 Fix empty lambda args range (#35)
Previously, empty lambda arguments (e.g. `lambda: 1`) would get the
range of the entire expression, which leads to incorrect comment
placement. Now empty lambda arguments get an empty range between the
`lambda` and the `:` tokens.
2023-07-21 17:41:19 +02:00
Zanie Blue
fb365c642b Implement Ranged for TypeParam (#32)
Required for https://github.com/astral-sh/ruff/pull/5927
2023-07-20 20:03:17 -04:00
konsti
db04fd4157 Refactor if statement, introduce nodes for elif and else (#22)
The old if layout couldn't differentiate between an else block with a
single if statement and an elif statement. Additionally we getting rid
of the recursion in favor of a single if struct with a vec of elif/else
children. This is accompanied by a big refactoring in ruff which removes
a bunch of TODOs and false negatives.
2023-07-18 13:14:40 +02:00
konstin
ff3e8ead36 Revert "snapshot"
This reverts commit 1bbec2b967.
2023-07-18 11:09:43 +02:00
konstin
1bbec2b967 snapshot 2023-07-18 11:08:14 +02:00
Dhruv Manilawala
510be51cfa Fix Jupyter Magic test snapshot (#29)
Sorry for the churn, I should've rebased and merged the original PR to
avoid this.
2023-07-17 23:06:53 -05:00
Dhruv Manilawala
3b4c8fffe5 Lex Jupyter line magic with Mode::Jupyter (#23)
Lex Jupyter line magic with `Mode::Jupyter`

This PR adds a new token `MagicCommand`[^1] which the lexer will
recognize when in `Mode::Jupyter`. The rules for the lexer is as
follows:
1. Given that we are at the start of line, skip the indentation and look
for [characters that represent the start of a magic
command](635815e8f1/IPython/core/inputtransformer2.py (L335-L346)),
determine the magic kind and capture all the characters following it as
the command string.
2. If the command extends multiple lines, the lexer will skip the line
continuation character (`\`) but only if it's followed by a newline
(`\n` or `\r`). The reason to skip this only in case of newline is
because they can occur in the command string which we should not skip:

	```rust
    //        Skip this backslash
    //        v
    //   !pwd \
    //      && ls -a | sed 's/^/\\    /'
    //                          ^^
    //                          Don't skip these backslashes
	```

3. The parser, when in `Mode::Jupyter`, will filter these tokens before
the parsing begins. There is a small caveat when the magic command is
indented. In the following example, when the parser filters out magic
command, it'll throw an indentation error:

	```python
	for i in range(5):
		!ls

	# What the parser will see
	for i in range(5):
	
	```

[^1]: I would prefer to have some other name as this not only represent
a line magic (`%`) but also shell command (`!`), help command (`?`) and
others. In original implementation, it's named as ["IPython
Syntax"](635815e8f1/IPython/core/inputtransformer2.py (L332))
2023-07-18 09:24:24 +05:30
Zanie
126652b684 Fix decorator ranges
Incorrectly merged LALRPOP file
2023-07-17 14:49:14 -05:00
Zanie
57e8712d76 Bump expected size of Stmt to 168 bytes 2023-07-17 14:49:14 -05:00
Zanie
78c6ede1c9 Format 2023-07-17 14:49:14 -05:00
Zanie Blue
a843a00f6b Add parsing of type alias statements i.e. the type keyword (#97)
Extends #95
Closes #82

Adds parsing of new `type` soft keyword for defining type aliases.

Supports type alias statements as defined in PEP 695 e.g.

```python
type IntOrStr = int | str

type ListOrSet[T] = list[T] | set[T]

type AnimalOrVegetable = Animal | "Vegetable"

type RecursiveList[T] = T | list[RecursiveList[T]]
```

All type parameter kinds are supported as in #95.

Builds on soft keyword abstractions introduced in https://github.com/RustPython/RustPython/pull/4519
2023-07-17 14:49:14 -05:00
Zanie Blue
f846f1ea42 Parse type parameters in function definitions (#96)
* Parse type parameters in function definitions
* Add test for combined items
2023-07-17 14:49:14 -05:00
Zanie
1d4b7a395f Consolidate tests and add coverage for trailing comma 2023-07-17 14:49:14 -05:00
Zanie
ce3ce0734b Add bound to test case test_parse_class_with_all_possible_generic_types 2023-07-17 14:49:14 -05:00
Zanie
b0e119f049 Add test for tuple bounds 2023-07-17 14:49:14 -05:00
Zanie
1f5e707829 Remove test for empty generic class Foo[]: ...
Not valid syntax
2023-07-17 14:49:14 -05:00
Zanie
ed7acfe477 Parse type parameters in class definitions 2023-07-17 14:49:14 -05:00
Zanie
c31b58eb39 Move type_param stubs into LALRPOP definition 2023-07-17 14:49:14 -05:00
Zanie
c0a3a20c63 Bump size assertion for Stmt from 136 to 160 bytes 2023-07-17 14:49:14 -05:00
Zanie
05ae26b935 Regenerate code with latest ASDL 2023-07-17 14:49:14 -05:00
David Szotten
b996b21ffc tuple constants are for optimisations, not source (#28)
my reading of https://docs.python.org/3/library/ast.html#ast.unparse and
https://discuss.python.org/t/ast-constant-value-tuple-s-and-frozenset-s/22578
is that tuple constants cannot come from parsing python source, they are
only for optimised bytecode

see also https://github.com/astral-sh/ruff/pull/5812
2023-07-17 15:46:37 -04:00
konsti
2d1f69cbb9 Remove asdl (#21)
This removes the ASDL code generation in favor of handwriting the AST. 

The motivations for moving away from the ASDL are:

* CPython compatibility is no longer a goal
* The ASDL grammar isn't as expressive as we would like
* The codegen scripts have a high complexity which makes extensions time
consuming
* We don't make heavy use of code generation (compared to e.g.
RustPython that generates Pyo3 bindings, a fold implementation etc).

We may want to revisit a grammar based code generation in the future,
e.g. by using [ungrammar](https://github.com/rust-analyzer/ungrammar)
2023-07-05 14:25:26 +02:00
konsti
0f2e295f3a impl Ranged for TextRange (#20)
This adds the missing implementation of `Ranged` for `TextRange` itself

```rust
impl Ranged for TextRange {
    fn range(&self) -> TextRange {
        *self
    }
}
```

This allows e.g. using `has_comments` with arbitrary ranges instead of
just a node.

It also adds .venv to the .gitignore
2023-07-02 10:11:06 +02:00
konstin
c174bbf1f2 impl<T> Ranged for &T where T: Ranged (#16)
In the example below, `arg` is `&Expr`, so `&Ranged`, but `entries()`
want a `T: Ranged`. This adds the missing bridge impl.

```rust
        let all_args = format_with(|f| {
            f.join_comma_separated()
                .entries(
                    // We have the parentheses from the call so the arguments never need any
                    args.iter()
                        .map(|arg| (arg, arg.format().with_options(Parenthesize::Never))),
                )
                .nodes(keywords.iter())
                .finish()
        });
```
2023-06-26 13:55:35 +02:00
Micha Reiser
8078663b6c Remove Range type parameter from AST nodes (#15) 2023-06-23 21:18:55 +01:00
Micha Reiser
f60e204b73 Fix range of keyword identifier (#14) 2023-06-22 10:00:25 +02:00
Micha Reiser
08ebbe40d7 Fix ArgWithDefault TextRange (#13) 2023-06-20 18:21:09 +02:00
Charlie Marsh
ed3b4eb72b Add TextRange to Identifier (#8)
## Summary

This PR adds `TextRange` to `Identifier`. Right now, the AST only
includes ranges for identifiers in certain cases (`Expr::Name`,
`Keyword`, etc.), namely when the identifier comprises an entire AST
node. In Ruff, we do additional ad-hoc lexing to extract identifiers
from source code.

One frequent example: given a function `def f(): ...`, we lex to find
the range of `f`, for use in diagnostics.

Another: `except ValueError as e`, for which the AST doesn't include a
range for `e`.

Note that, as an optimization, we avoid storing the `TextRange` for
`Expr::Name`, since it's already included.
2023-06-20 11:19:27 -04:00
Charlie Marsh
f0d200c8a1 Remove fold, unparse, and location features (#9) 2023-06-19 17:26:17 -04:00
Charlie Marsh
8d74eee750 Make malachite-bigint an optional dependency for rustpython-format (#12) 2023-06-19 16:31:25 -04:00
Charlie Marsh
21aa0b8d84 Optimize validate_arguments (#10) 2023-06-19 15:32:58 -04:00
Charlie Marsh
9cb00518e5 Update snapshot tests 2023-06-19 13:16:07 -04:00
Micha Reiser
6f65c5cba7 Add Decorator node (#7) 2023-06-19 12:53:05 -04:00
Micha Reiser
8a415fa61e Include argument parentheses in range (#5) 2023-06-19 12:51:18 -04:00
Micha Reiser
5054cbe84f Merge branch 'RustPython:main' into main 2023-06-19 12:38:24 -04:00
Jeong, YunWon
69d27d924c Rename unconventional nodes (#74) 2023-06-17 01:54:00 +09:00
Jeong, YunWon
5270020423 rustpython_ast python package (#79) 2023-06-17 01:32:27 +09:00
yt2b
edcfcb4a74 Fix bool format (#91)
* Fix format_bool

* Add test_format_bool
2023-06-15 14:56:03 +09:00
Jeong, YunWon
40a603208f Add Pylyzer
done by https://github.com/mtshiba/pylyzer/pull/37
2023-06-11 21:56:59 +09:00
Jeong, YunWon
b2f95e2848 Fix LinearLocator \r handling (#80) 2023-06-02 22:35:53 +09:00
Steve Shi
a2e3209c42 Replace num-bigint with malachite-bigint (#18)
Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2023-06-02 17:06:18 +09:00
Jeong, YunWon
5e9e8a7589 Linear Locator (#46) 2023-06-01 13:53:31 +09:00
Jeong, YunWon
fdec727f80 New Arguments and Arg/ArgWithDefault AST representation (#59) 2023-06-01 01:15:23 +09:00
Jeong, YunWon
3fbf4f6804 Parse for expr and stmt variants + identifier, constant (#78) 2023-05-31 20:03:46 +09:00
Micha Reiser
fe25708d89 Merge pull request #76 from astral-sh/match-case-end-location 2023-05-31 09:48:42 +02:00
Micha Reiser
342cd19f50 Add safety comment 2023-05-31 09:37:48 +02:00
Micha Reiser
4a2c4aad0b Align MatchCase end location 2023-05-30 17:31:51 +02:00
Jeong, YunWon
ae3a477c97 Add parser deps to rustpython_ast_pyo3 (#75) 2023-05-29 16:48:05 +09:00
Jeong, YunWon
531aeb3511 Cmpop::as_str (#72)
* clean up pyo3 generation

* Cmpop::as_str
2023-05-29 01:53:54 +09:00
Jeong, YunWon
4de0cb1827 Parse Trait (#71) 2023-05-28 21:03:27 +09:00
Micha Reiser
5493c9f4e3 Avoid removing elements from the beginning of a vec (#69) 2023-05-25 19:44:49 +09:00
Jeong, YunWon
d23611db65 Merge pull request #65 from youknowone/refactor-pyo3
to_pyo3_ast to return &'py + Separate rustpython_ast_pyo3
2023-05-23 03:03:01 +09:00
Jeong YunWon
7d384d88d0 Separate rustpython_ast_pyo3 2023-05-23 02:50:12 +09:00
Jeong YunWon
b81273e9bc to_pyo3_ast to return &'py 2023-05-23 00:12:22 +09:00
Micha Reiser
335780aeea Merge pull request #2 from astral-sh/include-decorators-in-class-and-func-range 2023-05-22 13:46:38 +02:00
Jeong, YunWon
e1f02fced7 Fix Vec::to_pyo3_ast (#63) 2023-05-22 15:15:05 +09:00
Jeong, YunWon
d4084fb17a impl From<T> for ast::Ast (#62) 2023-05-21 22:44:07 +09:00
Micha Reiser
41a0ef8740 Add test 2023-05-19 10:14:46 +02:00
Micha Reiser
25cc1da319 Include decorators in Class and FunctionDef range 2023-05-19 08:18:43 +02:00
Micha Reiser
33a3c407a9 Merge pull request #60 from astral-sh/reduce-copy 2023-05-19 08:16:28 +02:00
Micha Reiser
fac0c25343 Update python.rs 2023-05-19 08:08:35 +02:00
Micha Reiser
4ff779c298 Update parser/src/python.lalrpop
Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com>
2023-05-18 22:32:05 +02:00
Micha Reiser
726884b287 Avoid allocating vecs for each statement 2023-05-18 21:56:17 +02:00
Micha Reiser
851f23668f Use named parameters to reducue copying 2023-05-18 21:41:06 +02:00
Jeong, YunWon
3654cf0bdf Add Ast as top level enum (#58) 2023-05-19 01:53:39 +09:00
Jeong, YunWon
6c5c311bab Fix range field order (#56)
* Skip validate_arguments when empty

* Fix `range` field order

* Fix unused variable
2023-05-18 21:44:01 +09:00
Jeong, YunWon
531e41ae2c Upgrade bitflags to remove clippy wanrings (#54)
* Upgrade bitflags to remove clippy wanrings

* Fix python lint warnings
2023-05-18 01:50:00 +09:00
Jeong, YunWon
b48834fe2d More flexible map_user and fold for new constructor nodes (#53)
* make fold.rs file

* Split user_map steps

* Fold for new constructor nodes
2023-05-18 00:16:04 +09:00
Jeong, YunWon
205ee80033 README (#47) 2023-05-17 18:01:00 +09:00
Jeong, YunWon
fc301ab1b0 Fix build (#45)
* remove dedent

* Revert parser-pyo3 crate
2023-05-16 23:54:49 +09:00
Jeong, YunWon
e820928f11 Add experimental pyo3-wrapper feature (#41)
* Fix pyo3 unit type value error

* Add experimental pyo3-wrapper feature

* location support
2023-05-16 23:45:31 +09:00
Jeong, YunWon
ff17f6e178 Add utilities to enum (#44)
* Add utilities to enum

* Fix unexpected pyo3 dependency propagation
2023-05-16 23:29:49 +09:00
Jeong YunWon
3bdf8a940a Hash for ConversionFlag 2023-05-16 22:56:17 +09:00
Jeong, YunWon
9d47d3d212 specialize ConversionFlag (#42)
* specialize ConversionFlag

* Change value of ConversionFlag to i8 and None to -1

* is_* methods to ConversionFlag
2023-05-16 22:52:50 +09:00
Jeong, YunWon
611dcc2e9b rustpython_ast + pyo3 (#25) 2023-05-16 18:06:54 +09:00
Jeong, YunWon
53de75efc3 Add all node classes to ast module (#40) 2023-05-16 16:55:26 +09:00
Jeong, YunWon
0c7d16b61a Add is_* methods to Tok (#39) 2023-05-16 16:08:25 +09:00
Jeong, YunWon
735c06d5f4 Fix full-lexer feature (#38) 2023-05-16 15:45:03 +09:00
Jeong, YunWon
02f13abf50 Add Node trait for node type information (#31) 2023-05-16 15:41:30 +09:00
Charlie Marsh
10dda125ff Always emit non-logical newlines for 'empty' lines (#27) 2023-05-15 14:13:05 -04:00
Jeong, YunWon
27e3873dc2 Add full-lexer feature (#36) 2023-05-16 02:21:34 +09:00
Micha Reiser
dd4cc25227 Reduce copying elements when parsing (#35) 2023-05-15 23:20:44 +09:00
Jeong, YunWon
b78001c953 Generic types to generic (#30) 2023-05-15 20:22:42 +09:00
Jeong, YunWon
718354673e Move range to node (#23)
* black + clippy

* Fix module generation
2023-05-15 16:20:22 +09:00
Micha Reiser
192379cede Move range from Attributed to Nodes (#22)
* Move `range` from `Attributed` to `Node`s

* No Attributed + custom for Range PoC

* Generate all located variants, generate enum implementations

* Implement `Copy` on simple enums

* Move `Suite` to `ranged` and `located`

* Update tests
---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2023-05-15 15:08:12 +09:00
Micha Reiser
a983f4383f Add format and cformat modules from RustPython (#24)
* Add `format` and `cformat` modules from `RustPython`

* Introduce `rustpython-format` crate

* Remove unused dependencies
2023-05-12 18:27:05 +09:00
Jeong, YunWon
947fb53d0b Field accessor and utilities (#20)
* Apply is-macro to Constant and ast nodes
2023-05-11 19:44:20 +09:00
Jeong, YunWon
2baad9ead6 Merge pull request #19 from RustPython/identifier
A few more Identifier utilities
2023-05-11 16:12:46 +09:00
Jeong YunWon
2af9805662 A few more Identifier utilities 2023-05-11 16:05:11 +09:00
Jeong, YunWon
5b2af304a2 Merge pull request #17 from youknowone/ruff
ruff integration support
2023-05-11 04:42:52 +09:00
Jeong YunWon
cbe4e8c5f3 Make parser location optional 2023-05-11 04:40:10 +09:00
Jeong YunWon
aabc96dde9 ruff integration support 2023-05-11 04:08:57 +09:00
Jeong YunWon
99e108dd53 *Escape::with_preferred_quote 2023-05-10 21:51:01 +09:00
Jeong YunWon
d6b6df5d1c ast::Int::to_usize 2023-05-10 21:31:02 +09:00
Jeong YunWon
822cac5aa0 parse_expression{=>_starts}_at 2023-05-10 21:25:58 +09:00
Jeong YunWon
aa101e4f26 fix ImportDots 2023-05-10 20:31:04 +09:00
Jeong, YunWon
cbc4cb286b Merge pull request #16 from youknowone/ast-int
Give identifier and int ast types
2023-05-10 20:19:01 +09:00
Jeong YunWon
6fa3d0f90a Fine-tune int types 2023-05-10 19:33:39 +09:00
Jeong YunWon
455bcc01a0 Give identifier and int ast types 2023-05-10 19:33:39 +09:00
Jeong, YunWon
d495cd9129 Merge pull request #15 from youknowone/spellchecker
Setup spell checker
2023-05-10 18:11:40 +09:00
Jeong YunWon
d8822d1091 spell check ast/asdl_rs.py 2023-05-10 18:05:39 +09:00
Jeong YunWon
75f6ce1ae5 Setup spell checker 2023-05-10 17:57:15 +09:00
Jeong, YunWon
41e9e7280a Merge pull request #12 from youknowone/visitor
Visitor
2023-05-10 17:52:25 +09:00
Jeong YunWon
17c8abcec1 asdl_rs.py to multiple file output 2023-05-10 17:41:01 +09:00
Jeong YunWon
e000b1c304 Remove redundant types 2023-05-10 17:15:01 +09:00
Jeong YunWon
243ca16b34 Fix visitor to fit in new structure 2023-05-10 17:14:44 +09:00
Joshua
4de9580b92 Generate a visitor trait to ast_gen.rs 2023-05-10 17:14:34 +09:00
Jeong, YunWon
5cf85f0b9d Merge pull request #13 from youknowone/locator
Python location transform and related refactoring
2023-05-10 17:04:30 +09:00
Jeong YunWon
4dc030ba9d Vendor SourceLocation from ruff 2023-05-10 17:00:12 +09:00
Jeong YunWon
1d366d52ab Let located only for python located stuff 2023-05-10 14:35:38 +09:00
Jeong YunWon
a3d9d8cb14 numerous refactoring
- Split parser core and compiler core. Fix #14
- AST int type to `u32`
- Updated asdl_rs.py and update_asdl.sh fix #6
- Use `ruff_python_ast::SourceLocation` for Python source location. Deleted our own Location.
- Renamed ast::Located to ast::Attributed to distinguish terms for TextSize and SourceLocation
- `ast::<Node>`s for TextSize located ast. `ast::located::<Node>` for Python source located ast.
- And also strictly renaming `located` to refer only python location related interfaces.
- `SourceLocator` to convert locations.
- New `source-code` features of to disable python locations when unnecessary.
- Also including fully merging https://github.com/astral-sh/RustPython/pull/4 closes #9
2023-05-10 14:35:38 +09:00
Jeong YunWon
09a6afdd04 Adapt SourceLocation 2023-05-09 20:34:48 +09:00
Jeong YunWon
a14e43e03a Separate byteoffset ast and located ast 2023-05-09 00:21:52 +09:00
Jeong YunWon
f47dfca4e3 Rename compiler Location to TextSize 2023-05-08 03:38:10 +09:00
Micha Reiser
58c35ab458 Replace row/column based Location with byte-offsets. 2023-05-08 03:38:10 +09:00
Jeong, YunWon
7b8844bd3e Merge pull request #11 from youknowone/refactor-asdl
Refactor ast to hold data as seperated type
2023-05-07 19:29:02 +09:00
Jeong YunWon
6d7358090b Refactor ast to hold data as seperated type 2023-05-07 19:20:47 +09:00
Jeong YunWon
9f1a538eba gitattribute 2023-05-07 17:20:52 +09:00
Jeong, YunWon
48920a034e Merge pull request #10 from youknowone/remove-compile-error
Remove CompileError
2023-05-06 17:20:44 +09:00
Jeong YunWon
13d6e275ef Remove CompileError 2023-05-06 17:15:18 +09:00
Jeong, YunWon
96eb80f5cf Merge pull request #8 from youknowone/python-lint
Add python lint
2023-05-06 14:42:15 +09:00
Jeong YunWon
a73bac9ed1 Add python lint 2023-05-06 14:34:31 +09:00
Jeong, YunWon
6b60f85cc4 Merge pull request #7 from youknowone/lalrpop
Embed generated parser + update lalrpop
2023-05-06 13:57:15 +09:00
Jeong YunWon
39b2dbe04d Update lalrpop to 0.20.0 2023-05-06 13:48:20 +09:00
Jeong YunWon
e1f70100ac Update parser/build.rs to embed python.rs 2023-05-06 05:28:30 +09:00
Jeong, YunWon
d66d935879 Merge pull request #5 from youknowone/ci
Add CI
2023-05-06 03:42:28 +09:00
Jeong YunWon
e28f333f23 Add CI 2023-05-06 03:39:20 +09:00
Jeong YunWon
0adcdd995d Remove unused workspace dependencies 2023-05-06 00:44:34 +09:00
Jeong YunWon
e6eb49ffb0 Set up workspace - Forked from
git@github.com:RustPython/RustPython.git ff5076b12c075b3e87c0ac2971e390b4a209d14f
2023-05-05 23:49:50 +09:00
Jeong YunWon
80109b1fe0 rustpython-literal 2023-05-05 21:49:12 +09:00
Jeong YunWon
bd64603950 Refactor common::bytes::repr using common::escape 2023-05-03 16:08:48 +09:00
Jeong YunWon
5b0e92d725 Refactor common::str::repr using common::escape 2023-05-03 13:47:04 +09:00
Micha Reiser
ae9d3c3193 Add Located::start, Located::end and impl Deref 2023-04-26 10:24:34 -06:00
Micha Reiser
3873414b30 Use Located::new over struct initializer 2023-04-25 18:10:13 -06:00
Jeong YunWon
a9f4b59f40 module objects' type as PyModule 2023-04-20 20:48:57 +09:00
Jeong YunWon
6d6155413b Update lalrpop 2023-03-25 22:18:02 +09:00
Jeong YunWon
f9b5469642 Update cspell for compiler 2023-03-16 22:39:09 +09:00
Charlie Marsh
ef38eb6b1a Include Derive feature with optional Serde dependency 2023-03-12 14:46:10 -04:00
Jeong YunWon
0ea53825db Merge pull request #4608 from coolreader18/bag-deser
Rework frozen modules and directly deserialize to CodeObject<Literal>
2023-03-10 05:19:23 +09:00
Noa
9d6ae774f8 Rework frozen modules and directly deserialize to CodeObject<Literal> 2023-03-06 13:45:33 -06:00
Charlie Marsh
55fc0e83f3 Treat match and case as soft keywords in lambda assignments (#4623) 2023-03-04 12:42:05 -05:00
Charlie Marsh
713dd2b91e Add optional serde dependency 2023-03-02 15:38:40 -05:00
Noa
969ea23d67 Address review comments 2023-03-01 21:11:59 -06:00
Noa
b80bbec8e6 Custom marshal enc/decoding impl 2023-03-01 20:47:21 -06:00
DimitrisJim
8f425e9ce2 Use insta to verify values. 2023-02-28 20:00:47 +09:00
Jeong YunWon
d7e2e7361e Use git version of unicode_names2 to avoid alias search failure 2023-02-28 20:00:47 +09:00
Charlie Marsh
c37e0c7f03 Use proper locations for sub-expressions in constant matches 2023-02-26 23:10:38 -05:00
Charlie Marsh
0d7b94817d Allow type variable tuple for *args 2023-02-23 08:39:59 -05:00
Jeong YunWon
f43e5b72e2 Merge pull request #4552 from charliermarsh/charlie/loc
Limit match range to end of last statement
2023-02-23 02:11:42 +09:00
Jeong YunWon
a19f294b0c Merge pull request #4543 from youknowone/flatten-parser
Flatten parser interface
2023-02-23 02:09:46 +09:00
Charlie Marsh
7ebef61c47 Limit match range to end of last statement 2023-02-22 11:25:50 -05:00
Charlie Marsh
b61f4d7b69 Allow trailing commas in MappingPattern 2023-02-22 10:02:41 -05:00
Jeong YunWon
e26369a34e use super::* from tests submodules 2023-02-22 21:01:39 +09:00
Jeong YunWon
97a08ee77b remove #[macro_use] 2023-02-22 20:41:27 +09:00
Jeong YunWon
cb8c6fb78d Flatten rustpython_parser interface 2023-02-22 20:32:31 +09:00
Jeong YunWon
8580e4ebb5 make_tokenizer -> lex to integrate terms
we don't distinguish scanner or tokenizer from lexer
2023-02-22 20:28:15 +09:00
Jeong YunWon
39fc23cf92 relocate feature-independent use 2023-02-22 20:28:03 +09:00
Jeong YunWon
66e3080173 Fix ModeParseError message 2023-02-22 20:28:03 +09:00
Jeong YunWon
1511b6631b Break down rustpython_parser::error module
because it doesn't share any common errors but specific error for each sub module
2023-02-22 20:28:01 +09:00
Charlie Marsh
2a8aa6f308 Always wrap in SoftKeywordTransformer 2023-02-21 19:18:42 -05:00
Charlie Marsh
dc628cab8f Expose SoftKeywordTransformer on public API 2023-02-21 19:00:32 -05:00
Jim Fasarakis-Hilliard
c137bc9d77 Merge pull request #4519 from charliermarsh/charlie/match
Add support for match statements to parser
2023-02-21 19:43:28 +02:00
Jeong YunWon
60180fd54c Merge pull request #4531 from charliermarsh/charlie/exception-groups
Implement except* syntax
2023-02-21 13:20:18 +09:00
Charlie Marsh
c7ed645cc6 Implement except* syntax 2023-02-21 12:19:54 +09:00
Charlie Marsh
8caa28f0f8 Update Python.asdl from CPython 3.11.1 2023-02-21 12:19:51 +09:00
Charlie Marsh
8aa3bc93f3 Allow starred expressions in subscripts 2023-02-20 17:59:35 -05:00
Charlie Marsh
f39ffef370 Update compiler/parser/src/soft_keywords.rs
Co-authored-by: Jim Fasarakis-Hilliard <d.f.hilliard@gmail.com>
2023-02-20 15:03:39 -05:00
Jeong YunWon
e093d2bee6 clean up soft-keyword transform 2023-02-20 15:03:39 -05:00
Charlie Marsh
ca5b474d45 Use muiltipeek 2023-02-20 15:03:39 -05:00
Charlie Marsh
2b43d45bd5 Add support for match statements to parser 2023-02-20 15:03:39 -05:00
John Pham
4bdc2d47c1 Make common::repr throw error instead of panic (#4520) 2023-02-19 22:09:54 +09:00
Jeong YunWon
09b82e41d5 Merge pull request #4490 from DimitrisJim/function_parser
Add tests, some comments, to function.rs.
2023-02-13 20:00:35 +09:00
Jeong YunWon
cc6d8a1c58 Merge pull request #4492 from DimitrisJim/doc_parser_uno
Document parser crate.
2023-02-13 17:25:14 +09:00
Dimitris Fasarakis Hilliard
07918f0a9a Document parser crate. 2023-02-12 17:58:19 +02:00
Dimitris Fasarakis Hilliard
a0786ea872 Add tests, some comments, to function.rs. 2023-02-11 23:07:57 +02:00
Dimitris Fasarakis Hilliard
4713b2b3ab Refactor: Join string and string_parser. 2023-02-11 18:05:06 +02:00
Jeong YunWon
e7f14ab9b8 Add test_generator_expression_argument 2023-02-11 05:20:39 +09:00
Charlie Marsh
56c73cc63d Use entire range for generators-as-arguments 2023-02-10 10:39:40 -05:00
Dimitris Fasarakis Hilliard
659f4dd8bf Document lexer. 2023-02-07 21:43:57 +02:00
Dimitris Fasarakis Hilliard
bd158089e0 Move NewLineHandler inline, don't check each character twice. 2023-02-07 20:58:53 +02:00
Jeong YunWon
a73bee7aae use workspace dependencies 2023-02-06 15:30:38 +09:00
Dimitris Fasarakis Hilliard
1468fe46ab Hint that the unwrap should always succeed. 2023-02-01 12:17:33 +02:00
Dimitris Fasarakis Hilliard
c9364718b4 Eat for comma. 2023-01-31 12:26:05 +02:00
Dimitris Fasarakis Hilliard
38cf933bcb Add initial capacities, use u32s for indents/spaces. 2023-01-31 12:26:05 +02:00
Dimitris Fasarakis Hilliard
838990ae15 Don't call is_emoji_presentation for each invocation of consume_normal 2023-01-31 12:26:05 +02:00
Dimitris Fasarakis Hilliard
aa0290bbfc Match on ascii start/continuation characters before calling functions. 2023-01-31 12:26:05 +02:00
Aarni Koskela
f74e44d1e8 Bump phf to 0.11 series
string_cache is still using phf_shared 0.10.0 though
2023-01-25 19:58:43 +02:00
Jeong YunWon
d9df131720 Merge pull request #4449 from harupy/fix-dict-spread-in-dict
Fix AST generated from a dict literal containing dict unpacking
2023-01-22 20:44:26 +09:00
harupy
f2ffe12f8c Fix comment 2023-01-22 00:06:52 +09:00
Anders Kaseorg
6dba8430be Fix end location for elif blocks
Since we parse an `elif:` block as an `If` node, its location should
include its `orelse` node like it would for an `if:` block.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-01-17 22:39:44 -05:00
harupy
b26365b215 Remove useless String::from
Signed-off-by: harupy <hkawamura0130@gmail.com>
2023-01-16 21:27:57 +09:00
harupy
2d019930e9 Rename test 2023-01-15 23:36:07 +09:00
harupy
d5fc7c4c87 Improve test 2023-01-15 16:53:13 +09:00
harupy
393869c47c Add Option to Dict.keys field 2023-01-15 16:43:13 +09:00
harupy
4edd2bf78a Remove commented-out code 2023-01-15 16:10:51 +09:00
harupy
d3c4551629 Fix unparse 2023-01-15 13:11:55 +09:00
harupy
581f6e176c Fix dict spreading in dict literal 2023-01-15 13:01:59 +09:00
Jim Fasarakis-Hilliard
ac4d3c076c Merge pull request #4443 from bluetech/non-logical-newline-token-fixup
Fixup parse_tokens after "Add NonLogicalNewline token"
2023-01-14 12:49:04 +02:00
Ran Benita
e5fe037e38 Fixup parse_tokens after "Add NonLogicalNewline token"
I only updated `parse()` to ignore `NonLogicalNewline`, didn't notice
it's also needed in `parse_tokens()`.
2023-01-14 11:52:33 +02:00
Noa
872b9d4765 Switch from 64-bit instruction enum to out-of-line arg values 2023-01-12 23:05:17 -06:00
Ran Benita
674eeec29c Add NonLogicalNewline token
This token is completely ignored by the parser, but it's useful for
other users of the lexer, such as the Ruff linter. For example, the
token is helpful for a "trailing comma" lint.

The same idea exists in Python's `tokenize` module - there is a NEWLINE
token (logical newline), and a NL token (non-logical newline).

Fixes #4385.
2023-01-12 16:47:12 +02:00
Martin Fischer
4f1e7c6291 Fix docs.rs build for rustpython-parser
docs.rs failed to build the documentation of the recently released
rustpython-parser 0.2.0 because the build.rs script couldn't write the
parser.rs file because docs.rs builds the documentation in a sandbox
with a read-only filesystem.

This commit fixes this by writing the parser.rs file to the cargo output
directory instead, as recommended by the docs.rs documentation.[1]

Fixes #4436.

[1]: https://docs.rs/about/builds#read-only-directories
2023-01-11 09:58:10 +01:00
Noa
884a7bdb15 Bump all crate versions to 0.2.0 2023-01-11 00:14:28 -06:00
Jeong YunWon
7885344bcf first cspell dict 2023-01-09 19:57:23 +09:00
Jeong YunWon
e8fef39861 Merge pull request #4429 from youknowone/fix-format
Fix nightly clippy warnings
2023-01-07 21:38:41 +09:00
Jeong YunWon
509cf7ed0d Fix nightly clippy warnings 2023-01-07 21:07:10 +09:00
harupy
a4a5366504 Include comment text in token 2023-01-06 23:29:20 +09:00
harupy
2dfd053bed Implement Default for Location 2023-01-05 22:48:47 +09:00
harupy
7f552e4594 Address comments 2023-01-05 18:24:54 +09:00
harupy
9efa872023 Use try_from 2023-01-05 01:18:30 +09:00
harupy
fd8468c5eb Simplify string check 2023-01-04 23:57:50 +09:00
Jeong YunWon
958c7e33ad Merge pull request #4417 from harupy/add-with-offset-methods
Add `with_col_offset` and `with_row_offset` to `Location` for conveniece
2023-01-04 17:07:31 +09:00
Jeong YunWon
0f311cd5e5 Merge pull request #4413 from harupy/more-generic-window-impl
Update `CharWindow` in `compiler/parser/src/lexer.rs` to allow slicing
2023-01-04 17:06:06 +09:00
harupy
0365752bf3 Use Self 2023-01-04 13:19:23 +09:00
harupy
6d140426c1 Add with_col_offset and with_row_offset to Location 2023-01-04 13:16:03 +09:00
harupy
33a62789f7 Address comment 2023-01-04 00:03:18 +09:00
harupy
84dff79ddc Remove incorrect EmptyExpression in parse_formatted_value 2023-01-03 23:55:50 +09:00
harupy
300710f7db Improve CharWindow 2023-01-03 17:27:35 +09:00
harupy
515dceb07b Remove repetitive to_string in parse_escaped_char 2023-01-03 14:48:00 +09:00
Jeong YunWon
2858c315bf Merge pull request #4409 from harupy/improve-error-conversion-in-string-parser
Improve error conversion in `string_parsers.rs`
2023-01-03 14:39:01 +09:00
harupy
3b0fd61b3b Fix clippy error 2023-01-03 12:51:31 +09:00
harupy
9030679193 Improve error conversion in string_parsers.rs 2023-01-03 12:46:10 +09:00
Jeong YunWon
71ba4226c1 Merge pull request #4405 from harupy/use-drain
Use `drain` to simplify `compiler/parser/src/string_parser.rs`
2023-01-03 12:41:23 +09:00
Jeong YunWon
3d03439618 Merge pull request #4399 from branai/shell-continuing-fix
Fix IndentationError works differently with cpython in interective shell
2023-01-03 04:32:03 +09:00
harupy
39f410c909 Use drain 2023-01-03 01:23:44 +09:00
Jim Fasarakis-Hilliard
4222b13e6c Merge pull request #4404 from harupy/merge-match-arms
Merge match arms in `StringParser.parse_formatted_value`
2023-01-02 18:18:40 +02:00
Jim Fasarakis-Hilliard
d691527ead Merge pull request #4402 from harupy/remove-unreachable-if
Remove unreachable code in `compiler/parser/src/string_parser.rs`
2023-01-02 17:05:45 +02:00
harupy
40e2150f7e Merge match arms in parse_formatted_value 2023-01-02 23:16:51 +09:00
harupy
92bf96608c Fix match 2023-01-02 22:54:48 +09:00
harupy
fac6a857f6 Simplify code using match 2023-01-02 22:26:09 +09:00
harupy
68586f8e3c Remove unreachable code in compiler/parser/src/string_parser.rs 2023-01-02 20:48:40 +09:00
Jeong YunWon
f8787a9377 Move (c)format basic implementations to rustpython-common 2023-01-02 20:21:36 +09:00
Bijan Naimi
530a20cc96 forgot to add formatted errors.rs 2023-01-01 17:28:49 -08:00
Bijan Naimi
6cb57b2075 changed the shell logic for handling indents 2023-01-01 15:41:51 -08:00
Dimitris Fasarakis Hilliard
92b2574d52 Move tests for with into parser. 2023-01-01 21:36:07 +02:00
Jim Fasarakis-Hilliard
95fb938bd6 Merge pull request #4389 from harupy/4384-follow-up
Follow-up for #4384
2023-01-01 14:53:54 +02:00
harupy
9683314264 Remove unreachable code 2023-01-01 17:43:25 +09:00
harupy
d9bbeeb9b3 Fix NamedExpr location 2022-12-31 23:32:08 +09:00
harupy
68116d5c11 Move tests 2022-12-31 12:15:33 +09:00
Jeong YunWon
e164a41723 Merge pull request #4373 from andersk/named
Allow named expression in subscript and set comprehension
2022-12-31 10:58:09 +09:00
Jeong YunWon
aa32a73c5b Merge pull request #4379 from harupy/refactor-FStringParser
Refactor `FStringParser`
2022-12-31 10:56:52 +09:00
Jeong YunWon
9d7d629cef Merge pull request #4384 from harupy/parse-formatted-value
Fix the location of `FormattedValue`
2022-12-31 10:52:44 +09:00
harupy
439298e735 Fix FormattedValue location 2022-12-30 21:39:29 +09:00
anilbey
16173bf581 Update compiler/parser/src/error.rs
Co-authored-by: fanninpm <fanninpm@miamioh.edu>
2022-12-29 22:56:34 +01:00
Anil Tuncel
7e7b2eadee format using cargo fmt 2022-12-29 22:15:44 +01:00
Anil Tuncel
80116c768d arg name to be written upon duplicate kwargs error #4381 2022-12-29 22:06:41 +01:00
Nick Liu
200390c1ab format code 2022-12-29 22:49:26 +08:00
Nick Liu
24d2ab8b0a use is_none 2022-12-29 22:49:26 +08:00
Nick Liu
41f21a7b5d add arg_name in duplicate argument error msg 2022-12-29 22:49:26 +08:00
Nick Liu
63e4a36e27 added check: named arguments must follow bare star 2022-12-29 22:49:26 +08:00
Nick Liu
15ab44384c added lex error: DuplicateArguments 2022-12-29 22:49:26 +08:00
Jim Fasarakis-Hilliard
5380b85579 Merge pull request #4367 from andersk/star-order
Prohibit starred arguments after double-starred arguments
2022-12-29 16:27:16 +02:00
Harutaka Kawamura
b707f53f23 Update compiler/parser/src/fstring.rs
Co-authored-by: Zgarbul Andrey <zgarbul.andrey@gmail.com>
2022-12-29 08:10:33 +09:00
Anders Kaseorg
6439867f78 Allow named expression in set comprehension: {a := b for c in d}
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-28 09:57:32 -08:00
Anders Kaseorg
1904d095f9 Allow named expression in subscript: a[b := c]
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-28 09:57:32 -08:00
harupy
7e8f683808 Rename 2022-12-29 01:01:41 +09:00
harupy
37b1894834 Clean up FStringParser 2022-12-29 00:08:59 +09:00
Jim Fasarakis-Hilliard
201d08583a Merge pull request #4377 from andersk/duplicate-from
Remove duplicate declaration of "from" token
2022-12-28 11:19:54 +02:00
Anders Kaseorg
107b2e11ae Remove duplicate declaration of "from" token
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-27 18:56:24 -08:00
Anders Kaseorg
661b210391 Prohibit starred arguments after double-starred arguments
CPython prohibits ‘f(**kwargs, *args)’; we should too.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-27 12:49:50 -08:00
harupy
9aed9143fe Refactor parse_formatted_value 2022-12-28 00:23:25 +09:00
harupy
c21d0d9283 Fix make_tokenizer_located 2022-12-27 21:54:18 +09:00
Jeong YunWon
313fd7d28c Merge pull request #4359 from yt2b/check_bom
Add BOM check
2022-12-26 16:03:51 +09:00
Jeong YunWon
3aa0096212 Merge pull request #4358 from harupy/fix-slice-location
Fix `Slice` location
2022-12-26 16:03:09 +09:00
Jeong YunWon
53dec88029 Merge pull request #4356 from andersk/with-tuple-named
Fix parsing of tuple with named expression as context manager
2022-12-26 16:02:17 +09:00
yt2b
bd0c15d34e Fix comment 2022-12-26 09:30:12 +09:00
yt2b
ce0be73841 Add BOM check 2022-12-25 11:15:29 +09:00
harupy
b2ac4f60f1 Fix slice location 2022-12-25 09:37:07 +09:00
Anders Kaseorg
1fdfa5fe1b Fix parsing of tuple with named expression as context manager
Because the ‘with’ item grammar disallows named expressions, CPython
parses ‘with (a := 0, b := 1):’ as a tuple rather than two ‘with’
items.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-24 13:15:53 -08:00
harupy
c0f390ebc6 Fix IfExp location 2022-12-25 00:33:02 +09:00
Anders Kaseorg
c387b5d523 Simplify parenthesized context manager parsing with LALRPOP conditions
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-24 23:13:10 +09:00
harupy
ea95e1a715 Fix the location of BinOp 2022-12-21 22:05:05 +09:00
Jim Fasarakis-Hilliard
c16e08d59b Merge pull request #4340 from harupy/fix-locations-of-parethesized-expressions
Fix the start and end locations of `Tuple`
2022-12-18 15:17:40 +02:00
harupy
ff00460ff4 Fix locations of parethesized expressions 2022-12-18 20:53:30 +09:00
Anders Kaseorg
f4672e4256 Remove unnecessary boxing of ASDL product children
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-14 02:30:02 -08:00
Dimitris Fasarakis Hilliard
6e89b3ab1a Fix end location in with statements. 2022-12-13 13:28:17 +02:00
Jim Fasarakis-Hilliard
6a174dae45 Merge pull request #4327 from harupy/fix-end-location-body
Fix end location of compound statements
2022-12-13 12:30:21 +02:00
Charlie Marsh
8936ab2f8d Set ExprContext::Store on parenthesized with expressions 2022-12-12 09:09:15 -05:00
harupy
165b979733 Refactor 2022-12-12 22:36:34 +09:00
harupy
06c92bb899 Update snapshot 2022-12-12 22:26:03 +09:00
harupy
1a657731dd Format 2022-12-12 22:18:26 +09:00
harupy
d8cfc7e84f Resolve conflict 2022-12-12 22:16:46 +09:00
harupy
de2e88656e Address comments
Signed-off-by: harupy <hkawamura0130@gmail.com>
2022-12-12 22:14:05 +09:00
Anders Kaseorg
052dee72b8 Parse Python 3.9+ parenthesized context managers
Since the upstream grammar for this is not LR(1), we abuse LALRPOP
macros and the Into/TryInto traits to build a cover grammar that
converts to either tuples or `with` items after additional validation.
It’s annoying and ugly, but something like this is basically our only
option short of switching to a more powerful parser algorithm.

Fixes #4145.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-12 00:47:33 -08:00
Anders Kaseorg
751f9d304f Split and simplify some LALRPOP rules
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-12-11 22:02:08 -08:00
harupy
d6f9dd0763 Use method chaining 2022-12-12 10:24:00 +09:00
harupy
5fc8c4d0b1 Fix other compound statements 2022-12-12 01:10:42 +09:00
harupy
1b7a272b77 Fix end location of nodes containing body 2022-12-11 12:35:28 +09:00
harupy
3abdc87076 Refactor 2022-12-10 22:01:42 +09:00
harupy
99b02be35a Fix 2022-12-10 18:45:36 +09:00
harupy
f99167d4ed Fix plain string 2022-12-10 18:09:26 +09:00
harupy
816e1e711c Fix the end location of an implicitly-concatenated string 2022-12-10 17:49:57 +09:00
Jeong YunWon
e2b28d07c8 Merge pull request #4310 from youknowone/fix-clippy
Fix nightly clippy warnings
2022-12-05 12:57:01 +09:00
Jeong YunWon
28785784b2 Fix nightly clippy warnings 2022-12-05 12:18:16 +09:00
harupy
6f6b7b2312 add tests 2022-12-04 05:59:51 +09:00
harupy
93cb05ebda Fix location 2022-12-04 05:45:15 +09:00
yt2b
e8200ab674 use bool.then 2022-11-24 09:23:20 +09:00
yt2b
492f09298f restore if expression 2022-11-23 11:30:54 +09:00
yt2b
35eea0b8ec Refactor lexer functions 2022-11-22 21:10:19 +09:00
yt2b
1eeddb521e Add test 2022-11-21 22:36:05 +09:00
Charlie Marsh
96a50810a6 Use match 2022-11-20 23:41:52 -05:00
Charlie Marsh
51b7dbb89c Use rustc-hash 2022-11-20 15:30:19 -05:00
Charlie Marsh
28a8c3a062 Implement some minor performance optimizations 2022-11-20 13:33:20 -05:00
Bongjun Jang
4e5626dfd5 Refactor lexer struct (#4257) 2022-11-19 21:43:34 +09:00
Jeong YunWon
f979d8dbc3 Apply let-else statements 2022-11-10 15:39:31 +09:00
Jim Fasarakis-Hilliard
cc084b4fec Merge pull request #4266 from charliermarsh/charlie/comments
Implement Tok::Comment
2022-11-07 23:20:15 +02:00
Charlie Marsh
bbeec36fdb Set comparator start location to beginning of comparison 2022-11-07 12:24:14 -05:00
Charlie Marsh
b6c230f3ca Implement Tok::Comment 2022-11-07 10:33:55 -05:00
dvermd
a5b59f3c9d improve col_offset in new line and lalr 2022-10-26 21:30:51 +02:00
dvermd
d5a208ca9d improve fstring parser
part of: #1671
2022-10-26 21:15:24 +02:00
dvermd
fa41a1e2f6 Fix ast types' _fields and use 0-based column 2022-10-27 03:09:38 +09:00
Charlie Marsh
952d70b9d1 Add expression context parsing 2022-10-17 15:20:33 -04:00
Charlie Marsh
02953b9fe6 Remove parse_program_tokens 2022-10-17 12:04:30 -04:00
Charlie Marsh
8adc74fe26 Expose a method to parse AST from tokens directly 2022-10-17 09:39:48 -04:00
Jeong YunWon
48c0cb5599 Merge pull request #4218 from charliermarsh/charlie/clone
Make AST nodes Clone-able
2022-10-17 13:47:21 +09:00
Jeong YunWon
2e33a3d0e9 Merge pull request #4223 from youknowone/nightly-clippy
Fix nightly clippy warnings
2022-10-17 13:28:53 +09:00
Charlie Marsh
1cc342e4ed Add end locations to all nodes (#4192) 2022-10-17 13:18:30 +09:00
Charlie Marsh
519718e65d Start simple string at quote mark 2022-10-16 11:25:46 -04:00
Charlie Marsh
5da5490b19 Make AST nodes Clone-able 2022-10-16 11:01:17 -04:00
Jeong YunWon
518cf728c3 Fix nightly clippy warnings 2022-10-16 02:38:50 +09:00
Charlie Marsh
3397737a76 Start string location at kind or quote prefix 2022-10-15 11:03:50 -04:00
dvermd
6211a3a3a8 Refactor fstrings (#4188) 2022-10-14 12:16:34 +09:00
fanninpm
e64d7d196d Merge pull request #4186 from andersk/arithmetic
Spell “arithmetic” correctly
2022-09-30 09:29:36 -04:00
Anders Kaseorg
f8b45e48e1 Spell “arithmetic” correctly
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-09-28 17:58:30 -07:00
Jeong YunWon
f99e4789ed temporary fix of parser build 2022-09-21 16:49:35 +09:00
Jeong YunWon
86b847204e better parser build experience 2022-09-21 16:47:06 +09:00
Charlie Marsh
26b529f9dc Add PartialOrd to Location 2022-09-04 16:31:43 -04:00
Jeong YunWon
b21ed24025 Merge pull request #4116 from charliermarsh/charlie/f-string
Avoid creating unused JoinedStr in FStringParser
2022-08-23 19:30:36 +09:00
Jeong YunWon
a414677892 remove Mode from codegen root 2022-08-23 05:15:27 +09:00
Jeong YunWon
bfe4795b6c parser::Mode from compile::Mode 2022-08-23 05:08:24 +09:00
Jeong YunWon
40690b9761 use thiserror 2022-08-23 05:08:24 +09:00
Jeong YunWon
1fd898c14c new_syntax_error working without full compiler
Fix #4100
2022-08-23 05:08:24 +09:00
Jeong YunWon
42b95a9a95 Move out CompileError to core as generic form 2022-08-23 01:30:00 +09:00
Jeong YunWon
e8230efe1a Integrate CompileError to compiler-core::BaseError 2022-08-22 23:18:41 +09:00
Jeong YunWon
7fcc18daea integrate CodegenError to compiler-core::Error 2022-08-22 18:43:03 +09:00
Jeong YunWon
2b7bf79d29 Integrate ParseError to compiler-core::Error 2022-08-22 16:28:08 +09:00
Jeong YunWon
904fc477f1 integrate ast::Location into compilre-core::Location 2022-08-22 08:42:20 +09:00
Jeong YunWon
bfac0355dc Share location between compiler crates 2022-08-22 08:42:20 +09:00
Jeong YunWon
c16e650071 rustpython-bytecode -> rustpython-compiler-core 2022-08-22 08:42:20 +09:00
Jeong YunWon
acde8bb625 sort Cargo.toml dependencies 2022-08-22 08:42:20 +09:00
Jeong YunWon
7f99404618 clean up cargo.toml sort 2022-08-22 08:42:20 +09:00
Jeong YunWon
3742f9117b Add source_path to ParseError 2022-08-22 08:42:20 +09:00
Jeong YunWon
a66902406f Refactor Mode and partial parser/codegen for eval/exec 2022-08-22 08:42:20 +09:00
Charles Marsh
51b6571ee1 Fix f-string regression 2022-08-21 19:20:23 -04:00
Charles Marsh
2345bc895d Avoid creating unused JoinedStr in FStringParser 2022-08-21 16:59:36 -04:00
Jeong YunWon
53c48bf6b9 reorganize compiler crates 2022-08-22 04:42:42 +09:00
Jeong YunWon
ffacac05bb Clean up imports 2022-08-22 03:42:29 +09:00
Jeong YunWon
3bae0823f7 replace try_parse!() macro to closure 2022-08-12 08:35:34 +09:00
Jeong Yunwon
29644a30d7 Add compile::Mode::BlockExpr 2022-05-27 10:14:13 +09:00
Jeong Yunwon
07712eda58 Upgrade libraries 2022-04-18 00:09:22 +09:00
Noa
76728bb69d Run cargo upgrade 2021-10-30 19:42:35 -05:00
Noa
721c2709c8 Migrate to 2021 edition 2021-10-21 11:46:24 -05:00
Noa
7865cace06 Add unparse feature to ast 2021-10-17 21:55:27 -05:00
Noah
a9330765fa Add an ast optimizer 2021-04-05 09:46:30 -05:00
Noah
9941eef853 Upgrade dependencies 2021-03-25 08:06:56 -05:00
Noah
e494175cd2 Update the compiler to use the new asdl ast types 2021-01-14 12:37:37 -06:00
Noah
dd7560c494 Implement asdl in the ast and parser 2021-01-14 12:37:37 -06:00
Noah
6603b4a124 Use ahash/phf in parser 2021-01-08 17:09:09 -06:00
Noah
dab7e20a33 Use ahash in the compiler 2021-01-08 17:00:23 -06:00
Noah
13f3ec5b2b Update other stuff to use the root of rustpython_bytecode 2020-12-19 14:59:07 -06:00
Noah
452cd308d5 Optimize the size of Instruction 2020-12-14 14:25:19 -06:00
Noah
3c469eaa26 compiler/porcelain wrapper 2020-11-07 15:43:23 -06:00
Noah
0c5bf8e470 Split the ast from the parser, remove compiler dep on parser 2020-11-07 15:43:23 -06:00
Noah
3f52fdd50a Use disassembly for snapshot testing 2020-10-19 23:55:56 -05:00
Noah
f0c0897f23 Update compiler to output codeobj.constants, use insta for snapshot testing 2020-10-19 23:55:55 -05:00
Jeong YunWon
a35d4dc22a clean up PyComplex 2020-08-09 21:46:16 +09:00
Noah
5fe1bd3900 Update itertools 2020-07-26 15:21:39 -05:00
Noah
3737af157f Release 0.1.2
rustpython@0.1.2
rustpython-bytecode@0.1.2
rustpython-compiler@0.1.2
rustpython-derive@0.1.2
rustpython-parser@0.1.2
rustpython-vm@0.1.2
rustpython_freeze@0.1.2
rustpython_wasm@0.1.2

Generated by cargo-workspaces
2020-06-23 18:47:08 -05:00
Chris West (Faux)
ed01cd63ee upgrade low-risk deps 2019-10-10 21:07:26 +01:00
Seo Sanghyeon
1dd57971a9 Optimize BuildMap bytecode emission 2019-10-03 00:03:08 +09:00
Noah
d1ae5a7448 Bump crate versions 2019-09-25 11:57:38 -05:00
coolreader18
e89d6febea Make peephole optimizer a stream processor 2019-08-03 22:02:29 -05:00
Windel Bouwman
4dcad05aa0 Extend symtable module. 2019-07-19 22:05:35 +02:00
Windel Bouwman
b0d270b881 Set all versions to 0.1.0 2019-07-07 13:04:12 +02:00
Windel Bouwman
e3ccef2504 Change authors to team name. 2019-07-01 21:14:07 +02:00
Windel Bouwman
dcdd964e0c Change underscore into hyphen 2019-07-01 21:05:29 +02:00
Windel Bouwman
107442cc06 Make bytecode crate independent of parser crate. 2019-06-30 11:42:36 +02:00
Windel Bouwman
10957879db Move bytecode into own crate. 2019-06-30 11:01:40 +02:00
coolreader18
f0148f46d0 Split off bytecode compilation into a separate crate 2019-06-12 21:43:43 -05:00
885 changed files with 57808 additions and 70678 deletions

1
.github/release.yml vendored
View File

@@ -4,7 +4,6 @@ changelog:
labels:
- internal
- documentation
- formatter
categories:
- title: Breaking Changes
labels:

View File

@@ -3,12 +3,12 @@ name: Benchmark
on:
pull_request:
paths:
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain"
- "crates/**"
- "!crates/ruff_dev"
- "!crates/ruff_shrinking"
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain'
- 'crates/**'
- '!crates/ruff_dev'
- '!crates/ruff_shrinking'
workflow_dispatch:
@@ -22,7 +22,7 @@ jobs:
name: "Run | ${{ matrix.os }}"
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
os: [ ubuntu-latest, windows-latest ]
runs-on: ${{ matrix.os }}
steps:

View File

@@ -2,7 +2,7 @@ name: CI
on:
push:
branches: [main]
branches: [ main ]
pull_request:
workflow_dispatch:
@@ -16,7 +16,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.11"
PYTHON_VERSION: "3.11" # to build abi3 wheels
jobs:
determine_changes:
@@ -42,7 +42,6 @@ jobs:
- "!crates/ruff_formatter/**"
- "!crates/ruff_dev/**"
- "!crates/ruff_shrinking/**"
- scripts/*
formatter:
- Cargo.toml
@@ -51,12 +50,7 @@ jobs:
- crates/ruff_formatter/**
- crates/ruff_python_trivia/**
- crates/ruff_python_ast/**
- crates/ruff_source_file/**
- crates/ruff_python_index/**
- crates/ruff_text_size/**
- crates/ruff_python_parser/**
- crates/ruff_dev/**
- scripts/*
cargo-fmt:
name: "cargo fmt"
@@ -85,7 +79,7 @@ jobs:
cargo-test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
os: [ ubuntu-latest, windows-latest ]
runs-on: ${{ matrix.os }}
name: "cargo test | ${{ matrix.os }}"
steps:
@@ -237,6 +231,7 @@ jobs:
- name: "Run cargo-udeps"
run: cargo +nightly-2023-06-08 udeps
python-package:
name: "python package"
runs-on: ubuntu-latest
@@ -336,8 +331,11 @@ jobs:
- name: "Cache rust"
uses: Swatinem/rust-cache@v2
- name: "Formatter progress"
run: scripts/formatter_ecosystem_checks.sh
run: scripts/formatter_progress.sh
- name: "Github step summary"
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
- name: "Remove checkouts from cache"
run: rm -r target/progress_projects
run: grep "similarity index" target/progress_projects_report.txt | sort > $GITHUB_STEP_SUMMARY
# CPython is not black formatted, so we run only the stability check
- name: "Clone CPython 3.10"
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
- name: "Check CPython stability"
run: cargo run --bin ruff_dev -- format-dev --stability-check crates/ruff/resources/test/cpython

View File

@@ -3,7 +3,7 @@ name: mkdocs
on:
workflow_dispatch:
release:
types: [published]
types: [ published ]
jobs:
mkdocs:
@@ -40,7 +40,7 @@ jobs:
run: mkdocs build --strict -f mkdocs.generated.yml
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@3.0.0
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -66,7 +66,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
target: [ x64, x86 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@@ -94,7 +94,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, i686]
target: [ x86_64, i686 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@@ -121,7 +121,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [aarch64, armv7, s390x, ppc64le, ppc64]
target: [ aarch64, armv7, s390x, ppc64le, ppc64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4

View File

@@ -3,7 +3,7 @@ name: "[Playground] Release"
on:
workflow_dispatch:
release:
types: [published]
types: [ published ]
env:
CARGO_INCREMENTAL: 0
@@ -40,7 +40,7 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@3.0.0
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -2,8 +2,8 @@ name: PR Check Comment
on:
workflow_run:
workflows: [CI, Benchmark]
types: [completed]
workflows: [ CI, Benchmark ]
types: [ completed ]
workflow_dispatch:
inputs:
workflow_run_id:

View File

@@ -42,13 +42,13 @@ repos:
name: cargo fmt
entry: cargo fmt --
language: system
types: [rust]
types: [ rust ]
pass_filenames: false # This makes it a lot faster
- id: ruff
name: ruff
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: system
types_or: [python, pyi]
types_or: [ python, pyi ]
require_serial: true
exclude: |
(?x)^(
@@ -62,12 +62,5 @@ repos:
hooks:
- id: black
# Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0
hooks:
- id: prettier
types: [yaml]
ci:
skip: [cargo-fmt, dev-generate-all]
skip: [ cargo-fmt, dev-generate-all ]

View File

@@ -1,15 +1,5 @@
# Breaking Changes
## 0.0.283 / 0.284
### The target Python version now defaults to 3.8 instead of 3.10 ([#6397](https://github.com/astral-sh/ruff/pull/6397))
Previously, when a target Python version was not specified, Ruff would use a default of Python 3.10. However, it is safer to default to an _older_ Python version to avoid assuming the availability of new features. We now default to the oldest supported Python version which is currently Python 3.8.
(We still support Python 3.7 but since [it has reached EOL](https://devguide.python.org/versions/#unsupported-versions) we've decided not to make it the default here.)
Note this change was announced in 0.0.283 but not active until 0.0.284.
## 0.0.277
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))

View File

@@ -69,13 +69,6 @@ and pre-commit to run some validation checks:
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
```
You can optionally install pre-commit hooks to automatically run the validation checks
when making a commit:
```shell
pre-commit install
```
### Development
After cloning the repository, run Ruff locally from the repository root with:
@@ -131,6 +124,7 @@ At time of writing, the repository includes the following crates:
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
intermediate representation for each node, which `ruff_formatter` prints based on the configured
line length.

63
Cargo.lock generated
View File

@@ -133,12 +133,6 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "ascii-canvas"
version = "3.0.0"
@@ -800,7 +794,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.284"
version = "0.0.280"
dependencies = [
"anyhow",
"clap",
@@ -1033,7 +1027,6 @@ dependencies = [
"number_prefix",
"portable-atomic",
"unicode-width",
"vt100",
]
[[package]]
@@ -2042,7 +2035,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.284"
version = "0.0.280"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2141,7 +2134,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.284"
version = "0.0.280"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2201,6 +2194,7 @@ dependencies = [
"indoc",
"itertools",
"libcst",
"log",
"once_cell",
"pretty_assertions",
"rayon",
@@ -2224,9 +2218,6 @@ dependencies = [
"strum_macros",
"tempfile",
"toml",
"tracing",
"tracing-indicatif",
"tracing-subscriber",
]
[[package]]
@@ -2291,7 +2282,6 @@ dependencies = [
"rustc-hash",
"serde",
"smallvec",
"static_assertions",
]
[[package]]
@@ -3125,18 +3115,6 @@ dependencies = [
"valuable",
]
[[package]]
name = "tracing-indicatif"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b38ed3722d27705c3bd7ca0ccf29acc3d8e1c717b4cd87f97891a2c1834ea1af"
dependencies = [
"indicatif",
"tracing",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
@@ -3335,39 +3313,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vt100"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
dependencies = [
"itoa",
"log",
"unicode-width",
"vte",
]
[[package]]
name = "vte"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
dependencies = [
"arrayvec",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wait-timeout"
version = "0.2.0"

View File

@@ -4,7 +4,7 @@ resolver = "2"
[workspace.package]
edition = "2021"
rust-version = "1.71"
rust-version = "1.70"
homepage = "https://beta.ruff.rs/docs"
documentation = "https://beta.ruff.rs/docs"
repository = "https://github.com/astral-sh/ruff"
@@ -46,9 +46,6 @@ syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
thiserror = { version = "1.0.43" }
toml = { version = "0.7.2" }
tracing = "0.1.37"
tracing-indicatif = "0.3.4"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
wsl = { version = "0.1.0" }
# v1.0.1

View File

@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.284
rev: v0.0.280
hooks:
- id: ruff
```
@@ -211,8 +211,8 @@ line-length = 88
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Assume Python 3.8
target-version = "py38"
# Assume Python 3.10.
target-version = "py310"
[tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.284"
version = "0.0.280"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.284"
version = "0.0.280"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -14,19 +14,3 @@ with open("/dev/shm/unit/test", "w") as f:
# not ok by config
with open("/foo/bar", "w") as f:
f.write("def")
# Using `tempfile` module should be ok
import tempfile
from tempfile import TemporaryDirectory
with tempfile.NamedTemporaryFile(dir="/tmp") as f:
f.write(b"def")
with tempfile.NamedTemporaryFile(dir="/var/tmp") as f:
f.write(b"def")
with tempfile.TemporaryDirectory(dir="/dev/shm") as d:
pass
with TemporaryDirectory(dir="/tmp") as d:
pass

View File

@@ -67,8 +67,7 @@ cfg.getboolean("hello", True)
os.set_blocking(0, False)
g_action.set_enabled(True)
settings.set_enable_developer_extras(True)
foo.is_(True)
bar.is_not(False)
class Registry:
def __init__(self) -> None:

View File

@@ -240,16 +240,12 @@ def foo(f=lambda x: print(x)):
from collections import abc
from typing import Annotated, Dict, Optional, Sequence, Union, Set
import typing_extensions
def immutable_annotations(
a: Sequence[int] | None = [],
b: Optional[abc.Mapping[int, int]] = {},
c: Annotated[Union[abc.Set[str], abc.Sized], "annotation"] = set(),
d: typing_extensions.Annotated[
Union[abc.Set[str], abc.Sized], "annotation"
] = set(),
):
pass
@@ -258,6 +254,5 @@ def mutable_annotations(
a: list[int] | None = [],
b: Optional[Dict[int, int]] = {},
c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
):
pass

View File

@@ -74,10 +74,3 @@ try:
except (ValueError, binascii.Error):
# binascii.Error is a subclass of ValueError.
pass
# https://github.com/astral-sh/ruff/issues/6412
try:
pass
except (ValueError, ValueError, TypeError):
pass

View File

@@ -3,6 +3,3 @@ import logging
name = "world"
logging.info(f"Hello {name}")
logging.log(logging.INFO, f"Hello {name}")
_LOGGER = logging.getLogger()
_LOGGER.info(f"{__name__}")

View File

@@ -1,19 +1,20 @@
import typing
# Shouldn't affect non-union field types.
field1: str
# Should emit for duplicate field types.
field2: str | str # PYI016: Duplicate union member `str`
# Should emit for union types in arguments.
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
print(arg1)
# Should emit for unions in return types.
def func2() -> str | str: # PYI016: Duplicate union member `str`
return "my string"
# Should emit in longer unions, even if not directly adjacent.
field3: str | str | int # PYI016: Duplicate union member `str`
field4: int | int | str # PYI016: Duplicate union member `int`
@@ -32,55 +33,3 @@ field10: (str | int) | str # PYI016: Duplicate union member `str`
# Should emit for nested unions.
field11: dict[int | int, str]
# Should emit for unions with more than two cases
field12: int | int | int # Error
field13: int | int | int | int # Error
# Should emit for unions with more than two cases, even if not directly adjacent
field14: int | int | str | int # Error
# Should emit for duplicate literal types; also covered by PYI030
field15: typing.Literal[1] | typing.Literal[1] # Error
# Shouldn't emit if in new parent type
field16: int | dict[int, str] # OK
# Shouldn't emit if not in a union parent
field17: dict[int, int] # OK
# Should emit in cases with newlines
field18: typing.Union[
set[
int # foo
],
set[
int # bar
],
] # Error, newline and comment will not be emitted in message
# Should emit in cases with `typing.Union` instead of `|`
field19: typing.Union[int, int] # Error
# Should emit in cases with nested `typing.Union`
field20: typing.Union[int, typing.Union[int, str]] # Error
# Should emit in cases with mixed `typing.Union` and `|`
field21: typing.Union[int, int | str] # Error
# Should emit only once in cases with multiple nested `typing.Union`
field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
# Should emit in cases with newlines
field23: set[ # foo
int] | set[int]
# Should emit twice (once for each `int` in the nested union, both of which are
# duplicates of the outer `int`), but not three times (which would indicate that
# we incorrectly re-checked the nested union).
field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
# Should emit twice (once for each `int` in the nested union, both of which are
# duplicates of the outer `int`), but not three times (which would indicate that
# we incorrectly re-checked the nested union).
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`

View File

@@ -74,13 +74,3 @@ field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
# Should emit in cases with newlines
field23: set[ # foo
int] | set[int]
# Should emit twice (once for each `int` in the nested union, both of which are
# duplicates of the outer `int`), but not three times (which would indicate that
# we incorrectly re-checked the nested union).
field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
# Should emit twice (once for each `int` in the nested union, both of which are
# duplicates of the outer `int`), but not three times (which would indicate that
# we incorrectly re-checked the nested union).
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`

View File

@@ -1,46 +0,0 @@
from typing import TypeVar, Self, Type
_S = TypeVar("_S", bound=BadClass)
_S2 = TypeVar("_S2", BadClass, GoodClass)
class BadClass:
def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019
def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019
@classmethod
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
@classmethod
def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
@classmethod
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
class GoodClass:
def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ...
def good_instance_method_1(self: Self, arg: bytes) -> Self: ...
def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ...
@classmethod
def good_cls_method_1(cls: type[Self], arg: int) -> Self: ...
@classmethod
def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ...
@staticmethod
def static_method(arg1: _S) -> _S: ...
# Python > 3.12
class PEP695BadDunderNew[T]:
def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
def generic_instance_method[S](self: S) -> S: ... # PYI019
class PEP695GoodDunderNew[T]:
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...

View File

@@ -1,46 +0,0 @@
from typing import TypeVar, Self, Type
_S = TypeVar("_S", bound=BadClass)
_S2 = TypeVar("_S2", BadClass, GoodClass)
class BadClass:
def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019
def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019
@classmethod
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
@classmethod
def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
@classmethod
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
class GoodClass:
def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ...
def good_instance_method_1(self: Self, arg: bytes) -> Self: ...
def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ...
@classmethod
def good_cls_method_1(cls: type[Self], arg: int) -> Self: ...
@classmethod
def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ...
@staticmethod
def static_method(arg1: _S) -> _S: ...
# Python > 3.12
class PEP695BadDunderNew[T]:
def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
def generic_instance_method[S](self: S) -> S: ... # PYI019
class PEP695GoodDunderNew[T]:
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...

View File

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

View File

@@ -1,38 +1,24 @@
import typing
import typing_extensions
from typing import Literal
# Shouldn't emit for any cases in the non-stub file for compatibility with flake8-pyi.
# Note that this rule could be applied here in the future.
# Shouldn't affect non-union field types.
field1: Literal[1] # OK
field2: Literal[1] | Literal[2] # OK
# Should emit for duplicate field types.
field2: Literal[1] | Literal[2] # Error
# Should emit for union types in arguments.
def func1(arg1: Literal[1] | Literal[2]): # Error
def func1(arg1: Literal[1] | Literal[2]): # OK
print(arg1)
# Should emit for unions in return types.
def func2() -> Literal[1] | Literal[2]: # Error
def func2() -> Literal[1] | Literal[2]: # OK
return "my Literal[1]ing"
# Should emit in longer unions, even if not directly adjacent.
field3: Literal[1] | Literal[2] | str # Error
field4: str | Literal[1] | Literal[2] # Error
field5: Literal[1] | str | Literal[2] # Error
field6: Literal[1] | bool | Literal[2] | str # Error
# Should emit for non-type unions.
field7 = Literal[1] | Literal[2] # Error
# Should emit for parenthesized unions.
field8: Literal[1] | (Literal[2] | str) # Error
# Should handle user parentheses when fixing.
field9: Literal[1] | (Literal[2] | str) # Error
field10: (Literal[1] | str) | Literal[2] # Error
# Should emit for union in generic parent type.
field11: dict[Literal[1] | Literal[2], str] # Error
field3: Literal[1] | Literal[2] | str # OK
field4: str | Literal[1] | Literal[2] # OK
field5: Literal[1] | str | Literal[2] # OK
field6: Literal[1] | bool | Literal[2] | str # OK
field7 = Literal[1] | Literal[2] # OK
field8: Literal[1] | (Literal[2] | str) # OK
field9: Literal[1] | (Literal[2] | str) # OK
field10: (Literal[1] | str) | Literal[2] # OK
field11: dict[Literal[1] | Literal[2], str] # OK

View File

@@ -3,8 +3,8 @@ import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Y032
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
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:

View File

@@ -9,16 +9,16 @@ from typing import (
just_literals_pipe_union: TypeAlias = (
Literal[True] | Literal["idk"]
) # PYI042, since not camel case
) # not PYI042 (not a stubfile)
PublicAliasT: TypeAlias = str | int
PublicAliasT2: TypeAlias = Union[str, bytes]
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
_snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case
Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
snake_case_alias1: TypeAlias = str | int # not PYI042 (not a stubfile)
_snake_case_alias2: TypeAlias = Literal["whatever"] # not PYI042 (not a stubfile)
Snake_case_alias: TypeAlias = int | float # not PYI042 (not a stubfile)
# check that this edge case doesn't crash
_: TypeAlias = str | int

View File

@@ -7,11 +7,11 @@ from typing import (
Literal,
)
_PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
_PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
_PrivateAliasT: TypeAlias = str | int # not PYI043 (not a stubfile)
_PrivateAliasT2: TypeAlias = typing.Any # not PYI043 (not a stubfile)
_PrivateAliasT3: TypeAlias = Literal[
"not", "a", "chance"
] # PYI043, since this ends in a T
] # not PYI043 (not a stubfile)
just_literals_pipe_union: TypeAlias = Literal[True] | Literal["idk"]
PublicAliasT: TypeAlias = str | int
PublicAliasT2: TypeAlias = Union[str, bytes]

View File

@@ -1,22 +0,0 @@
import typing
import sys
from typing import TypeAlias
_UnusedPrivateTypeAlias: TypeAlias = int | None
_T: typing.TypeAlias = str
# OK
_UsedPrivateTypeAlias: TypeAlias = int | None
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
...
if sys.version_info > (3, 9):
_PrivateTypeAlias: TypeAlias = str | None
else:
_PrivateTypeAlias: TypeAlias = float | None
def func2(arg: _PrivateTypeAlias) -> None: ...

View File

@@ -1,22 +0,0 @@
import typing
import sys
from typing import TypeAlias
_UnusedPrivateTypeAlias: TypeAlias = int | None
_T: typing.TypeAlias = str
# OK
_UsedPrivateTypeAlias: TypeAlias = int | None
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
...
if sys.version_info > (3, 9):
_PrivateTypeAlias: TypeAlias = str | None
else:
_PrivateTypeAlias: TypeAlias = float | None
def func2(arg: _PrivateTypeAlias) -> None: ...

View File

@@ -1,18 +0,0 @@
import typing
from typing import TypedDict
class _UnusedTypedDict(TypedDict):
foo: str
class _UnusedTypedDict2(typing.TypedDict):
bar: int
class _UsedTypedDict(TypedDict):
foo: bytes
class _CustomClass(_UsedTypedDict):
bar: list[int]

View File

@@ -1,32 +0,0 @@
import sys
import typing
from typing import TypedDict
class _UnusedTypedDict(TypedDict):
foo: str
class _UnusedTypedDict2(typing.TypedDict):
bar: int
# OK
class _UsedTypedDict(TypedDict):
foo: bytes
class _CustomClass(_UsedTypedDict):
bar: list[int]
if sys.version_info >= (3, 10):
class _UsedTypedDict2(TypedDict):
foo: int
else:
class _UsedTypedDict2(TypedDict):
foo: float
class _CustomClass2(_UsedTypedDict2):
bar: list[int]

View File

@@ -1,17 +0,0 @@
import typing
from typing import Literal, TypeAlias, Union
A: str | Literal["foo"]
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
# OK
A: Literal["foo"]
B: TypeAlias = Literal[b"bar", b"foo"]
C: TypeAlias = typing.Union[Literal[5], Literal["foo"]]
D: TypeAlias = Literal[b"str_bytes", 42]
def func(x: Literal[1J], y: Literal[3.14]): ...

View File

@@ -1,17 +0,0 @@
import typing
from typing import Literal, TypeAlias, Union
A: str | Literal["foo"]
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
# OK
A: Literal["foo"]
B: TypeAlias = Literal[b"bar", b"foo"]
C: TypeAlias = typing.Union[Literal[5], Literal["foo"]]
D: TypeAlias = Literal[b"str_bytes", 42]
def func(x: Literal[1J], y: Literal[3.14]): ...

View File

@@ -1,31 +0,0 @@
import builtins
from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None:
...
# OK
x: type[int, str, float]
y: builtins.type[int, str, complex]
z: Union[float, complex]
def func(arg: type[int, float] | str) -> None:
...
# OK
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@@ -1,24 +0,0 @@
import builtins
from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None: ...
# OK
x: type[int, str, float]
y: builtins.type[int, str, complex]
z: Union[float, complex]
def func(arg: type[int, float] | str) -> None: ...
# OK
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@@ -1,15 +1,7 @@
import contextlib
import pathlib
import pathlib as pl
from pathlib import Path
from pathlib import Path as P
# SIM115
f = open("foo.txt")
f = Path("foo.txt").open()
f = pathlib.Path("foo.txt").open()
f = pl.Path("foo.txt").open()
f = P("foo.txt").open()
data = f.read()
f.close()

View File

@@ -30,13 +30,3 @@ for key in list(obj.keys()):
(k for k in obj.keys()) # SIM118
key in (obj or {}).keys() # SIM118
from typing import KeysView
class Foo:
def keys(self) -> KeysView[object]:
...
def __contains__(self, key: object) -> bool:
return key in self.keys() # OK

View File

@@ -1,10 +0,0 @@
"""Regression test: ensure that we don't treat the export entry as a typing-only reference."""
from __future__ import annotations
from logging import getLogger
__all__ = ("getLogger",)
def foo() -> None:
pass

View File

@@ -18,5 +18,3 @@ file_name.split(os.sep)
# OK
"foo/bar/".split("/")
"foo/bar/".split(os.sep, 1)
"foo/bar/".split(1, sep=os.sep)

View File

@@ -1,2 +0,0 @@
import bar
import foo

View File

@@ -1,2 +0,0 @@
import foo
import bar

View File

@@ -1,8 +0,0 @@
{
"execution_count": null,
"cell_type": "code",
"id": "1",
"metadata": {},
"outputs": [],
"source": ["%%timeit\n", "print('hello world')"]
}

View File

@@ -1,52 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "eab4754a-d6df-4b41-8ee8-7e23aef440f9",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"\n",
"%matplotlib inline\n",
"\n",
"import os\n",
"\n",
"_ = math.pi"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b0e2986-1b87-4bb6-9b1d-c11ca1decd87",
"metadata": {},
"outputs": [],
"source": [
"%%timeit\n",
"import sys"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,51 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "cad32845-44f9-4a53-8b8c-a6b1bb3f3378",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"\n",
"%matplotlib inline\n",
"\n",
"\n",
"_ = math.pi"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7b8e967-8b4a-493b-b6f7-d5cecfb3a5c3",
"metadata": {},
"outputs": [],
"source": [
"%%timeit\n",
"import sys"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -25,23 +25,6 @@
"def foo():\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16214f6f-bb32-4594-81be-79fb27c6ec92",
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"import sys\n",
"\n",
"%matplotlib \\\n",
" --inline\n",
"\n",
"import math\n",
"import abc"
]
}
],
"metadata": {

View File

@@ -27,23 +27,6 @@
"def foo():\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d6c55c6-4a34-4662-914b-4ee11c9c24a5",
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"from pathlib import Path\n",
"\n",
"%matplotlib \\\n",
" --inline\n",
"\n",
"import abc\n",
"import math"
]
}
],
"metadata": {

View File

@@ -1,49 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "a0efffbc-85f1-4513-bf49-5387ec3a2a4e",
"metadata": {},
"outputs": [],
"source": [
"def f():\n",
" foo1 = %matplotlib --list\n",
" foo2: list[str] = %matplotlib --list"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6e0b2b50-43f2-4f59-951d-9404dd560ae4",
"metadata": {},
"outputs": [],
"source": [
"def f():\n",
" bar1 = !pwd\n",
" bar2: str = !pwd"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,49 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "24426ef2-046c-453e-b809-05b56e7355e0",
"metadata": {},
"outputs": [],
"source": [
"def f():\n",
" %matplotlib --list\n",
" %matplotlib --list"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d98fdae-b86b-476e-b4db-9d3ce5562682",
"metadata": {},
"outputs": [],
"source": [
"def f():\n",
" !pwd\n",
" !pwd"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff)",
"language": "python",
"name": "ruff"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,23 +1,28 @@
# PERF203
for i in range(10):
try:
try: # PERF203
print(f"{i}")
except:
print("error")
# OK
try:
for i in range(10):
print(f"{i}")
except:
print("error")
# OK
i = 0
while i < 10:
while i < 10: # PERF203
try:
print(f"{i}")
except:
print("error")
i += 1
try:
i = 0
while i < 10:
print(f"{i}")
i += 1
except:
print("error")

View File

@@ -45,18 +45,3 @@ def f():
for i in items:
if i not in result:
result.append(i) # OK
def f():
fibonacci = [0, 1]
for i in range(20):
fibonacci.append(sum(fibonacci[-2:])) # OK
print(fibonacci)
def f():
foo = object()
foo.fibonacci = [0, 1]
for i in range(20):
foo.fibonacci.append(sum(foo.fibonacci[-2:])) # OK
print(foo.fibonacci)

View File

@@ -1,13 +0,0 @@
#: E241
a = (1, 2)
#: Okay
b = (1, 20)
#: E242
a = (1, 2) # tab before 2
#: Okay
b = (1, 20) # space before 20
#: E241 E241 E241
# issue 135
more_spaces = [a, b,
ef, +h,
c, -d]

View File

@@ -66,6 +66,3 @@ while 1:
#: E703:2:1
0\
;
#: E701:2:3
a = \
5;

View File

@@ -58,6 +58,3 @@ assert type(res) == type(None)
types = StrEnum
if x == types.X:
pass
#: E721
assert type(res) is int

View File

@@ -634,8 +634,3 @@ def starts_with_this():
@expect('D404: First word of the docstring should not be "This"')
def starts_with_space_then_this():
""" This is a docstring that starts with a space.""" # noqa: D210
class SameLine: """This is a docstring on the same line"""
def same_line(): """This is a docstring on the same line"""

View File

@@ -92,10 +92,3 @@ match *0, 1, *2:
case 0,:
import x
import y
# Test: access a sub-importation via an alias.
import foo.bar as bop
import foo.bar.baz
print(bop.baz.read_csv("test.csv"))

View File

@@ -1,103 +0,0 @@
"""Test type parameters and aliases"""
# Type parameters in type alias statements
from some_module import Bar
type Foo[T] = T # OK
type Foo[T] = list[T] # OK
type Foo[T: ForwardA] = T # OK
type Foo[*Ts] = Bar[Ts] # OK
type Foo[**P] = Bar[P] # OK
class ForwardA: ...
# Types used in aliased assignment must exist
type Foo = DoesNotExist # F821: Undefined name `DoesNotExist`
type Foo = list[DoesNotExist] # F821: Undefined name `DoesNotExist`
# Type parameters do not escape alias scopes
type Foo[T] = T
T # F821: Undefined name `T` - not accessible afterward alias scope
# Type parameters in functions
def foo[T](t: T) -> T: return t # OK
async def afoo[T](t: T) -> T: return t # OK
def with_forward_ref[T: ForwardB](t: T) -> T: return t # OK
def can_access_inside[T](t: T) -> T: # OK
print(T) # OK
return t # OK
class ForwardB: ...
# Type parameters do not escape function scopes
from some_library import some_decorator
@some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
def foo[T](t: T) -> None: ...
T # F821: Undefined name `T` - not accessible afterward function scope
# Type parameters in classes
class Foo[T](list[T]): ... # OK
class UsesForward[T: ForwardC](list[T]): ... # OK
class ForwardC: ...
class WithinBody[T](list[T]): # OK
t = T # OK
x: T # OK
def foo(self, x: T) -> T: # OK
return x
def foo(self):
T # OK
# Type parameters do not escape class scopes
from some_library import some_decorator
@some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
class Foo[T](list[T]): ...
T # F821: Undefined name `T` - not accessible after class scope
# Types specified in bounds should exist
type Foo[T: DoesNotExist] = T # F821: Undefined name `DoesNotExist`
def foo[T: DoesNotExist](t: T) -> T: return t # F821: Undefined name `DoesNotExist`
class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
# Type parameters in nested classes
class Parent[T]:
t = T # OK
def can_use_class_variable(self, x: t) -> t: # OK
return x
class Child:
def can_access_parent_type_parameter(self, x: T) -> T: # OK
T # OK
return x
def cannot_access_parent_variable(self, x: t) -> t: # F821: Undefined name `T`
t # F821: Undefined name `t`
return x
# Type parameters in nested functions
def can_access_inside_nested[T](t: T) -> T: # OK
def bar(x: T) -> T: # OK
T # OK
return x
bar(t)

View File

@@ -63,20 +63,3 @@ def main():
for sys in range(5):
pass
import requests_mock as rm
def requests_mock(requests_mock: rm.Mocker):
print(rm.ANY)
import sklearn.base
import mlflow.sklearn
def f():
import sklearn
mlflow

View File

@@ -147,10 +147,3 @@ def f() -> None:
global CONSTANT
CONSTANT = 1
CONSTANT = 2
def f() -> None:
try:
print("hello")
except A as e :
print("oh no!")

View File

@@ -1,29 +0,0 @@
# pylint: disable=missing-docstring,consider-using-f-string, pointless-statement
## Old style formatting
"%s %z" % ("hello", "world") # [bad-format-character]
"%s" "%z" % ("hello", "world") # [bad-format-character]
"""%s %z""" % ("hello", "world") # [bad-format-character]
"""%s""" """%z""" % ("hello", "world") # [bad-format-character]
## New style formatting
"{:s} {:y}".format("hello", "world") # [bad-format-character]
"{:*^30s}".format("centered")
## f-strings
H, W = "hello", "world"
f"{H} {W}"
f"{H:s} {W:z}" # [bad-format-character]
f"{1:z}" # [bad-format-character]
## False negatives
print(("%" "z") % 1)

View File

@@ -19,10 +19,6 @@ foo in foo
foo not in foo
id(foo) == id(foo)
len(foo) == len(foo)
# Non-errors.
"foo" == "foo" # This is flagged by `comparison-of-constant` instead.
@@ -47,11 +43,3 @@ foo is not bar
foo in bar
foo not in bar
x(foo) == y(foo)
id(foo) == id(bar)
id(foo, bar) == id(foo, bar)
id(foo, bar=1) == id(foo, bar=1)

View File

@@ -1,16 +0,0 @@
class Person:
def __init__(self):
self.name = "monty"
def __eq__(self, other):
return isinstance(other, Person) and other.name == self.name
class Language:
def __init__(self):
self.name = "python"
def __eq__(self, other):
return isinstance(other, Language) and other.name == self.name
def __hash__(self):
return hash(self.name)

View File

@@ -40,4 +40,3 @@ __all__ = __all__ + ["Hello"]
__all__ = __all__ + multiprocessing.__all__
__all__ = list[str](["Hello", "world"])

View File

@@ -19,10 +19,6 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d")
logging.info(msg="Hello %s")
logging.info(msg="Hello %s %s")
import warning
warning.warning("Hello %s %s", "World!")

View File

@@ -15,10 +15,6 @@ logging.error("Example log %s, %s", "foo", "bar", "baz", **kwargs)
# do not handle keyword arguments
logging.error("%(objects)d modifications: %(modifications)d errors: %(errors)d", {"objects": 1, "modifications": 1, "errors": 1})
logging.info(msg="Hello")
logging.info(msg="Hello", something="else")
import warning
warning.warning("Hello %s", "World!", "again")

View File

@@ -32,30 +32,3 @@ print(
)
'{' '0}'.format(1)
args = list(range(10))
kwargs = {x: x for x in range(10)}
"{0}".format(*args)
"{0}".format(**kwargs)
"{0}_{1}".format(*args)
"{0}_{1}".format(1, *args)
"{0}_{1}".format(1, 2, *args)
"{0}_{1}".format(*args, 1, 2)
"{0}_{1}_{2}".format(1, **kwargs)
"{0}_{1}_{2}".format(1, 2, **kwargs)
"{0}_{1}_{2}".format(1, 2, 3, **kwargs)
"{0}_{1}_{2}".format(1, 2, 3, *args, **kwargs)
"{1}_{0}".format(1, 2, *args)
"{1}_{0}".format(1, 2)

View File

@@ -15,17 +15,3 @@ f"{0}".format(1)
print(f"{0}".format(1))
''.format(1)
'{1} {0}'.format(*args)
"{1}_{0}".format(*args, 1)
"{1}_{0}".format(*args, 1, 2)
"{1}_{0}".format(1, **kwargs)
"{1}_{0}".format(1, foo=2)
"{1}_{0}".format(1, 2, **kwargs)
"{1}_{0}".format(1, 2, foo=3, bar=4)

View File

@@ -0,0 +1,28 @@
# These SHOULD change
args = list(range(10))
kwargs = {x: x for x in range(10)}
"{0}".format(*args)
"{0}".format(**kwargs)
"{0}_{1}".format(*args)
"{0}_{1}".format(1, *args)
"{1}_{0}".format(*args)
"{1}_{0}".format(1, *args)
"{0}_{1}".format(1, 2, *args)
"{0}_{1}".format(*args, 1, 2)
"{0}_{1}_{2}".format(1, **kwargs)
"{0}_{1}_{2}".format(1, 2, **kwargs)
"{0}_{1}_{2}".format(1, 2, 3, **kwargs)
"{0}_{1}_{2}".format(1, 2, 3, *args, **kwargs)

View File

@@ -106,7 +106,3 @@ print('Hello %(arg)s' % bar['bop'])
"""
% (x,)
)
"%s" % (
x, # comment
)

View File

@@ -6,12 +6,8 @@
"{1} {0}".format(a, b)
"{0} {1} {0}".format(a, b)
"{x.y}".format(x=z)
"{x} {y} {x}".format(x=a, y=b)
"{.x} {.y}".format(a, b)
"{} {}".format(a.b, c.d)
@@ -76,58 +72,6 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
111111
)
"{a}" "{b}".format(a=1, b=1)
(
"{a}"
"{b}"
).format(a=1, b=1)
(
"{a}"
""
"{b}"
""
).format(a=1, b=1)
(
(
# comment
"{a}"
# comment
"{b}"
)
# comment
.format(a=1, b=1)
)
(
"{a}"
"b"
).format(a=1)
def d(osname, version, release):
return"{}-{}.{}".format(osname, version, release)
def e():
yield"{}".format(1)
assert"{}".format(1)
async def c():
return "{}".format(await 3)
async def c():
return "{}".format(1 + await 3)
"{}".format(1 * 2)
###
# Non-errors
###
@@ -141,6 +85,8 @@ async def c():
"{} {}".format(*a)
"{0} {0}".format(arg)
"{x} {x}".format(arg)
"{x.y} {x.z}".format(arg)
@@ -157,6 +103,8 @@ b"{} {}".format(a, b)
r'"\N{snowman} {}".format(a)'
"{a}" "{b}".format(a=1, b=1)
"123456789 {}".format(
11111111111111111111111111111111111111111111111111111111111111111111111111,
)
@@ -192,13 +140,20 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
]
)
(
"{a}"
"{1 + 2}"
).format(a=1)
async def c():
return "{}".format(await 3)
"{}".format(**c)
"{}".format(
1 # comment
)
async def c():
return "{}".format(1 + await 3)
def d(osname, version, release):
return"{}-{}.{}".format(osname, version, release)
def e():
yield"{}".format(1)
assert"{}".format(1)

View File

@@ -46,35 +46,6 @@ from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet, Con
if True: from collections import (
Mapping, Counter)
# Bad imports from PYI027 that are now handled by PYI022 (UP035)
from typing import ContextManager
from typing import OrderedDict
from typing_extensions import OrderedDict
from typing import Callable
from typing import ByteString
from typing import Container
from typing import Hashable
from typing import ItemsView
from typing import Iterable
from typing import Iterator
from typing import KeysView
from typing import Mapping
from typing import MappingView
from typing import MutableMapping
from typing import MutableSequence
from typing import MutableSet
from typing import Sequence
from typing import Sized
from typing import ValuesView
from typing import Awaitable
from typing import AsyncIterator
from typing import AsyncIterable
from typing import Coroutine
from typing import Collection
from typing import AsyncGenerator
from typing import Reversible
from typing import Generator
# OK
from a import b

View File

@@ -1,3 +1,5 @@
"""A mirror of UP037_1.py, with `from __future__ import annotations`."""
from __future__ import annotations
from typing import (

View File

@@ -0,0 +1,108 @@
"""A mirror of UP037_0.py, without `from __future__ import annotations`."""
from typing import (
Annotated,
Callable,
List,
Literal,
NamedTuple,
Tuple,
TypeVar,
TypedDict,
cast,
)
from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
def foo(var: "MyClass") -> "MyClass":
x: "MyClass"
def foo(*, inplace: "bool"):
pass
def foo(*args: "str", **kwargs: "int"):
pass
x: Tuple["MyClass"]
x: Callable[["MyClass"], None]
class Foo(NamedTuple):
x: "MyClass"
class D(TypedDict):
E: TypedDict("E", foo="int", total=False)
class D(TypedDict):
E: TypedDict("E", {"foo": "int"})
x: Annotated["str", "metadata"]
x: Arg("str", "name")
x: DefaultArg("str", "name")
x: NamedArg("str", "name")
x: DefaultNamedArg("str", "name")
x: DefaultNamedArg("str", name="name")
x: VarArg("str")
x: List[List[List["MyClass"]]]
x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
x: NamedTuple(typename="X", fields=[("foo", "int")])
X: MyCallable("X")
# OK
class D(TypedDict):
E: TypedDict("E")
x: Annotated[()]
x: DefaultNamedArg(name="name", quox="str")
x: DefaultNamedArg(name="name")
x: NamedTuple("X", [("foo",), ("bar",)])
x: NamedTuple("X", ["foo", "bar"])
x: NamedTuple()
x: Literal["foo", "bar"]
x = cast(x, "str")
def foo(x, *args, **kwargs):
...
def foo(*, inplace):
...
x: Annotated[1:2] = ...
x = TypeVar("x", "str", "int")
x = cast("str", x)
X = List["MyClass"]

View File

@@ -1,47 +0,0 @@
import typing
from typing import TypeAlias
# UP040
x: typing.TypeAlias = int
x: TypeAlias = int
# UP040 simple generic
T = typing.TypeVar["T"]
x: typing.TypeAlias = list[T]
# UP040 call style generic
T = typing.TypeVar("T")
x: typing.TypeAlias = list[T]
# UP040 bounded generic (todo)
T = typing.TypeVar("T", bound=int)
x: typing.TypeAlias = list[T]
T = typing.TypeVar("T", int, str)
x: typing.TypeAlias = list[T]
# UP040 contravariant generic (todo)
T = typing.TypeVar("T", contravariant=True)
x: typing.TypeAlias = list[T]
# UP040 covariant generic (todo)
T = typing.TypeVar("T", covariant=True)
x: typing.TypeAlias = list[T]
# UP040 in class scope
T = typing.TypeVar["T"]
class Foo:
# reference to global variable
x: typing.TypeAlias = list[T]
# reference to class variable
TCLS = typing.TypeVar["TCLS"]
y: typing.TypeAlias = list[TCLS]
# UP040 wont add generics in fix
T = typing.TypeVar(*args)
x: typing.TypeAlias = list[T]
# OK
x: TypeAlias
x: int = 1

View File

@@ -11,8 +11,6 @@ class A:
without_annotation = []
class_variable: ClassVar[list[int]] = []
final_variable: Final[list[int]] = []
class_variable_without_subscript: ClassVar = []
final_variable_without_subscript: Final = []
from dataclasses import dataclass, field

View File

@@ -2,9 +2,21 @@ x = range(10)
# RUF015
list(x)[0]
list(x)[:1]
list(x)[:1:1]
list(x)[:1:2]
tuple(x)[0]
tuple(x)[:1]
tuple(x)[:1:1]
tuple(x)[:1:2]
list(i for i in x)[0]
list(i for i in x)[:1]
list(i for i in x)[:1:1]
list(i for i in x)[:1:2]
[i for i in x][0]
[i for i in x][:1]
[i for i in x][:1:1]
[i for i in x][:1:2]
# OK (not indexing (solely) the first element)
list(x)
@@ -17,9 +29,6 @@ list(x)[::]
[i for i in x]
[i for i in x][1]
[i for i in x][-1]
[i for i in x][:1]
[i for i in x][:1:1]
[i for i in x][:1:2]
[i for i in x][1:]
[i for i in x][:3:2]
[i for i in x][::2]

View File

@@ -1,5 +0,0 @@
# ruff: noqa: RUF100
import os # noqa: F401
print(os.sep)

View File

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

View File

@@ -33,7 +33,7 @@ impl<'a, T: Codegen<'a>> CodegenStylist<'a> for T {
///
/// Returns `Ok(None)` if the statement is empty after removing the imports.
pub(crate) fn remove_imports<'a>(
member_names: impl Iterator<Item = &'a str>,
imports: impl Iterator<Item = &'a str>,
stmt: &Stmt,
locator: &Locator,
stylist: &Stylist,
@@ -45,20 +45,27 @@ pub(crate) fn remove_imports<'a>(
bail!("Expected Statement::Simple");
};
let aliases = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => &mut import_body.names,
let (aliases, import_module) = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
Some(SmallStatement::ImportFrom(import_body)) => {
if let ImportNames::Aliases(names) = &mut import_body.names {
names
(
names,
Some((&import_body.relative, import_body.module.as_ref())),
)
} else if let ImportNames::Star(..) = &import_body.names {
// Special-case: if the import is a `from ... import *`, then we delete the
// entire statement.
let mut found_star = false;
for member in member_names {
if member == "*" {
for import in imports {
let qualified_name = match import_body.module.as_ref() {
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
None => "*".to_string(),
};
if import == qualified_name {
found_star = true;
} else {
bail!("Expected \"*\" for unused import (got: \"{}\")", member);
bail!("Expected \"*\" for unused import (got: \"{}\")", import);
}
}
if !found_star {
@@ -75,10 +82,30 @@ pub(crate) fn remove_imports<'a>(
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
for member in member_names {
let alias_index = aliases
.iter()
.position(|alias| member == compose_module_path(&alias.name));
for import in imports {
let alias_index = aliases.iter().position(|alias| {
let qualified_name = match import_module {
Some((relative, module)) => {
let module = module.map(compose_module_path);
let member = compose_module_path(&alias.name);
let mut qualified_name = String::with_capacity(
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
);
for _ in 0..relative.len() {
qualified_name.push('.');
}
if let Some(module) = module {
qualified_name.push_str(&module);
qualified_name.push('.');
}
qualified_name.push_str(&member);
qualified_name
}
None => compose_module_path(&alias.name),
};
qualified_name == import
});
if let Some(index) = alias_index {
aliases.remove(index);
}
@@ -112,7 +139,7 @@ pub(crate) fn remove_imports<'a>(
///
/// Returns the modified import statement.
pub(crate) fn retain_imports(
member_names: &[&str],
imports: &[&str],
stmt: &Stmt,
locator: &Locator,
stylist: &Stylist,
@@ -124,11 +151,14 @@ pub(crate) fn retain_imports(
bail!("Expected Statement::Simple");
};
let aliases = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => &mut import_body.names,
let (aliases, import_module) = match body.body.first_mut() {
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
Some(SmallStatement::ImportFrom(import_body)) => {
if let ImportNames::Aliases(names) = &mut import_body.names {
names
(
names,
Some((&import_body.relative, import_body.module.as_ref())),
)
} else {
bail!("Expected: ImportNames::Aliases");
}
@@ -140,9 +170,28 @@ pub(crate) fn retain_imports(
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
aliases.retain(|alias| {
member_names
.iter()
.any(|member| *member == compose_module_path(&alias.name))
imports.iter().any(|import| {
let qualified_name = match import_module {
Some((relative, module)) => {
let module = module.map(compose_module_path);
let member = compose_module_path(&alias.name);
let mut qualified_name = String::with_capacity(
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
);
for _ in 0..relative.len() {
qualified_name.push('.');
}
if let Some(module) = module {
qualified_name.push_str(&module);
qualified_name.push('.');
}
qualified_name.push_str(&member);
qualified_name
}
None => compose_module_path(&alias.name),
};
qualified_name == *import
})
});
// But avoid destroying any trailing comments.

View File

@@ -1,17 +1,14 @@
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
use anyhow::{bail, Result};
use ruff_python_ast::{self as ast, ExceptHandler, Expr, Keyword, Ranged, Stmt};
use ruff_python_parser::{lexer, Mode};
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_diagnostics::Edit;
use ruff_python_ast::{
self as ast, Arguments, ExceptHandler, Expr, Keyword, PySourceType, Ranged, Stmt,
};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::{lexer, AsMode};
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::autofix::codemods;
@@ -58,115 +55,118 @@ pub(crate) fn delete_stmt(
/// Generate a `Fix` to remove the specified imports from an `import` statement.
pub(crate) fn remove_unused_imports<'a>(
member_names: impl Iterator<Item = &'a str>,
unused_imports: impl Iterator<Item = &'a str>,
stmt: &Stmt,
parent: Option<&Stmt>,
locator: &Locator,
stylist: &Stylist,
indexer: &Indexer,
) -> Result<Edit> {
match codemods::remove_imports(member_names, stmt, locator, stylist)? {
match codemods::remove_imports(unused_imports, stmt, locator, stylist)? {
None => Ok(delete_stmt(stmt, parent, locator, indexer)),
Some(content) => Ok(Edit::range_replacement(content, stmt.range())),
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum Parentheses {
/// Remove parentheses, if the removed argument is the only argument left.
Remove,
/// Preserve parentheses, even if the removed argument is the only argument
Preserve,
}
/// Generic function to remove arguments or keyword arguments in function
/// calls and class definitions. (For classes `args` should be considered
/// `bases`)
///
/// Supports the removal of parentheses when this is the only (kw)arg left.
/// For this behavior, set `remove_parentheses` to `true`.
pub(crate) fn remove_argument<T: Ranged>(
argument: &T,
arguments: &Arguments,
parentheses: Parentheses,
pub(crate) fn remove_argument(
locator: &Locator,
source_type: PySourceType,
call_at: TextSize,
expr_range: TextRange,
args: &[Expr],
keywords: &[Keyword],
remove_parentheses: bool,
) -> Result<Edit> {
// TODO(sbrugman): Preserve trailing comments.
if arguments.keywords.len() + arguments.args.len() > 1 {
let mut fix_start = None;
let mut fix_end = None;
let contents = locator.after(call_at);
if arguments
.args
.iter()
.map(Expr::start)
.chain(arguments.keywords.iter().map(Keyword::start))
.any(|location| location > argument.start())
{
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
// argument to the end of the subsequent comma.
let mut seen_comma = false;
for (tok, range) in lexer::lex_starts_at(
locator.slice(arguments.range()),
source_type.as_mode(),
arguments.start(),
)
.flatten()
{
if seen_comma {
if tok.is_non_logical_newline() {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if tok.is_newline() {
let mut fix_start = None;
let mut fix_end = None;
let n_arguments = keywords.len() + args.len();
if n_arguments == 0 {
bail!("No arguments or keywords to remove");
}
if n_arguments == 1 {
// Case 1: there is only one argument.
let mut count = 0u32;
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if tok.is_lpar() {
if count == 0 {
fix_start = Some(if remove_parentheses {
range.start()
} else {
range.start() + TextSize::from(1)
});
}
count = count.saturating_add(1);
}
if tok.is_rpar() {
count = count.saturating_sub(1);
if count == 0 {
fix_end = Some(if remove_parentheses {
range.end()
} else {
range.start()
range.end() - TextSize::from(1)
});
break;
}
if range.start() == argument.start() {
fix_start = Some(range.start());
}
if fix_start.is_some() && tok.is_comma() {
seen_comma = true;
}
}
} else {
// Case 2: argument or keyword is the last node, so delete from the start of the
// previous comma to the end of the argument.
for (tok, range) in lexer::lex_starts_at(
locator.slice(arguments.range()),
source_type.as_mode(),
arguments.start(),
)
.flatten()
{
if range.start() == argument.start() {
fix_end = Some(argument.end());
break;
}
if tok.is_comma() {
fix_start = Some(range.start());
}
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
} else if args
.iter()
.map(Expr::start)
.chain(keywords.iter().map(Keyword::start))
.any(|location| location > expr_range.start())
{
// Case 2: argument or keyword is _not_ the last node.
let mut seen_comma = false;
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if seen_comma {
if tok.is_non_logical_newline() {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if tok.is_newline() {
range.end()
} else {
range.start()
});
break;
}
if range.start() == expr_range.start() {
fix_start = Some(range.start());
}
if fix_start.is_some() && tok.is_comma() {
seen_comma = true;
}
}
} else {
// Only one argument; remove it (but preserve parentheses, if needed).
Ok(match parentheses {
Parentheses::Remove => Edit::deletion(arguments.start(), arguments.end()),
Parentheses::Preserve => {
Edit::replacement("()".to_string(), arguments.start(), arguments.end())
// Case 3: argument or keyword is the last node, so we have to find the last
// comma in the stmt.
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if range.start() == expr_range.start() {
fix_end = Some(expr_range.end());
break;
}
})
if tok.is_comma() {
fix_start = Some(range.start());
}
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
}
}
}
@@ -179,13 +179,16 @@ fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
match parent {
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
| Stmt::With(ast::StmtWith { body, .. }) => {
| Stmt::With(ast::StmtWith { body, .. })
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
if is_only(body, child) {
return true;
}
}
Stmt::For(ast::StmtFor { body, orelse, .. })
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
if is_only(body, child) || is_only(orelse, child) {
return true;
@@ -291,24 +294,24 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff_python_ast::Ranged;
use ruff_python_parser::parse_suite;
use ruff_source_file::Locator;
use ruff_python_ast::{Ranged, Suite};
use ruff_python_parser::Parse;
use ruff_text_size::TextSize;
use ruff_source_file::Locator;
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};
#[test]
fn find_semicolon() -> Result<()> {
let contents = "x = 1";
let program = parse_suite(contents, "<filename>")?;
let program = Suite::parse(contents, "<filename>")?;
let stmt = program.first().unwrap();
let locator = Locator::new(contents);
assert_eq!(trailing_semicolon(stmt.end(), &locator), None);
let contents = "x = 1; y = 1";
let program = parse_suite(contents, "<filename>")?;
let program = Suite::parse(contents, "<filename>")?;
let stmt = program.first().unwrap();
let locator = Locator::new(contents);
assert_eq!(
@@ -317,7 +320,7 @@ mod tests {
);
let contents = "x = 1 ; y = 1";
let program = parse_suite(contents, "<filename>")?;
let program = Suite::parse(contents, "<filename>")?;
let stmt = program.first().unwrap();
let locator = Locator::new(contents);
assert_eq!(
@@ -330,7 +333,7 @@ x = 1 \
; y = 1
"
.trim();
let program = parse_suite(contents, "<filename>")?;
let program = Suite::parse(contents, "<filename>")?;
let stmt = program.first().unwrap();
let locator = Locator::new(contents);
assert_eq!(

View File

@@ -1,6 +1,6 @@
use itertools::Itertools;
use std::collections::BTreeSet;
use itertools::Itertools;
use ruff_text_size::{TextLen, TextRange, TextSize};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -59,30 +59,35 @@ fn apply_fixes<'a>(
})
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
{
let mut edits = fix
.edits()
.iter()
.filter(|edit| !applied.contains(edit))
.peekable();
// If we already applied an identical fix as part of another correction, skip
// any re-application.
if fix.edits().iter().all(|edit| applied.contains(edit)) {
*fixed.entry(rule).or_default() += 1;
continue;
}
// If the fix contains at least one new edit, enforce isolation and positional requirements.
if let Some(first) = edits.peek() {
// If this fix requires isolation, and we've already applied another fix in the
// same isolation group, skip it.
if let IsolationLevel::Group(id) = fix.isolation() {
if !isolated.insert(id) {
continue;
}
}
// Best-effort approach: if this fix overlaps with a fix we've already applied,
// skip it.
if last_pos.map_or(false, |last_pos| {
fix.min_start()
.map_or(false, |fix_location| last_pos >= fix_location)
}) {
continue;
}
// If this fix overlaps with a fix we've already applied, skip it.
if last_pos.is_some_and(|last_pos| last_pos >= first.start()) {
// If this fix requires isolation, and we've already applied another fix in the
// same isolation group, skip it.
if let IsolationLevel::Group(id) = fix.isolation() {
if !isolated.insert(id) {
continue;
}
}
let mut applied_edits = Vec::with_capacity(fix.edits().len());
for edit in edits {
for edit in fix
.edits()
.iter()
.sorted_unstable_by_key(|edit| edit.start())
{
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice(TextRange::new(last_pos.unwrap_or_default(), edit.start()));
output.push_str(slice);
@@ -98,10 +103,9 @@ fn apply_fixes<'a>(
// Track that the edit was applied.
last_pos = Some(edit.end());
applied_edits.push(edit);
applied.insert(edit);
}
applied.extend(applied_edits.drain(..));
*fixed.entry(rule).or_default() += 1;
}

View File

@@ -1,28 +1,27 @@
use ruff_python_ast::{Parameter, Ranged};
use ruff_python_ast::{Arg, Ranged};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_builtins, pep8_naming, pycodestyle};
/// Run lint rules over a [`Parameter`] syntax node.
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
/// Run lint rules over an [`Arg`] syntax node.
pub(crate) fn argument(arg: &Arg, checker: &mut Checker) {
if checker.enabled(Rule::AmbiguousVariableName) {
if let Some(diagnostic) =
pycodestyle::rules::ambiguous_variable_name(&parameter.name, parameter.range())
if let Some(diagnostic) = pycodestyle::rules::ambiguous_variable_name(&arg.arg, arg.range())
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::InvalidArgumentName) {
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(
&parameter.name,
parameter,
&arg.arg,
arg,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::BuiltinArgumentShadowing) {
flake8_builtins::rules::builtin_argument_shadowing(checker, parameter);
flake8_builtins::rules::builtin_argument_shadowing(checker, arg);
}
}

View File

@@ -1,26 +1,26 @@
use ruff_python_ast::Parameters;
use ruff_python_ast::Arguments;
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
/// Run lint rules over a [`Parameters`] syntax node.
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
/// Run lint rules over a [`Arguments`] syntax node.
pub(crate) fn arguments(arguments: &Arguments, checker: &mut Checker) {
if checker.enabled(Rule::MutableArgumentDefault) {
flake8_bugbear::rules::mutable_argument_default(checker, parameters);
flake8_bugbear::rules::mutable_argument_default(checker, arguments);
}
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
flake8_bugbear::rules::function_call_in_argument_default(checker, arguments);
}
if checker.settings.rules.enabled(Rule::ImplicitOptional) {
ruff::rules::implicit_optional(checker, parameters);
ruff::rules::implicit_optional(checker, arguments);
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.enabled(Rule::TypedArgumentDefaultInStub) {
flake8_pyi::rules::typed_argument_simple_defaults(checker, parameters);
flake8_pyi::rules::typed_argument_simple_defaults(checker, arguments);
}
if checker.enabled(Rule::ArgumentDefaultInStub) {
flake8_pyi::rules::argument_simple_defaults(checker, parameters);
flake8_pyi::rules::argument_simple_defaults(checker, arguments);
}
}
}

View File

@@ -1,8 +1,7 @@
use ruff_diagnostics::{Diagnostic, Fix};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint};
use ruff_diagnostics::{Diagnostic, Fix};
/// Run lint rules over the [`Binding`]s.
pub(crate) fn bindings(checker: &mut Checker) {
@@ -11,7 +10,9 @@ pub(crate) fn bindings(checker: &mut Checker) {
Rule::InvalidAllObject,
Rule::UnaliasedCollectionsAbcSetImport,
Rule::UnconventionalImportAlias,
Rule::UnusedPrivateTypeVar,
Rule::UnusedVariable,
Rule::UnusedPrivateProtocol,
]) {
return;
}
@@ -56,11 +57,27 @@ pub(crate) fn bindings(checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
if let Some(diagnostic) =
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding)
{
checker.diagnostics.push(diagnostic);
if checker.is_stub {
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
if let Some(diagnostic) =
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::UnusedPrivateTypeVar) {
if let Some(diagnostic) =
flake8_pyi::rules::unused_private_type_var(checker, binding)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::UnusedPrivateProtocol) {
if let Some(diagnostic) =
flake8_pyi::rules::unused_private_protocol(checker, binding)
{
checker.diagnostics.push(diagnostic);
}
}
}
}

View File

@@ -11,18 +11,21 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
for snapshot in for_loops {
checker.semantic.restore(snapshot);
let Stmt::For(ast::StmtFor {
if let Stmt::For(ast::StmtFor {
target, iter, body, ..
}) = checker.semantic.current_statement()
else {
unreachable!("Expected Stmt::For");
};
if checker.enabled(Rule::UnusedLoopControlVariable) {
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
}
if checker.enabled(Rule::IncorrectDictIterator) {
perflint::rules::incorrect_dict_iterator(checker, target, iter);
})
| Stmt::AsyncFor(ast::StmtAsyncFor {
target, iter, body, ..
}) = &checker.semantic.stmt()
{
if checker.enabled(Rule::UnusedLoopControlVariable) {
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
}
if checker.enabled(Rule::IncorrectDictIterator) {
perflint::rules::incorrect_dict_iterator(checker, target, iter);
}
} else {
unreachable!("Expected Expr::For | Expr::AsyncFor");
}
}
}

View File

@@ -1,10 +1,11 @@
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::cast;
use ruff_python_semantic::analyze::{branch_detection, visibility};
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint};
use crate::rules::{flake8_type_checking, flake8_unused_arguments, pyflakes, pylint};
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
pub(crate) fn deferred_scopes(checker: &mut Checker) {
@@ -23,10 +24,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
Rule::UnusedImport,
Rule::UnusedLambdaArgument,
Rule::UnusedMethodArgument,
Rule::UnusedPrivateProtocol,
Rule::UnusedPrivateTypeAlias,
Rule::UnusedPrivateTypeVar,
Rule::UnusedPrivateTypedDict,
Rule::UnusedStaticMethodArgument,
Rule::UnusedVariable,
]) {
@@ -36,7 +33,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// Identify any valid runtime imports. If a module is imported at runtime, and
// used at runtime, then by default, we avoid flagging any other
// imports from that model as typing-only.
let enforce_typing_imports = !checker.source_type.is_stub()
let enforce_typing_imports = !checker.is_stub
&& checker.any_enabled(&[
Rule::RuntimeImportInTypeCheckingBlock,
Rule::TypingOnlyFirstPartyImport,
@@ -111,11 +108,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| {
binding.source.map_or(true, |right| {
branch_detection::different_forks(
left,
right,
checker.semantic.statements(),
)
branch_detection::different_forks(left, right, &checker.semantic.stmts)
})
}) {
continue;
@@ -171,25 +164,16 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
continue;
}
let Some(statement_id) = shadowed.source else {
continue;
};
// If this is an overloaded function, abort.
if shadowed.kind.is_function_definition() {
if checker
.semantic
.statement(statement_id)
.as_function_def_stmt()
.is_some_and(|function| {
visibility::is_overload(
&function.decorator_list,
&checker.semantic,
)
})
{
continue;
}
if shadowed.kind.is_function_definition()
&& visibility::is_overload(
cast::decorator_list(
checker.semantic.stmts[shadowed.source.unwrap()],
),
&checker.semantic,
)
{
continue;
}
} else {
// Only enforce cross-scope shadowing for imports.
@@ -207,11 +191,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| {
binding.source.map_or(true, |right| {
branch_detection::different_forks(
left,
right,
checker.semantic.statements(),
)
branch_detection::different_forks(left, right, &checker.semantic.stmts)
})
}) {
continue;
@@ -234,20 +214,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
}
}
if checker.enabled(Rule::UnusedPrivateTypeVar) {
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateProtocol) {
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
}
if checker.enabled(Rule::UnusedPrivateTypedDict) {
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
}
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
if matches!(
scope.kind,
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Lambda(_)
) {
if checker.enabled(Rule::UnusedVariable) {
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
}
@@ -256,7 +226,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
pyflakes::rules::unused_annotation(checker, scope, &mut diagnostics);
}
if !checker.source_type.is_stub() {
if !checker.is_stub {
if checker.any_enabled(&[
Rule::UnusedClassMethodArgument,
Rule::UnusedFunctionArgument,
@@ -273,7 +243,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
}
}
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
if matches!(
scope.kind,
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Module
) {
if enforce_typing_imports {
let runtime_imports: Vec<&Binding> = checker
.semantic

View File

@@ -30,8 +30,8 @@ pub(crate) fn definitions(checker: &mut Checker) {
Rule::MissingTypeKwargs,
Rule::MissingTypeSelf,
]);
let enforce_stubs = checker.source_type.is_stub() && checker.enabled(Rule::DocstringInStub);
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
let enforce_stubs = checker.is_stub
&& checker.any_enabled(&[Rule::DocstringInStub, Rule::IterMethodReturnIterable]);
let enforce_docstrings = checker.any_enabled(&[
Rule::BlankLineAfterLastSection,
Rule::BlankLineAfterSummary,
@@ -81,7 +81,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
Rule::UndocumentedPublicPackage,
]);
if !enforce_annotations && !enforce_docstrings && !enforce_stubs && !enforce_stubs_and_runtime {
if !enforce_annotations && !enforce_docstrings && !enforce_stubs {
return;
}
@@ -117,7 +117,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
// interfaces, without any AST nodes in between. Right now, we
// only error when traversing definition boundaries (functions,
// classes, etc.).
if !overloaded_name.is_some_and(|overloaded_name| {
if !overloaded_name.map_or(false, |overloaded_name| {
flake8_annotations::helpers::is_overload_impl(
definition,
&overloaded_name,
@@ -141,8 +141,6 @@ pub(crate) fn definitions(checker: &mut Checker) {
if checker.enabled(Rule::DocstringInStub) {
flake8_pyi::rules::docstring_in_stubs(checker, docstring);
}
}
if enforce_stubs_and_runtime {
if checker.enabled(Rule::IterMethodReturnIterable) {
flake8_pyi::rules::iter_method_return_iterable(checker, definition);
}

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator, Ranged};
use ruff_python_ast::{self as ast, Constant, Expr, ExprContext, Operator, Ranged};
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
use ruff_diagnostics::Diagnostic;
@@ -31,7 +31,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic)
{
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.source_type.is_stub()
if !checker.is_stub
&& checker.settings.target_version < PythonVersion::Py310
&& checker.settings.target_version >= PythonVersion::Py37
&& !checker.semantic.future_annotations()
@@ -44,7 +44,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::NonPEP604Annotation) {
if checker.source_type.is_stub()
if checker.is_stub
|| checker.settings.target_version >= PythonVersion::Py310
|| (checker.settings.target_version >= PythonVersion::Py37
&& checker.semantic.future_annotations()
@@ -59,7 +59,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
// Ex) list[...]
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
if !checker.source_type.is_stub()
if !checker.is_stub
&& checker.settings.target_version < PythonVersion::Py39
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
@@ -74,27 +74,24 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
// Ex) Union[...]
if checker.any_enabled(&[
Rule::UnnecessaryLiteralUnion,
Rule::DuplicateUnionMember,
Rule::RedundantLiteralUnion,
Rule::UnnecessaryTypeUnion,
]) {
// Avoid duplicate checks if the parent is a union, since these rules already
// traverse nested unions.
if !checker.semantic.in_nested_union() {
if checker.any_enabled(&[Rule::UnnecessaryLiteralUnion, Rule::DuplicateUnionMember]) {
// Determine if the current expression is an union
// Avoid duplicate checks if the parent is an `Union[...]` since these rules traverse nested unions
let is_unchecked_union = checker
.semantic
.expr_grandparent()
.and_then(Expr::as_subscript_expr)
.map_or(true, |parent| {
!checker.semantic.match_typing_expr(&parent.value, "Union")
});
if is_unchecked_union {
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
}
if checker.enabled(Rule::DuplicateUnionMember) {
flake8_pyi::rules::duplicate_union_member(checker, expr);
}
if checker.enabled(Rule::RedundantLiteralUnion) {
flake8_pyi::rules::redundant_literal_union(checker, expr);
}
if checker.enabled(Rule::UnnecessaryTypeUnion) {
flake8_pyi::rules::unnecessary_type_union(checker, expr);
}
}
}
@@ -155,8 +152,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::NumpyDeprecatedFunction) {
numpy::rules::deprecated_function(checker, expr);
}
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
if checker.is_stub {
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
}
// Ex) List[...]
@@ -168,18 +167,20 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
typing::to_pep585_generic(expr, &checker.semantic)
{
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.source_type.is_stub()
if !checker.is_stub
&& checker.settings.target_version < PythonVersion::Py39
&& checker.settings.target_version >= PythonVersion::Py37
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
&& !checker.settings.pyupgrade.keep_runtime_typing
{
flake8_future_annotations::rules::future_rewritable_type_annotation(checker, expr);
flake8_future_annotations::rules::future_rewritable_type_annotation(
checker, expr,
);
}
}
if checker.enabled(Rule::NonPEP585Annotation) {
if checker.source_type.is_stub()
if checker.is_stub
|| checker.settings.target_version >= PythonVersion::Py39
|| (checker.settings.target_version >= PythonVersion::Py37
&& checker.semantic.future_annotations()
@@ -198,13 +199,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
ExprContext::Store => {
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
if checker.semantic.current_scope().kind.is_function() {
if checker.semantic.scope().kind.is_any_function() {
// Ignore globals.
if !checker
.semantic
.current_scope()
.scope()
.get(id)
.is_some_and(|binding_id| {
.map_or(false, |binding_id| {
checker.semantic.binding(binding_id).is_global()
})
{
@@ -215,19 +216,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
&checker.semantic.current_scope().kind
if let ScopeKind::Class(ast::StmtClassDef { bases, .. }) =
&checker.semantic.scope().kind
{
pep8_naming::rules::mixed_case_variable_in_class_scope(
checker,
expr,
id,
arguments.as_deref(),
checker, expr, id, bases,
);
}
}
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
if matches!(checker.semantic.scope().kind, ScopeKind::Module) {
pep8_naming::rules::mixed_case_variable_in_global_scope(
checker, expr, id,
);
@@ -240,7 +238,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_attribute_shadowing(
checker, class_def, id, *range,
@@ -269,7 +267,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
]) {
if let Some(replacement) = typing::to_pep585_generic(expr, &checker.semantic) {
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
if !checker.source_type.is_stub()
if !checker.is_stub
&& checker.settings.target_version < PythonVersion::Py39
&& checker.settings.target_version >= PythonVersion::Py37
&& !checker.semantic.future_annotations()
@@ -282,7 +280,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::NonPEP585Annotation) {
if checker.source_type.is_stub()
if checker.is_stub
|| checker.settings.target_version >= PythonVersion::Py39
|| (checker.settings.target_version >= PythonVersion::Py37
&& checker.semantic.future_annotations()
@@ -320,26 +318,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::PrivateMemberAccess) {
flake8_self::rules::private_member_access(checker, expr);
}
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
if checker.is_stub {
if checker.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(checker, expr);
}
}
pandas_vet::rules::attr(checker, attr, value, expr);
}
Expr::Call(
call @ ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
args,
keywords,
range: _,
},
) => {
if checker.any_enabled(&[
// pylint
Rule::BadStringFormatCharacter,
// pyflakes
Rule::StringDotFormatInvalidFormat,
Rule::StringDotFormatExtraNamedArguments,
@@ -388,8 +382,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
.enabled(Rule::StringDotFormatExtraPositionalArguments)
{
pyflakes::rules::string_dot_format_extra_positional_arguments(
checker, &summary, args, location,
);
checker,
&summary, args, location,
);
}
if checker.enabled(Rule::StringDotFormatMissingArguments) {
pyflakes::rules::string_dot_format_missing_argument(
@@ -402,7 +397,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
);
}
if checker.enabled(Rule::FormatLiterals) {
pyupgrade::rules::format_literals(checker, &summary, call);
pyupgrade::rules::format_literals(checker, &summary, expr);
}
if checker.enabled(Rule::FString) {
pyupgrade::rules::f_strings(
@@ -415,14 +410,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
}
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::call(
checker,
val.as_str(),
location,
);
}
}
}
}
@@ -437,10 +424,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::super_call_with_parameters(checker, expr, func, args);
}
if checker.enabled(Rule::UnnecessaryEncodeUTF8) {
pyupgrade::rules::unnecessary_encode_utf8(checker, call);
pyupgrade::rules::unnecessary_encode_utf8(checker, expr, func, args, keywords);
}
if checker.enabled(Rule::RedundantOpenModes) {
pyupgrade::rules::redundant_open_modes(checker, call);
pyupgrade::rules::redundant_open_modes(checker, expr);
}
if checker.enabled(Rule::NativeLiterals) {
pyupgrade::rules::native_literals(checker, expr, func, args, keywords);
@@ -449,10 +436,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::open_alias(checker, expr, func);
}
if checker.enabled(Rule::ReplaceUniversalNewlines) {
pyupgrade::rules::replace_universal_newlines(checker, call);
pyupgrade::rules::replace_universal_newlines(checker, func, keywords);
}
if checker.enabled(Rule::ReplaceStdoutStderr) {
pyupgrade::rules::replace_stdout_stderr(checker, call);
pyupgrade::rules::replace_stdout_stderr(checker, expr, func, args, keywords);
}
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(checker, func);
@@ -472,7 +459,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_async::rules::blocking_os_call(checker, expr);
}
if checker.any_enabled(&[Rule::Print, Rule::PPrint]) {
flake8_print::rules::print_call(checker, call);
flake8_print::rules::print_call(checker, func, keywords);
}
if checker.any_enabled(&[
Rule::SuspiciousPickleUsage,
@@ -524,11 +511,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
if checker.enabled(Rule::ZipWithoutExplicitStrict) {
if checker.settings.target_version >= PythonVersion::Py310 {
flake8_bugbear::rules::zip_without_explicit_strict(checker, call);
flake8_bugbear::rules::zip_without_explicit_strict(
checker, expr, func, args, keywords,
);
}
}
if checker.enabled(Rule::NoExplicitStacklevel) {
flake8_bugbear::rules::no_explicit_stacklevel(checker, call);
flake8_bugbear::rules::no_explicit_stacklevel(checker, func, keywords);
}
if checker.enabled(Rule::UnnecessaryDictKwargs) {
flake8_pie::rules::unnecessary_dict_kwargs(checker, expr, keywords);
@@ -537,22 +526,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_bandit::rules::exec_used(checker, func);
}
if checker.enabled(Rule::BadFilePermissions) {
flake8_bandit::rules::bad_file_permissions(checker, call);
flake8_bandit::rules::bad_file_permissions(checker, func, args, keywords);
}
if checker.enabled(Rule::RequestWithNoCertValidation) {
flake8_bandit::rules::request_with_no_cert_validation(checker, call);
flake8_bandit::rules::request_with_no_cert_validation(checker, func, keywords);
}
if checker.enabled(Rule::UnsafeYAMLLoad) {
flake8_bandit::rules::unsafe_yaml_load(checker, call);
flake8_bandit::rules::unsafe_yaml_load(checker, func, args, keywords);
}
if checker.enabled(Rule::SnmpInsecureVersion) {
flake8_bandit::rules::snmp_insecure_version(checker, call);
flake8_bandit::rules::snmp_insecure_version(checker, func, keywords);
}
if checker.enabled(Rule::SnmpWeakCryptography) {
flake8_bandit::rules::snmp_weak_cryptography(checker, call);
flake8_bandit::rules::snmp_weak_cryptography(checker, func, args, keywords);
}
if checker.enabled(Rule::Jinja2AutoescapeFalse) {
flake8_bandit::rules::jinja2_autoescape_false(checker, call);
flake8_bandit::rules::jinja2_autoescape_false(checker, func, keywords);
}
if checker.enabled(Rule::HardcodedPasswordFuncArg) {
flake8_bandit::rules::hardcoded_password_func_arg(checker, keywords);
@@ -561,16 +550,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_bandit::rules::hardcoded_sql_expression(checker, expr);
}
if checker.enabled(Rule::HashlibInsecureHashFunction) {
flake8_bandit::rules::hashlib_insecure_hash_functions(checker, call);
flake8_bandit::rules::hashlib_insecure_hash_functions(
checker, func, args, keywords,
);
}
if checker.enabled(Rule::RequestWithoutTimeout) {
flake8_bandit::rules::request_without_timeout(checker, call);
flake8_bandit::rules::request_without_timeout(checker, func, keywords);
}
if checker.enabled(Rule::ParamikoCall) {
flake8_bandit::rules::paramiko_call(checker, func);
}
if checker.enabled(Rule::LoggingConfigInsecureListen) {
flake8_bandit::rules::logging_config_insecure_listen(checker, call);
flake8_bandit::rules::logging_config_insecure_listen(checker, func, keywords);
}
if checker.any_enabled(&[
Rule::SubprocessWithoutShellEqualsTrue,
@@ -581,7 +572,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Rule::StartProcessWithPartialPath,
Rule::UnixCommandWildcardInjection,
]) {
flake8_bandit::rules::shell_injection(checker, call);
flake8_bandit::rules::shell_injection(checker, func, args, keywords);
}
if checker.enabled(Rule::UnnecessaryGeneratorList) {
flake8_comprehensions::rules::unnecessary_generator_list(
@@ -665,7 +656,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_comprehensions::rules::unnecessary_map(
checker,
expr,
checker.semantic.current_expression_parent(),
checker.semantic.expr_parent(),
func,
args,
);
@@ -684,17 +675,23 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_debugger::rules::debugger_call(checker, expr, func);
}
if checker.enabled(Rule::PandasUseOfInplaceArgument) {
pandas_vet::rules::inplace_argument(checker, call);
pandas_vet::rules::inplace_argument(checker, expr, func, args, keywords);
}
pandas_vet::rules::call(checker, func);
if checker.enabled(Rule::PandasUseOfDotReadTable) {
pandas_vet::rules::use_of_read_table(checker, call);
pandas_vet::rules::use_of_read_table(checker, func, keywords);
}
if checker.enabled(Rule::PandasUseOfPdMerge) {
pandas_vet::rules::use_of_pd_merge(checker, func);
}
if checker.enabled(Rule::CallDatetimeWithoutTzinfo) {
flake8_datetimez::rules::call_datetime_without_tzinfo(checker, call);
flake8_datetimez::rules::call_datetime_without_tzinfo(
checker,
func,
args,
keywords,
expr.range(),
);
}
if checker.enabled(Rule::CallDatetimeToday) {
flake8_datetimez::rules::call_datetime_today(checker, func, expr.range());
@@ -710,13 +707,30 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
);
}
if checker.enabled(Rule::CallDatetimeNowWithoutTzinfo) {
flake8_datetimez::rules::call_datetime_now_without_tzinfo(checker, call);
flake8_datetimez::rules::call_datetime_now_without_tzinfo(
checker,
func,
args,
keywords,
expr.range(),
);
}
if checker.enabled(Rule::CallDatetimeFromtimestamp) {
flake8_datetimez::rules::call_datetime_fromtimestamp(checker, call);
flake8_datetimez::rules::call_datetime_fromtimestamp(
checker,
func,
args,
keywords,
expr.range(),
);
}
if checker.enabled(Rule::CallDatetimeStrptimeWithoutZone) {
flake8_datetimez::rules::call_datetime_strptime_without_zone(checker, call);
flake8_datetimez::rules::call_datetime_strptime_without_zone(
checker,
func,
args,
expr.range(),
);
}
if checker.enabled(Rule::CallDateToday) {
flake8_datetimez::rules::call_date_today(checker, func, expr.range());
@@ -740,16 +754,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pylint::rules::bad_str_strip_call(checker, func, args);
}
if checker.enabled(Rule::InvalidEnvvarDefault) {
pylint::rules::invalid_envvar_default(checker, call);
pylint::rules::invalid_envvar_default(checker, func, args, keywords);
}
if checker.enabled(Rule::InvalidEnvvarValue) {
pylint::rules::invalid_envvar_value(checker, call);
pylint::rules::invalid_envvar_value(checker, func, args, keywords);
}
if checker.enabled(Rule::NestedMinMax) {
pylint::rules::nested_min_max(checker, expr, func, args, keywords);
}
if checker.enabled(Rule::PytestPatchWithLambda) {
if let Some(diagnostic) = flake8_pytest_style::rules::patch_with_lambda(call) {
if let Some(diagnostic) =
flake8_pytest_style::rules::patch_with_lambda(func, args, keywords)
{
checker.diagnostics.push(diagnostic);
}
}
@@ -761,16 +777,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::SubprocessPopenPreexecFn) {
pylint::rules::subprocess_popen_preexec_fn(checker, call);
pylint::rules::subprocess_popen_preexec_fn(checker, func, keywords);
}
if checker.any_enabled(&[
Rule::PytestRaisesWithoutException,
Rule::PytestRaisesTooBroad,
]) {
flake8_pytest_style::rules::raises_call(checker, call);
flake8_pytest_style::rules::raises_call(checker, func, args, keywords);
}
if checker.enabled(Rule::PytestFailWithoutMessage) {
flake8_pytest_style::rules::fail_call(checker, call);
flake8_pytest_style::rules::fail_call(checker, func, args, keywords);
}
if checker.enabled(Rule::PairwiseOverZipped) {
if checker.settings.target_version >= PythonVersion::Py310 {
@@ -841,7 +857,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_use_pathlib::rules::path_constructor_current_directory(checker, expr, func);
}
if checker.enabled(Rule::OsSepSplit) {
flake8_use_pathlib::rules::os_sep_split(checker, call);
flake8_use_pathlib::rules::os_sep_split(checker, func, args, keywords);
}
if checker.enabled(Rule::NumpyLegacyRandom) {
numpy::rules::legacy_random(checker, func);
@@ -856,15 +872,15 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Rule::LoggingExcInfo,
Rule::LoggingRedundantExcInfo,
]) {
flake8_logging_format::rules::logging_call(checker, call);
flake8_logging_format::rules::logging_call(checker, func, args, keywords);
}
if checker.any_enabled(&[Rule::LoggingTooFewArgs, Rule::LoggingTooManyArgs]) {
pylint::rules::logging_call(checker, call);
pylint::rules::logging_call(checker, func, args, keywords);
}
if checker.enabled(Rule::DjangoLocalsInRenderFunction) {
flake8_django::rules::locals_in_render_function(checker, call);
flake8_django::rules::locals_in_render_function(checker, func, args, keywords);
}
if checker.enabled(Rule::UnsupportedMethodCallOnAll) {
if checker.is_stub && checker.enabled(Rule::UnsupportedMethodCallOnAll) {
flake8_pyi::rules::unsupported_method_call_on_all(checker, func);
}
}
@@ -915,7 +931,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pylint::rules::await_outside_async(checker, expr);
}
}
Expr::FString(ast::ExprFString { values, range: _ }) => {
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
if checker.enabled(Rule::FStringMissingPlaceholders) {
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
}
@@ -1029,9 +1045,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker.locator,
);
}
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::percent(checker, expr);
}
if checker.enabled(Rule::BadStringFormatType) {
pylint::rules::bad_string_format_type(checker, expr, right);
}
@@ -1063,7 +1076,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}) => {
// Ex) `str | None`
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
if !checker.source_type.is_stub()
if !checker.is_stub
&& checker.settings.target_version < PythonVersion::Py310
&& !checker.semantic.future_annotations()
&& checker.semantic.in_annotation()
@@ -1075,24 +1088,26 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
);
}
}
// Avoid duplicate checks if the parent is a union, since these rules already
// traverse nested unions.
if !checker.semantic.in_nested_union() {
if checker.is_stub {
if checker.enabled(Rule::DuplicateUnionMember)
&& checker.semantic.in_type_definition()
&& checker.semantic.in_type_definition()
// Avoid duplicate checks if the parent is an `|`
&& !matches!(
checker.semantic.expr_parent(),
Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..}))
)
{
flake8_pyi::rules::duplicate_union_member(checker, expr);
}
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
if checker.enabled(Rule::UnnecessaryLiteralUnion)
// Avoid duplicate checks if the parent is an `|`
&& !matches!(
checker.semantic.expr_parent(),
Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..}))
)
{
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
}
if checker.enabled(Rule::RedundantLiteralUnion) {
flake8_pyi::rules::redundant_literal_union(checker, expr);
}
if checker.enabled(Rule::UnnecessaryTypeUnion) {
flake8_pyi::rules::unnecessary_type_union(checker, expr);
}
}
}
Expr::UnaryOp(ast::ExprUnaryOp {
@@ -1127,14 +1142,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_simplify::rules::double_negation(checker, expr, *op, operand);
}
}
Expr::Compare(
compare @ ast::ExprCompare {
left,
ops,
comparators,
range: _,
},
) => {
Expr::Compare(ast::ExprCompare {
left,
ops,
comparators,
range: _,
}) => {
let check_none_comparisons = checker.enabled(Rule::NoneComparison);
let check_true_false_comparisons = checker.enabled(Rule::TrueFalseComparison);
if check_none_comparisons || check_true_false_comparisons {
@@ -1152,7 +1165,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
}
if checker.enabled(Rule::TypeComparison) {
pycodestyle::rules::type_comparison(checker, compare);
pycodestyle::rules::type_comparison(checker, expr, ops, comparators);
}
if checker.any_enabled(&[
Rule::SysVersionCmpStr3,
@@ -1203,7 +1216,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
kind: _,
range: _,
}) => {
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
if checker.is_stub && checker.enabled(Rule::NumericLiteralTooLong) {
flake8_pyi::rules::numeric_literal_too_long(checker, expr);
}
}
@@ -1212,7 +1225,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
kind: _,
range: _,
}) => {
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
if checker.is_stub && checker.enabled(Rule::StringOrBytesTooLong) {
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
}
}
@@ -1229,12 +1242,18 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
if checker.enabled(Rule::HardcodedTempFile) {
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
if let Some(diagnostic) = flake8_bandit::rules::hardcoded_tmp_directory(
expr,
value,
&checker.settings.flake8_bandit.hardcoded_tmp_directory,
) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::UnicodeKindPrefix) {
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.enabled(Rule::StringOrBytesTooLong) {
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
}
@@ -1242,7 +1261,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
Expr::Lambda(
lambda @ ast::ExprLambda {
parameters: _,
args: _,
body: _,
range: _,
},

View File

@@ -1,3 +1,5 @@
pub(super) use argument::argument;
pub(super) use arguments::arguments;
pub(super) use bindings::bindings;
pub(super) use comprehension::comprehension;
pub(super) use deferred_for_loops::deferred_for_loops;
@@ -6,12 +8,12 @@ pub(super) use definitions::definitions;
pub(super) use except_handler::except_handler;
pub(super) use expression::expression;
pub(super) use module::module;
pub(super) use parameter::parameter;
pub(super) use parameters::parameters;
pub(super) use statement::statement;
pub(super) use suite::suite;
pub(super) use unresolved_references::unresolved_references;
mod argument;
mod arguments;
mod bindings;
mod comprehension;
mod deferred_for_loops;
@@ -20,8 +22,6 @@ mod definitions;
mod except_handler;
mod expression;
mod module;
mod parameter;
mod parameters;
mod statement;
mod suite;
mod unresolved_references;

View File

@@ -53,7 +53,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::BreakOutsideLoop) {
if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
stmt,
&mut checker.semantic.current_statements().skip(1),
&mut checker.semantic.parents().skip(1),
) {
checker.diagnostics.push(diagnostic);
}
@@ -63,21 +63,27 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ContinueOutsideLoop) {
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
stmt,
&mut checker.semantic.current_statements().skip(1),
&mut checker.semantic.parents().skip(1),
) {
checker.diagnostics.push(diagnostic);
}
}
}
Stmt::FunctionDef(ast::StmtFunctionDef {
is_async,
name,
decorator_list,
returns,
parameters,
args,
body,
type_params,
range: _,
..
})
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
name,
decorator_list,
returns,
args,
body,
..
}) => {
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
@@ -105,10 +111,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
pep8_naming::rules::invalid_first_argument_name_for_class_method(
checker,
checker.semantic.current_scope(),
checker.semantic.scope(),
name,
decorator_list,
parameters,
args,
)
{
checker.diagnostics.push(diagnostic);
@@ -117,15 +123,15 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
checker,
checker.semantic.current_scope(),
checker.semantic.scope(),
name,
decorator_list,
parameters,
args,
) {
checker.diagnostics.push(diagnostic);
}
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.enabled(Rule::PassStatementStubBody) {
flake8_pyi::rules::pass_statement_stub_body(checker, body);
}
@@ -135,52 +141,41 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::StubBodyMultipleStatements) {
flake8_pyi::rules::stub_body_multiple_statements(checker, stmt, body);
}
}
if checker.enabled(Rule::AnyEqNeAnnotation) {
flake8_pyi::rules::any_eq_ne_annotation(checker, name, parameters);
}
if checker.enabled(Rule::NonSelfReturnType) {
flake8_pyi::rules::non_self_return_type(
checker,
stmt,
*is_async,
name,
decorator_list,
returns.as_ref().map(AsRef::as_ref),
parameters,
);
}
if checker.enabled(Rule::CustomTypeVarReturnType) {
flake8_pyi::rules::custom_type_var_return_type(
checker,
name,
decorator_list,
returns.as_ref().map(AsRef::as_ref),
parameters,
type_params.as_ref(),
);
}
if checker.source_type.is_stub() {
if checker.enabled(Rule::AnyEqNeAnnotation) {
flake8_pyi::rules::any_eq_ne_annotation(checker, name, args);
}
if checker.enabled(Rule::NonSelfReturnType) {
flake8_pyi::rules::non_self_return_type(
checker,
stmt,
name,
decorator_list,
returns.as_ref().map(AsRef::as_ref),
args,
stmt.is_async_function_def_stmt(),
);
}
if checker.enabled(Rule::StrOrReprDefinedInStub) {
flake8_pyi::rules::str_or_repr_defined_in_stub(checker, stmt);
}
}
if checker.source_type.is_stub()
|| checker.settings.target_version >= PythonVersion::Py311
{
if checker.enabled(Rule::NoReturnArgumentAnnotationInStub) {
flake8_pyi::rules::no_return_argument_annotation(checker, parameters);
flake8_pyi::rules::no_return_argument_annotation(checker, args);
}
if checker.enabled(Rule::BadExitAnnotation) {
flake8_pyi::rules::bad_exit_annotation(
checker,
stmt.is_async_function_def_stmt(),
name,
args,
);
}
if checker.enabled(Rule::RedundantNumericUnion) {
flake8_pyi::rules::redundant_numeric_union(checker, args);
}
}
if checker.enabled(Rule::BadExitAnnotation) {
flake8_pyi::rules::bad_exit_annotation(checker, *is_async, name, parameters);
}
if checker.enabled(Rule::RedundantNumericUnion) {
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
}
if checker.enabled(Rule::DunderFunctionName) {
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
checker.semantic.current_scope(),
checker.semantic.scope(),
stmt,
name,
&checker.settings.pep8_naming.ignore_names,
@@ -235,13 +230,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.enabled(Rule::HardcodedPasswordDefault) {
flake8_bandit::rules::hardcoded_password_default(checker, parameters);
flake8_bandit::rules::hardcoded_password_default(checker, args);
}
if checker.enabled(Rule::PropertyWithParameters) {
pylint::rules::property_with_parameters(checker, stmt, decorator_list, parameters);
pylint::rules::property_with_parameters(checker, stmt, decorator_list, args);
}
if checker.enabled(Rule::TooManyArguments) {
pylint::rules::too_many_arguments(checker, parameters, stmt);
pylint::rules::too_many_arguments(checker, args, stmt);
}
if checker.enabled(Rule::TooManyReturnStatements) {
if let Some(diagnostic) = pylint::rules::too_many_return_statements(
@@ -287,7 +282,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker,
stmt,
name,
parameters,
args,
decorator_list,
body,
);
@@ -309,7 +304,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker,
name,
decorator_list,
parameters,
args,
);
}
if checker.enabled(Rule::BooleanDefaultValueInFunctionDefinition) {
@@ -317,7 +312,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker,
name,
decorator_list,
parameters,
args,
);
}
if checker.enabled(Rule::UnexpectedSpecialMethodSignature) {
@@ -326,7 +321,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
name,
decorator_list,
parameters,
args,
);
}
if checker.enabled(Rule::FStringDocstring) {
@@ -335,7 +330,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::YieldInForLoop) {
pyupgrade::rules::yield_in_for_loop(checker, stmt);
}
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_method_shadowing(
checker,
@@ -368,7 +363,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
Stmt::ClassDef(
class_def @ ast::StmtClassDef {
name,
arguments,
bases,
keywords,
type_params: _,
decorator_list,
body,
@@ -379,36 +375,27 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_django::rules::nullable_model_string_field(checker, body);
}
if checker.enabled(Rule::DjangoExcludeWithModelForm) {
if let Some(diagnostic) = flake8_django::rules::exclude_with_model_form(
checker,
arguments.as_deref(),
body,
) {
if let Some(diagnostic) =
flake8_django::rules::exclude_with_model_form(checker, bases, body)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::DjangoAllWithModelForm) {
if let Some(diagnostic) =
flake8_django::rules::all_with_model_form(checker, arguments.as_deref(), body)
flake8_django::rules::all_with_model_form(checker, bases, body)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::DjangoUnorderedBodyContentInModel) {
flake8_django::rules::unordered_body_content_in_model(
checker,
arguments.as_deref(),
body,
);
flake8_django::rules::unordered_body_content_in_model(checker, bases, body);
}
if !checker.source_type.is_stub() {
if !checker.is_stub {
if checker.enabled(Rule::DjangoModelWithoutDunderStr) {
flake8_django::rules::model_without_dunder_str(checker, class_def);
}
}
if checker.enabled(Rule::EqWithoutHash) {
pylint::rules::object_without_hash_method(checker, class_def);
}
if checker.enabled(Rule::GlobalStatement) {
pylint::rules::global_statement(checker, name);
}
@@ -435,37 +422,33 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ErrorSuffixOnExceptionName) {
if let Some(diagnostic) = pep8_naming::rules::error_suffix_on_exception_name(
stmt,
arguments.as_deref(),
bases,
name,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
}
}
if !checker.source_type.is_stub() {
if !checker.is_stub {
if checker.any_enabled(&[
Rule::AbstractBaseClassWithoutAbstractMethod,
Rule::EmptyMethodWithoutAbstractDecorator,
]) {
flake8_bugbear::rules::abstract_base_class(
checker,
stmt,
name,
arguments.as_deref(),
body,
checker, stmt, name, bases, keywords, body,
);
}
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.enabled(Rule::PassStatementStubBody) {
flake8_pyi::rules::pass_statement_stub_body(checker, body);
}
if checker.enabled(Rule::PassInClassBody) {
flake8_pyi::rules::pass_in_class_body(checker, stmt, body);
}
}
if checker.enabled(Rule::EllipsisInNonEmptyClassBody) {
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, stmt, body);
if checker.enabled(Rule::EllipsisInNonEmptyClassBody) {
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, stmt, body);
}
}
if checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle) {
flake8_pytest_style::rules::marks(checker, decorator_list);
@@ -492,7 +475,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
}
if checker.enabled(Rule::DuplicateBases) {
pylint::rules::duplicate_bases(checker, name, arguments.as_deref());
pylint::rules::duplicate_bases(checker, name, bases);
}
if checker.enabled(Rule::NoSlotsInStrSubclass) {
flake8_slots::rules::no_slots_in_str_subclass(checker, stmt, class_def);
@@ -558,7 +541,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
alias,
);
}
if !checker.source_type.is_stub() {
if !checker.is_stub {
if checker.enabled(Rule::UselessImportAlias) {
pylint::rules::useless_import_alias(checker, alias);
}
@@ -733,7 +716,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.enabled(Rule::FutureAnnotationsInStub) {
flake8_pyi::rules::from_future_import(checker, import_from);
}
@@ -753,7 +736,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
} else if &alias.name == "*" {
if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
if !matches!(checker.semantic.scope().kind, ScopeKind::Module) {
checker.diagnostics.push(Diagnostic::new(
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
name: helpers::format_import_from(level, module),
@@ -878,7 +861,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if !checker.source_type.is_stub() {
if !checker.is_stub {
if checker.enabled(Rule::UselessImportAlias) {
pylint::rules::useless_import_alias(checker, alias);
}
@@ -969,7 +952,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_simplify::rules::nested_if_statements(
checker,
if_,
checker.semantic.current_statement_parent(),
checker.semantic.stmt_parent(),
);
}
if checker.enabled(Rule::IfWithSameArms) {
@@ -991,7 +974,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
tryceratops::rules::type_check_without_type_error(
checker,
if_,
checker.semantic.current_statement_parent(),
checker.semantic.stmt_parent(),
);
}
if checker.enabled(Rule::OutdatedVersionBlock) {
@@ -1002,7 +985,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.any_enabled(&[
Rule::UnrecognizedVersionInfoCheck,
Rule::PatchVersionComparison,
@@ -1084,7 +1067,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pygrep_hooks::rules::non_existent_mock_method(checker, test);
}
}
Stmt::With(with_ @ ast::StmtWith { items, body, .. }) => {
Stmt::With(ast::StmtWith { items, body, .. })
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
if checker.enabled(Rule::AssertRaisesException) {
flake8_bugbear::rules::assert_raises_exception(checker, items);
}
@@ -1094,8 +1078,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::MultipleWithStatements) {
flake8_simplify::rules::multiple_with_statements(
checker,
with_,
checker.semantic.current_statement_parent(),
stmt,
body,
checker.semantic.stmt_parent(),
);
}
if checker.enabled(Rule::RedefinedLoopName) {
@@ -1119,6 +1104,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
iter,
orelse,
..
})
| Stmt::AsyncFor(ast::StmtAsyncFor {
target,
body,
iter,
orelse,
..
}) => {
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
{
@@ -1305,7 +1297,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.settings.rules.enabled(Rule::TypeBivariance) {
pylint::rules::type_bivariance(checker, value);
}
if checker.source_type.is_stub() {
if checker.is_stub {
if checker.any_enabled(&[
Rule::UnprefixedTypeParam,
Rule::AssignmentDefaultInStub,
@@ -1316,8 +1308,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
// Ignore assignments in function bodies; those are covered by other rules.
if !checker
.semantic
.current_scopes()
.any(|scope| scope.kind.is_function())
.scopes()
.any(|scope| scope.kind.is_any_function())
{
if checker.enabled(Rule::UnprefixedTypeParam) {
flake8_pyi::rules::prefix_type_params(checker, value, targets);
@@ -1342,14 +1334,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
}
Stmt::AnnAssign(
assign_stmt @ ast::StmtAnnAssign {
target,
value,
annotation,
..
},
) => {
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
value,
annotation,
..
}) => {
if let Some(value) = value {
if checker.enabled(Rule::LambdaAssignment) {
pycodestyle::rules::lambda_assignment(
@@ -1372,17 +1362,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
);
}
if checker.enabled(Rule::NonPEP695TypeAlias) {
pyupgrade::rules::non_pep695_type_alias(checker, assign_stmt);
}
if checker.source_type.is_stub() {
if checker.is_stub {
if let Some(value) = value {
if checker.enabled(Rule::AssignmentDefaultInStub) {
// Ignore assignments in function bodies; those are covered by other rules.
if !checker
.semantic
.current_scopes()
.any(|scope| scope.kind.is_function())
.scopes()
.any(|scope| scope.kind.is_any_function())
{
flake8_pyi::rules::annotated_assignment_default_in_stub(
checker, target, value, annotation,
@@ -1396,13 +1383,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
);
}
}
}
if checker.semantic.match_typing_expr(annotation, "TypeAlias") {
if checker.enabled(Rule::SnakeCaseTypeAlias) {
flake8_pyi::rules::snake_case_type_alias(checker, target);
}
if checker.enabled(Rule::TSuffixedTypeAlias) {
flake8_pyi::rules::t_suffixed_type_alias(checker, target);
if checker.semantic.match_typing_expr(annotation, "TypeAlias") {
if checker.enabled(Rule::SnakeCaseTypeAlias) {
flake8_pyi::rules::snake_case_type_alias(checker, target);
}
if checker.enabled(Rule::TSuffixedTypeAlias) {
flake8_pyi::rules::t_suffixed_type_alias(checker, target);
}
}
}
}

View File

@@ -1,7 +1,8 @@
use ruff_python_ast::{Expr, TypeParam};
use ruff_python_semantic::{ScopeId, Snapshot};
use ruff_python_ast::Expr;
use ruff_text_size::TextRange;
use ruff_python_semantic::{ScopeId, Snapshot};
/// A collection of AST nodes that are deferred for later analysis.
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
/// module-level definitions have been analyzed.
@@ -10,7 +11,6 @@ pub(crate) struct Deferred<'a> {
pub(crate) scopes: Vec<ScopeId>,
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
pub(crate) type_param_definitions: Vec<(&'a TypeParam, Snapshot)>,
pub(crate) functions: Vec<Snapshot>,
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
pub(crate) for_loops: Vec<Snapshot>,

View File

@@ -31,21 +31,18 @@ use std::path::Path;
use itertools::Itertools;
use log::error;
use ruff_python_ast::{
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
ExprContext, Keyword, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt,
Suite, UnaryOp,
self as ast, Arg, ArgWithDefault, Arguments, Comprehension, Constant, ElifElseClause,
ExceptHandler, Expr, ExprContext, Keyword, Pattern, Ranged, Stmt, Suite, UnaryOp,
};
use ruff_text_size::{TextRange, TextSize};
use ruff_diagnostics::{Diagnostic, IsolationLevel};
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
use ruff_python_ast::helpers::{
collect_import_from_member, extract_handled_exceptions, to_module_path,
};
use ruff_python_ast::helpers::{extract_handled_exceptions, to_module_path};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::str::trailing_quote;
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
use ruff_python_ast::{helpers, str, visitor, PySourceType};
use ruff_python_ast::{helpers, str, visitor};
use ruff_python_codegen::{Generator, Quote, Stylist};
use ruff_python_index::Indexer;
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
@@ -55,6 +52,7 @@ use ruff_python_semantic::{
ModuleKind, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport, SubmoduleImport,
};
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_source_file::Locator;
use crate::checkers::ast::deferred::Deferred;
@@ -76,8 +74,8 @@ pub(crate) struct Checker<'a> {
package: Option<&'a Path>,
/// The module representation of the current file (e.g., `foo.bar`).
module_path: Option<&'a [String]>,
/// The [`PySourceType`] of the current file.
pub(crate) source_type: PySourceType,
/// Whether the current file is a stub (`.pyi`) file.
is_stub: bool,
/// The [`flags::Noqa`] for the current analysis (i.e., whether to respect suppression
/// comments).
noqa: flags::Noqa,
@@ -119,7 +117,6 @@ impl<'a> Checker<'a> {
stylist: &'a Stylist,
indexer: &'a Indexer,
importer: Importer<'a>,
source_type: PySourceType,
) -> Checker<'a> {
Checker {
settings,
@@ -128,7 +125,7 @@ impl<'a> Checker<'a> {
path,
package,
module_path: module.path(),
source_type,
is_stub: is_python_stub_file(path),
locator,
stylist,
indexer,
@@ -176,12 +173,13 @@ impl<'a> Checker<'a> {
///
/// If the current expression in the context is not an f-string, returns ``None``.
pub(crate) fn f_string_quote_style(&self) -> Option<Quote> {
if !self.semantic.in_f_string() {
let model = &self.semantic;
if !model.in_f_string() {
return None;
}
// Find the quote character used to start the containing f-string.
let expr = self.semantic.current_expression()?;
let expr = model.expr()?;
let string_range = self.indexer.f_string_range(expr.start())?;
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
@@ -201,7 +199,7 @@ impl<'a> Checker<'a> {
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
parent
.and_then(|stmt| self.semantic.statement_id(stmt))
.and_then(|stmt| self.semantic.stmts.node_id(stmt))
.map_or(IsolationLevel::default(), |node_id| {
IsolationLevel::Group(node_id.into())
})
@@ -263,7 +261,7 @@ where
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
// Step 0: Pre-processing
self.semantic.push_statement(stmt);
self.semantic.push_stmt(stmt);
// Track whether we've seen docstrings, non-imports, etc.
match stmt {
@@ -287,7 +285,7 @@ where
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
if !self.semantic.seen_import_boundary()
&& !helpers::is_assignment_to_a_dunder(stmt)
&& !helpers::in_nested_block(self.semantic.current_statements())
&& !helpers::in_nested_block(self.semantic.parents())
{
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
}
@@ -321,11 +319,11 @@ where
// Given `import foo.bar`, `name` would be "foo", and `qualified_name` would be
// "foo.bar".
let name = alias.name.split('.').next().unwrap();
let call_path: Box<[&str]> = alias.name.split('.').collect();
let qualified_name = &alias.name;
self.add_binding(
name,
alias.identifier(),
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }),
BindingFlags::EXTERNAL,
);
} else {
@@ -336,17 +334,17 @@ where
if alias
.asname
.as_ref()
.is_some_and(|asname| asname.as_str() == alias.name.as_str())
.map_or(false, |asname| asname.as_str() == alias.name.as_str())
{
flags |= BindingFlags::EXPLICIT_EXPORT;
}
let name = alias.asname.as_ref().unwrap_or(&alias.name);
let call_path: Box<[&str]> = alias.name.split('.').collect();
let qualified_name = &alias.name;
self.add_binding(
name,
alias.identifier(),
BindingKind::Import(Import { call_path }),
BindingKind::Import(Import { qualified_name }),
flags,
);
}
@@ -371,7 +369,7 @@ where
);
} else if &alias.name == "*" {
self.semantic
.current_scope_mut()
.scope_mut()
.add_star_import(StarImport { level, module });
} else {
let mut flags = BindingFlags::EXTERNAL;
@@ -381,7 +379,7 @@ where
if alias
.asname
.as_ref()
.is_some_and(|asname| asname.as_str() == alias.name.as_str())
.map_or(false, |asname| asname.as_str() == alias.name.as_str())
{
flags |= BindingFlags::EXPLICIT_EXPORT;
}
@@ -390,16 +388,12 @@ where
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
// and `qualified_name` would be "foo.bar".
let name = alias.asname.as_ref().unwrap_or(&alias.name);
// Attempt to resolve any relative imports; but if we don't know the current
// module path, or the relative import extends beyond the package root,
// fallback to a literal representation (e.g., `[".", "foo"]`).
let call_path = collect_import_from_member(level, module, &alias.name)
.into_boxed_slice();
let qualified_name =
helpers::format_import_from_member(level, module, &alias.name);
self.add_binding(
name,
alias.identifier(),
BindingKind::FromImport(FromImport { call_path }),
BindingKind::FromImport(FromImport { qualified_name }),
flags,
);
}
@@ -420,7 +414,7 @@ where
BindingKind::Global,
BindingFlags::GLOBAL,
);
let scope = self.semantic.current_scope_mut();
let scope = self.semantic.scope_mut();
scope.add(name, binding_id);
}
}
@@ -443,7 +437,7 @@ where
BindingKind::Nonlocal(scope_id),
BindingFlags::NONLOCAL,
);
let scope = self.semantic.current_scope_mut();
let scope = self.semantic.scope_mut();
scope.add(name, binding_id);
}
}
@@ -454,16 +448,20 @@ where
// Step 2: Traversal
match stmt {
Stmt::FunctionDef(
function_def @ ast::StmtFunctionDef {
body,
parameters,
decorator_list,
returns,
type_params,
..
},
) => {
Stmt::FunctionDef(ast::StmtFunctionDef {
body,
args,
decorator_list,
returns,
..
})
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
body,
args,
decorator_list,
returns,
..
}) => {
// Visit the decorators and arguments, but avoid the body, which will be
// deferred.
for decorator in decorator_list {
@@ -474,30 +472,24 @@ where
// are enabled.
let runtime_annotation = !self.semantic.future_annotations();
self.semantic.push_scope(ScopeKind::Type);
if let Some(type_params) = type_params {
self.visit_type_params(type_params);
}
for parameter_with_default in parameters
for arg_with_default in args
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.chain(&args.args)
.chain(&args.kwonlyargs)
{
if let Some(expr) = &parameter_with_default.parameter.annotation {
if let Some(expr) = &arg_with_default.def.annotation {
if runtime_annotation {
self.visit_runtime_annotation(expr);
} else {
self.visit_annotation(expr);
};
}
if let Some(expr) = &parameter_with_default.default {
if let Some(expr) = &arg_with_default.default {
self.visit_expr(expr);
}
}
if let Some(arg) = &parameters.vararg {
if let Some(arg) = &args.vararg {
if let Some(expr) = &arg.annotation {
if runtime_annotation {
self.visit_runtime_annotation(expr);
@@ -506,7 +498,7 @@ where
};
}
}
if let Some(arg) = &parameters.kwarg {
if let Some(arg) = &args.kwarg {
if let Some(expr) = &arg.annotation {
if runtime_annotation {
self.visit_runtime_annotation(expr);
@@ -524,7 +516,8 @@ where
}
let definition = docstrings::extraction::extract_definition(
ExtractionTarget::Function(function_def),
ExtractionTarget::Function,
stmt,
self.semantic.definition_id,
&self.semantic.definitions,
);
@@ -532,7 +525,8 @@ where
self.semantic.push_scope(match &stmt {
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
_ => unreachable!("Expected Stmt::FunctionDef"),
Stmt::AsyncFunctionDef(stmt) => ScopeKind::AsyncFunction(stmt),
_ => unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef"),
});
self.deferred.functions.push(self.semantic.snapshot());
@@ -545,32 +539,30 @@ where
Stmt::ClassDef(
class_def @ ast::StmtClassDef {
body,
arguments,
bases,
keywords,
decorator_list,
type_params,
..
},
) => {
for expr in bases {
self.visit_expr(expr);
}
for keyword in keywords {
self.visit_keyword(keyword);
}
for decorator in decorator_list {
self.visit_decorator(decorator);
}
self.semantic.push_scope(ScopeKind::Type);
if let Some(type_params) = type_params {
self.visit_type_params(type_params);
}
if let Some(arguments) = arguments {
self.visit_arguments(arguments);
}
let definition = docstrings::extraction::extract_definition(
ExtractionTarget::Class(class_def),
ExtractionTarget::Class,
stmt,
self.semantic.definition_id,
&self.semantic.definitions,
);
self.semantic.push_definition(definition);
self.semantic.push_scope(ScopeKind::Class(class_def));
// Extract any global bindings from the class body.
@@ -580,20 +572,6 @@ where
self.visit_body(body);
}
Stmt::TypeAlias(ast::StmtTypeAlias {
range: _,
name,
type_params,
value,
}) => {
self.semantic.push_scope(ScopeKind::Type);
if let Some(type_params) = type_params {
self.visit_type_params(type_params);
}
self.visit_expr(value);
self.semantic.pop_scope();
self.visit_expr(name);
}
Stmt::Try(ast::StmtTry {
body,
handlers,
@@ -647,7 +625,7 @@ where
// available at runtime.
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
let runtime_annotation = if self.semantic.future_annotations() {
if self.semantic.current_scope().kind.is_class() {
if self.semantic.scope().kind.is_class() {
let baseclasses = &self
.settings
.flake8_type_checking
@@ -666,7 +644,7 @@ where
}
} else {
matches!(
self.semantic.current_scope().kind,
self.semantic.scope().kind,
ScopeKind::Class(_) | ScopeKind::Module
)
};
@@ -733,12 +711,12 @@ where
// Step 3: Clean-up
match stmt {
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. })
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
let scope_id = self.semantic.scope_id;
self.deferred.scopes.push(scope_id);
self.semantic.pop_scope(); // Function scope
self.semantic.pop_scope();
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
self.add_binding(
name,
stmt.identifier(),
@@ -749,9 +727,8 @@ where
Stmt::ClassDef(ast::StmtClassDef { name, .. }) => {
let scope_id = self.semantic.scope_id;
self.deferred.scopes.push(scope_id);
self.semantic.pop_scope(); // Class scope
self.semantic.pop_scope();
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
self.add_binding(
name,
stmt.identifier(),
@@ -766,7 +743,7 @@ where
analyze::statement(stmt, self);
self.semantic.flags = flags_snapshot;
self.semantic.pop_statement();
self.semantic.pop_stmt();
}
fn visit_annotation(&mut self, expr: &'b Expr) {
@@ -802,7 +779,7 @@ where
return;
}
self.semantic.push_expression(expr);
self.semantic.push_expr(expr);
// Store the flags prior to any further descent, so that we can restore them after visiting
// the node.
@@ -825,12 +802,13 @@ where
match expr {
Expr::Call(ast::ExprCall {
func,
arguments: _,
args: _,
keywords: _,
range: _,
}) => {
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
if id == "locals" && ctx.is_load() {
let scope = self.semantic.current_scope_mut();
let scope = self.semantic.scope_mut();
scope.set_uses_locals();
}
}
@@ -875,21 +853,21 @@ where
}
Expr::Lambda(
lambda @ ast::ExprLambda {
parameters,
args,
body: _,
range: _,
},
) => {
// Visit the default arguments, but avoid the body, which will be deferred.
for ParameterWithDefault {
for ArgWithDefault {
default,
parameter: _,
def: _,
range: _,
} in parameters
} in args
.posonlyargs
.iter()
.chain(&parameters.args)
.chain(&parameters.kwonlyargs)
.chain(&args.args)
.chain(&args.kwonlyargs)
{
if let Some(expr) = &default {
self.visit_expr(expr);
@@ -911,12 +889,8 @@ where
}
Expr::Call(ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
args,
keywords,
range: _,
}) => {
self.visit_expr(func);
@@ -1094,7 +1068,7 @@ where
arg,
range: _,
} = keyword;
if arg.as_ref().is_some_and(|arg| arg == "type") {
if arg.as_ref().map_or(false, |arg| arg == "type") {
self.visit_type_definition(value);
} else {
self.visit_non_type_definition(value);
@@ -1196,7 +1170,7 @@ where
));
}
}
Expr::FString(_) => {
Expr::JoinedStr(_) => {
self.semantic.flags |= SemanticModelFlags::F_STRING;
visitor::walk_expr(self, expr);
}
@@ -1220,7 +1194,7 @@ where
analyze::expression(expr, self);
self.semantic.flags = flags_snapshot;
self.semantic.pop_expression();
self.semantic.pop_expr();
}
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
@@ -1275,7 +1249,7 @@ where
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
match format_spec {
Expr::FString(ast::ExprFString { values, range: _ }) => {
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
for value in values {
self.visit_expr(value);
}
@@ -1284,43 +1258,43 @@ where
}
}
fn visit_parameters(&mut self, parameters: &'b Parameters) {
fn visit_arguments(&mut self, arguments: &'b Arguments) {
// Step 1: Binding.
// Bind, but intentionally avoid walking default expressions, as we handle them
// upstream.
for parameter_with_default in &parameters.posonlyargs {
self.visit_parameter(&parameter_with_default.parameter);
for arg_with_default in &arguments.posonlyargs {
self.visit_arg(&arg_with_default.def);
}
for parameter_with_default in &parameters.args {
self.visit_parameter(&parameter_with_default.parameter);
for arg_with_default in &arguments.args {
self.visit_arg(&arg_with_default.def);
}
if let Some(arg) = &parameters.vararg {
self.visit_parameter(arg);
if let Some(arg) = &arguments.vararg {
self.visit_arg(arg);
}
for parameter_with_default in &parameters.kwonlyargs {
self.visit_parameter(&parameter_with_default.parameter);
for arg_with_default in &arguments.kwonlyargs {
self.visit_arg(&arg_with_default.def);
}
if let Some(arg) = &parameters.kwarg {
self.visit_parameter(arg);
if let Some(arg) = &arguments.kwarg {
self.visit_arg(arg);
}
// Step 4: Analysis
analyze::parameters(parameters, self);
analyze::arguments(arguments, self);
}
fn visit_parameter(&mut self, parameter: &'b Parameter) {
fn visit_arg(&mut self, arg: &'b Arg) {
// Step 1: Binding.
// Bind, but intentionally avoid walking the annotation, as we handle it
// upstream.
self.add_binding(
&parameter.name,
parameter.identifier(),
&arg.arg,
arg.identifier(),
BindingKind::Argument,
BindingFlags::empty(),
);
// Step 4: Analysis
analyze::parameter(parameter, self);
analyze::argument(arg, self);
}
fn visit_pattern(&mut self, pattern: &'b Pattern) {
@@ -1357,26 +1331,6 @@ where
self.visit_stmt(stmt);
}
}
fn visit_type_param(&mut self, type_param: &'b ast::TypeParam) {
// Step 1: Binding
match type_param {
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, range, .. })
| ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { name, range })
| ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { name, range }) => {
self.add_binding(
name.as_str(),
*range,
BindingKind::TypeParam,
BindingFlags::empty(),
);
}
}
// Step 2: Traversal
self.deferred
.type_param_definitions
.push((type_param, self.semantic.snapshot()));
}
}
impl<'a> Checker<'a> {
@@ -1593,14 +1547,14 @@ impl<'a> Checker<'a> {
}
fn handle_node_load(&mut self, expr: &Expr) {
let Expr::Name(expr) = expr else {
let Expr::Name(ast::ExprName { id, .. }) = expr else {
return;
};
self.semantic.resolve_load(expr);
self.semantic.resolve_load(id, expr.range());
}
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
let parent = self.semantic.current_statement();
let parent = self.semantic.stmt();
if matches!(
parent,
@@ -1615,7 +1569,7 @@ impl<'a> Checker<'a> {
return;
}
if parent.is_for_stmt() {
if matches!(parent, Stmt::For(_) | Stmt::AsyncFor(_)) {
self.add_binding(
id,
expr.range(),
@@ -1635,7 +1589,7 @@ impl<'a> Checker<'a> {
return;
}
let scope = self.semantic.current_scope();
let scope = self.semantic.scope();
if scope.kind.is_module()
&& match parent {
@@ -1687,8 +1641,8 @@ impl<'a> Checker<'a> {
if self
.semantic
.current_expressions()
.any(Expr::is_named_expr_expr)
.expr_ancestors()
.any(|expr| expr.is_named_expr_expr())
{
self.add_binding(
id,
@@ -1714,7 +1668,7 @@ impl<'a> Checker<'a> {
self.semantic.resolve_del(id, expr.range());
if helpers::on_conditional_branch(&mut self.semantic.current_statements()) {
if helpers::on_conditional_branch(&mut self.semantic.parents()) {
return;
}
@@ -1722,12 +1676,11 @@ impl<'a> Checker<'a> {
let binding_id =
self.semantic
.push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty());
let scope = self.semantic.current_scope_mut();
let scope = self.semantic.scope_mut();
scope.add(id, binding_id);
}
fn visit_deferred_future_type_definitions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.deferred.future_type_definitions.is_empty() {
let type_definitions = std::mem::take(&mut self.deferred.future_type_definitions);
for (expr, snapshot) in type_definitions {
@@ -1738,29 +1691,9 @@ impl<'a> Checker<'a> {
self.visit_expr(expr);
}
}
self.semantic.restore(snapshot);
}
fn visit_deferred_type_param_definitions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.deferred.type_param_definitions.is_empty() {
let type_params = std::mem::take(&mut self.deferred.type_param_definitions);
for (type_param, snapshot) in type_params {
self.semantic.restore(snapshot);
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
bound: Some(bound), ..
}) = type_param
{
self.visit_expr(bound);
}
}
}
self.semantic.restore(snapshot);
}
fn visit_deferred_string_type_definitions(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
let snapshot = self.semantic.snapshot();
while !self.deferred.string_type_definitions.is_empty() {
let type_definitions = std::mem::take(&mut self.deferred.string_type_definitions);
for (range, value, snapshot) in type_definitions {
@@ -1771,12 +1704,12 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
if self.semantic.in_annotation() && self.semantic.future_annotations() {
if self.semantic.in_typing_only_annotation() {
if self.enabled(Rule::QuotedAnnotation) {
pyupgrade::rules::quoted_annotation(self, value, range);
}
}
if self.source_type.is_stub() {
if self.is_stub {
if self.enabled(Rule::QuotedAnnotationInStub) {
flake8_pyi::rules::quoted_annotation_in_stub(self, value, range);
}
@@ -1804,57 +1737,51 @@ impl<'a> Checker<'a> {
}
}
}
self.semantic.restore(snapshot);
}
fn visit_deferred_functions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.deferred.functions.is_empty() {
let deferred_functions = std::mem::take(&mut self.deferred.functions);
for snapshot in deferred_functions {
self.semantic.restore(snapshot);
if let Stmt::FunctionDef(ast::StmtFunctionDef {
body, parameters, ..
}) = self.semantic.current_statement()
{
self.visit_parameters(parameters);
self.visit_body(body);
} else {
unreachable!("Expected Stmt::FunctionDef")
match &self.semantic.stmt() {
Stmt::FunctionDef(ast::StmtFunctionDef { body, args, .. })
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, args, .. }) => {
self.visit_arguments(args);
self.visit_body(body);
}
_ => {
unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef")
}
}
}
}
self.semantic.restore(snapshot);
}
fn visit_deferred_lambdas(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.deferred.lambdas.is_empty() {
let lambdas = std::mem::take(&mut self.deferred.lambdas);
for (expr, snapshot) in lambdas {
self.semantic.restore(snapshot);
if let Expr::Lambda(ast::ExprLambda {
parameters,
args,
body,
range: _,
}) = expr
{
self.visit_parameters(parameters);
self.visit_arguments(args);
self.visit_expr(body);
} else {
unreachable!("Expected Expr::Lambda");
}
}
}
self.semantic.restore(snapshot);
}
/// Run any lint rules that operate over the module exports (i.e., members of `__all__`).
fn visit_exports(&mut self) {
let snapshot = self.semantic.snapshot();
let exports: Vec<(&str, TextRange)> = self
.semantic
.global_scope()
@@ -1897,8 +1824,6 @@ impl<'a> Checker<'a> {
}
}
}
self.semantic.restore(snapshot);
}
}
@@ -1913,7 +1838,6 @@ pub(crate) fn check_ast(
noqa: flags::Noqa,
path: &Path,
package: Option<&Path>,
source_type: PySourceType,
) -> Vec<Diagnostic> {
let module_path = package.and_then(|package| to_module_path(package, path));
let module = Module {
@@ -1941,7 +1865,6 @@ pub(crate) fn check_ast(
stylist,
indexer,
Importer::new(python_ast, locator, stylist),
source_type,
);
checker.bind_builtins();
@@ -1959,7 +1882,6 @@ pub(crate) fn check_ast(
checker.visit_deferred_functions();
checker.visit_deferred_lambdas();
checker.visit_deferred_future_type_definitions();
checker.visit_deferred_type_param_definitions();
let allocator = typed_arena::Arena::new();
checker.visit_deferred_string_type_definitions(&allocator);
checker.visit_exports();

View File

@@ -2,7 +2,7 @@
use std::borrow::Cow;
use std::path::Path;
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt, Suite};
use ruff_python_ast::{self as ast, Ranged, Stmt, Suite};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::helpers::to_module_path;
@@ -10,7 +10,7 @@ use ruff_python_ast::imports::{ImportMap, ModuleImport};
use ruff_python_ast::statement_visitor::StatementVisitor;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_source_file::Locator;
use crate::directives::IsortDirectives;
@@ -87,12 +87,12 @@ pub(crate) fn check_imports(
path: &Path,
package: Option<&Path>,
source_kind: Option<&SourceKind>,
source_type: PySourceType,
) -> (Vec<Diagnostic>, Option<ImportMap>) {
let is_stub = is_python_stub_file(path);
// Extract all import blocks from the AST.
let tracker = {
let mut tracker =
BlockBuilder::new(locator, directives, source_type.is_stub(), source_kind);
let mut tracker = BlockBuilder::new(locator, directives, is_stub, source_kind);
tracker.visit_body(python_ast);
tracker
};
@@ -104,13 +104,7 @@ pub(crate) fn check_imports(
for block in &blocks {
if !block.imports.is_empty() {
if let Some(diagnostic) = isort::rules::organize_imports(
block,
locator,
stylist,
indexer,
settings,
package,
source_type,
block, locator, stylist, indexer, settings, package,
) {
diagnostics.push(diagnostic);
}
@@ -119,11 +113,7 @@ pub(crate) fn check_imports(
}
if settings.rules.enabled(Rule::MissingRequiredImport) {
diagnostics.extend(isort::rules::add_required_imports(
python_ast,
locator,
stylist,
settings,
source_type,
python_ast, locator, stylist, settings, is_stub,
));
}

View File

@@ -9,9 +9,9 @@ use ruff_source_file::Locator;
use crate::registry::{AsRule, Rule};
use crate::rules::pycodestyle::rules::logical_lines::{
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
missing_whitespace_around_operator, space_after_comma, space_around_operator,
whitespace_around_keywords, whitespace_around_named_parameter_equals,
whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags,
missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords,
whitespace_around_named_parameter_equals, whitespace_before_comment,
whitespace_before_parameters, LogicalLines, TokenFlags,
};
use crate::settings::Settings;
@@ -61,9 +61,6 @@ pub(crate) fn check_logical_lines(
missing_whitespace_around_operator(&line, &mut context);
missing_whitespace(&line, should_fix_missing_whitespace, &mut context);
}
if line.flags().contains(TokenFlags::PUNCTUATION) {
space_after_comma(&line, &mut context);
}
if line
.flags()

View File

@@ -94,15 +94,8 @@ pub(crate) fn check_noqa(
}
}
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
// suppressed.
if settings.rules.enabled(Rule::UnusedNOQA)
&& analyze_directives
&& !exemption.is_some_and(|exemption| match exemption {
FileExemption::All => true,
FileExemption::Codes(codes) => codes.contains(&Rule::UnusedNOQA.noqa_code()),
})
{
// Enforce that the noqa directive was actually used (RUF100).
if analyze_directives && settings.rules.enabled(Rule::UnusedNOQA) {
for line in noqa_directives.lines() {
match &line.directive {
Directive::All(directive) => {

View File

@@ -84,8 +84,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
@@ -191,7 +189,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "E1142") => (RuleGroup::Unspecified, rules::pylint::rules::AwaitOutsideAsync),
(Pylint, "E1205") => (RuleGroup::Unspecified, rules::pylint::rules::LoggingTooManyArgs),
(Pylint, "E1206") => (RuleGroup::Unspecified, rules::pylint::rules::LoggingTooFewArgs),
(Pylint, "E1300") => (RuleGroup::Unspecified, rules::pylint::rules::BadStringFormatCharacter),
(Pylint, "E1307") => (RuleGroup::Unspecified, rules::pylint::rules::BadStringFormatType),
(Pylint, "E1310") => (RuleGroup::Unspecified, rules::pylint::rules::BadStrStripCall),
(Pylint, "E1507") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarValue),
@@ -226,7 +223,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
@@ -439,7 +435,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pyupgrade, "037") => (RuleGroup::Unspecified, rules::pyupgrade::rules::QuotedAnnotation),
(Pyupgrade, "038") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP604Isinstance),
(Pyupgrade, "039") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnnecessaryClassParentheses),
(Pyupgrade, "040") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP695TypeAlias),
// pydocstyle
(Pydocstyle, "100") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicModule),
@@ -638,7 +633,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "016") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::DuplicateUnionMember),
(Flake8Pyi, "017") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ComplexAssignmentInStub),
(Flake8Pyi, "018") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeVar),
(Flake8Pyi, "019") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CustomTypeVarReturnType),
(Flake8Pyi, "020") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::QuotedAnnotationInStub),
(Flake8Pyi, "021") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::DocstringInStub),
(Flake8Pyi, "024") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CollectionsNamedTuple),
@@ -657,15 +651,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "044") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::FutureAnnotationsInStub),
(Flake8Pyi, "045") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::IterMethodReturnIterable),
(Flake8Pyi, "046") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateProtocol),
(Flake8Pyi, "047") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeAlias),
(Flake8Pyi, "048") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StubBodyMultipleStatements),
(Flake8Pyi, "049") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypedDict),
(Flake8Pyi, "050") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NoReturnArgumentAnnotationInStub),
(Flake8Pyi, "051") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::RedundantLiteralUnion),
(Flake8Pyi, "052") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnannotatedAssignmentInStub),
(Flake8Pyi, "054") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NumericLiteralTooLong),
(Flake8Pyi, "053") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StringOrBytesTooLong),
(Flake8Pyi, "055") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnnecessaryTypeUnion),
(Flake8Pyi, "056") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
// flake8-pytest-style

View File

@@ -1,6 +1,7 @@
//! Extract docstrings from an AST.
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
/// Extract a docstring from a function or class body.
@@ -27,48 +28,63 @@ pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
match definition {
Definition::Module(module) => docstring_from(module.python_ast),
Definition::Member(member) => docstring_from(member.body()),
Definition::Member(member) => {
if let Stmt::ClassDef(ast::StmtClassDef { body, .. })
| Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) = &member.stmt
{
docstring_from(body)
} else {
None
}
}
}
}
#[derive(Copy, Clone)]
pub(crate) enum ExtractionTarget<'a> {
Class(&'a ast::StmtClassDef),
Function(&'a ast::StmtFunctionDef),
pub(crate) enum ExtractionTarget {
Class,
Function,
}
/// Extract a `Definition` from the AST node defined by a `Stmt`.
pub(crate) fn extract_definition<'a>(
target: ExtractionTarget<'a>,
target: ExtractionTarget,
stmt: &'a Stmt,
parent: DefinitionId,
definitions: &Definitions<'a>,
) -> Member<'a> {
match target {
ExtractionTarget::Function(function) => match &definitions[parent] {
ExtractionTarget::Function => match &definitions[parent] {
Definition::Module(..) => Member {
parent,
kind: MemberKind::Function(function),
kind: MemberKind::Function,
stmt,
},
Definition::Member(Member {
kind: MemberKind::Class(_) | MemberKind::NestedClass(_),
kind: MemberKind::Class | MemberKind::NestedClass,
..
}) => Member {
parent,
kind: MemberKind::Method(function),
kind: MemberKind::Method,
stmt,
},
Definition::Member(_) => Member {
Definition::Member(..) => Member {
parent,
kind: MemberKind::NestedFunction(function),
kind: MemberKind::NestedFunction,
stmt,
},
},
ExtractionTarget::Class(class) => match &definitions[parent] {
Definition::Module(_) => Member {
ExtractionTarget::Class => match &definitions[parent] {
Definition::Module(..) => Member {
parent,
kind: MemberKind::Class(class),
kind: MemberKind::Class,
stmt,
},
Definition::Member(_) => Member {
Definition::Member(..) => Member {
parent,
kind: MemberKind::NestedClass(class),
kind: MemberKind::NestedClass,
stmt,
},
},
}

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