Compare commits

...

44 Commits

Author SHA1 Message Date
Charlie Marsh
125615af12 Bump version to 0.0.244 2023-02-08 17:28:59 -05:00
Charlie Marsh
6339f8e009 Use separate exit codes for fatal errors vs. lint errors (#2670) 2023-02-08 15:21:15 -05:00
Charlie Marsh
81abc5d7d8 Move error and warning messages into log macro (#2669) 2023-02-08 14:39:09 -05:00
Charlie Marsh
75fad989f4 Add --exit-non-zero-on-fix (#2668) 2023-02-08 14:27:54 -05:00
Charlie Marsh
cb4a221905 Treat annotated assignments in class and module scopes as runtime (#2667) 2023-02-08 13:59:25 -05:00
Charlie Marsh
286d8c18dd Remove ExprKind::Call from call path collection (#2666) 2023-02-08 13:35:18 -05:00
Florian Best
124461bddf test(UP003): let type reference be the builtin (#2664) 2023-02-08 12:44:37 -05:00
Charlie Marsh
7482a4a5b8 Avoid false-positive in chained type calls (#2663) 2023-02-08 12:18:36 -05:00
Charlie Marsh
9f9f25ff7c Accommodate multiple @pytest.mark.parametrize decorators (#2662) 2023-02-08 11:13:24 -05:00
Nuno Mendes
9cd1bf9c03 doc: add documentation for TRY002 (#2655) 2023-02-08 11:04:31 -05:00
Florian Best
3862dc2626 docs: use new command line arguments (#2658) 2023-02-08 10:36:53 -05:00
Charlie Marsh
2a0927a5ef Update Discord link 2023-02-08 04:36:59 -05:00
Charlie Marsh
824c0d2680 Implement whitespace-before-comment (E261, E262, E265, E266) (#2654) 2023-02-07 23:41:32 -05:00
Charlie Marsh
f5efdd058e Implement whitespace-around-keywords (E271, E272, E273, E274) (#2653) 2023-02-07 22:31:13 -05:00
Charlie Marsh
4c35feaa18 Add documentation for eradicate, flake8-import-conventions, and flake8-no-pep420 (#2652) 2023-02-07 22:19:21 -05:00
Charlie Marsh
8261d0656e Disable autofix for flake8-print rules (#2651) 2023-02-07 21:38:57 -05:00
Charlie Marsh
a9aa96b24f Add documentation for flake8-quotes rules (#2650) 2023-02-07 21:20:24 -05:00
Charlie Marsh
367f115d83 Add color to fixable error asterisk (#2647) 2023-02-07 19:12:18 -05:00
Charlie Marsh
56398e0002 Tweak format for rule explanations (#2645) 2023-02-07 19:02:41 -05:00
Ville Skyttä
4b49fd9494 Ignore all non-.py wrt. implicit namespace package (#2640)
It's not only `.pyi` that should be exempt for this, but also for example scripts which don't have an extension, explicitly passed in command line args.
2023-02-07 18:21:59 -05:00
Charlie Marsh
271e4fda8c Create per-rule pages and link from README (#2644) 2023-02-07 18:15:05 -05:00
Charlie Marsh
f1cdd108e6 Derive explanation method on Rule struct via rustdoc (#2642)
```console
❯ cargo run rule B017
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/ruff rule B017`
no-assert-raises-exception

Code: B017 (flake8-bugbear)

### What it does
Checks for `self.assertRaises(Exception)`.

## Why is this bad?
`assertRaises(Exception)` can lead to your test passing even if the
code being tested is never executed due to a typo.

Either assert for a more specific exception (builtin or custom), use
`assertRaisesRegex` or the context manager form of `assertRaises`.
```
2023-02-07 17:23:29 -05:00
Charlie Marsh
8fd29b3b60 Remove dependency on "unparse" feature (#2641) 2023-02-07 17:23:09 -05:00
Charlie Marsh
e427171323 Unify imports from rustpython_parser::ast (#2639) 2023-02-07 16:54:50 -05:00
Charlie Marsh
be08384fb0 Run cargo dev generate-all 2023-02-07 16:48:06 -05:00
Charlie Marsh
2f7f4943e3 Rename some local variables 2023-02-07 16:24:53 -05:00
Charlie Marsh
67e9ff7cc8 Reorder imports (#2638) 2023-02-07 16:22:47 -05:00
Charlie Marsh
0355ba571e Skip ternary fixes for yields and awaits (#2637) 2023-02-07 15:18:52 -05:00
Charlie Marsh
38db7fd114 Avoid boolean-trap errors in __setitem__ (#2636) 2023-02-07 15:04:33 -05:00
Charlie Marsh
8ee51eb5c6 Treat @staticmethod as higher-precedence than ABC (#2635) 2023-02-07 14:57:03 -05:00
Aarni Koskela
2bc16eb4e3 flake8-annotations: add ignore-fully-untyped (#2128)
This PR adds a configuration option to inhibit ANN* violations for functions that have no other annotations either, for easier gradual typing of a large codebase.
2023-02-07 11:35:57 -05:00
Charlie Marsh
4e36225145 Avoid no-unnecessary-dict-kwargs errors with reserved keywords (#2628) 2023-02-07 11:25:09 -05:00
Charlie Marsh
850069d0aa Avoid non-recursion in nested typing function calls (#2627) 2023-02-07 11:21:49 -05:00
Charlie Marsh
9fa98ed90b Accommodate pos-only arguments when checking self name (#2626) 2023-02-07 10:50:50 -05:00
Charlie Marsh
2b4ce78830 Delete unreferenced snapshots (#2619) 2023-02-06 23:22:41 -05:00
Colin Delahunty
7647cafe12 [pylint]: bidirectional-unicode (#2589) 2023-02-06 22:49:18 -05:00
Charlie Marsh
7686179318 Remove unused src/registry.rs 2023-02-06 22:43:25 -05:00
Charlie Marsh
bf718fdf26 Bump Ruff version to 0.0.243 2023-02-06 21:22:54 -05:00
Steve Dignam
3b3466f6da Add flake8-pie single_starts_ends_with (#2616) 2023-02-06 21:22:32 -05:00
Charlie Marsh
f981f491aa Support ignore-names for all relevant pep8-naming rules (#2617) 2023-02-06 21:14:55 -05:00
Charlie Marsh
95fef43c4d Add some additional tests for relative imports 2023-02-06 21:13:23 -05:00
Charlie Marsh
097c679cf3 Support relative paths for typing-modules (#2615) 2023-02-06 19:51:37 -05:00
Charlie Marsh
3bca987665 Avoid removing quotes from runtime annotations (#2614) 2023-02-06 18:15:19 -05:00
Ville Skyttä
60ee1d2c17 fix(pep8-naming): typing.NamedTuple and typing.TypedDict treatment (#2611) 2023-02-06 17:11:37 -05:00
456 changed files with 4344 additions and 2121 deletions

3
.gitignore vendored
View File

@@ -1,7 +1,8 @@
# Local cache
.ruff_cache
crates/ruff/resources/test/cpython
docs/
docs/*
!docs/rules
mkdocs.yml
.overrides

View File

@@ -10,7 +10,7 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/charliermarsh/ruff/issues) outlining your proposed
change. You can also join us on [**Discord**](https://discord.gg/Z8KbeK24) to discuss your idea with
change. You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with
the community.
If you're looking for a place to start, we recommend implementing a new lint rule (see:
@@ -38,7 +38,7 @@ cargo install cargo-insta
After cloning the repository, run Ruff locally with:
```shell
cargo run /path/to/file.py --no-cache
cargo run check /path/to/file.py --no-cache
```
Prior to opening a pull request, ensure that your code has been auto-formatted,
@@ -120,7 +120,7 @@ contain a variety of violations and non-violations designed to evaluate and demo
of your lint rule.
Run `cargo dev generate-all` to generate the code for your new fixture. Then run Ruff
locally with (e.g.) `cargo run crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
locally with (e.g.) `cargo run check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `crates/ruff/src/[linter]/mod.rs` file. Then, run `cargo test --all`.

16
Cargo.lock generated
View File

@@ -747,7 +747,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.242"
version = "0.0.244"
dependencies = [
"anyhow",
"clap 4.1.4",
@@ -1896,7 +1896,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.242"
version = "0.0.244"
dependencies = [
"anyhow",
"bisection",
@@ -1931,7 +1931,6 @@ dependencies = [
"ruff_macros",
"ruff_python",
"rustc-hash",
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"schemars",
@@ -1953,7 +1952,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.242"
version = "0.0.244"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1989,7 +1988,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.242"
version = "0.0.244"
dependencies = [
"anyhow",
"clap 4.1.4",
@@ -1998,7 +1997,6 @@ dependencies = [
"once_cell",
"ruff",
"ruff_cli",
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"schemars",
@@ -2010,8 +2008,9 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.242"
version = "0.0.244"
dependencies = [
"itertools",
"once_cell",
"proc-macro2",
"quote",
@@ -2021,7 +2020,7 @@ dependencies = [
[[package]]
name = "ruff_python"
version = "0.0.242"
version = "0.0.244"
dependencies = [
"once_cell",
"regex",
@@ -2076,7 +2075,6 @@ version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=adc23253e4b58980b407ba2760dbe61681d752fc#adc23253e4b58980b407ba2760dbe61681d752fc"
dependencies = [
"num-bigint",
"rustpython-common",
"rustpython-compiler-core",
]

137
README.md
View File

@@ -9,7 +9,7 @@
[![Actions status](https://github.com/charliermarsh/ruff/workflows/CI/badge.svg)](https://github.com/charliermarsh/ruff/actions)
[![image](https://img.shields.io/date/1676394000?label=Jetbrains%20Ruff%20Webinar&logo=jetbrains)](https://info.jetbrains.com/PyCharm-Webinar-February14-2023.html)
[**Discord**](https://discord.gg/Z8KbeK24) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
[**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://beta.ruff.rs/docs/) | [**Playground**](https://play.ruff.rs/)
An extremely fast Python linter, written in Rust.
@@ -213,16 +213,16 @@ apk add ruff
To run Ruff, try any of the following:
```shell
ruff . # Lint all files in the current directory (and any subdirectories)
ruff path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories)
ruff path/to/code/*.py # Lint all `.py` files in `/path/to/code`
ruff path/to/code/to/file.py # Lint `file.py`
ruff check . # Lint all files in the current directory (and any subdirectories)
ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories)
ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`
ruff check path/to/code/to/file.py # Lint `file.py`
```
You can run Ruff in `--watch` mode to automatically re-run on-change:
```shell
ruff path/to/code/ --watch
ruff check path/to/code/ --watch
```
Ruff also works with [pre-commit](https://pre-commit.com):
@@ -230,7 +230,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.242'
rev: 'v0.0.244'
hooks:
- id: ruff
```
@@ -377,7 +377,7 @@ Some configuration settings can be provided via the command-line, such as those
rule enablement and disablement, file discovery, logging level, and more:
```shell
ruff path/to/code/ --select F401 --select F403 --quiet
ruff check path/to/code/ --select F401 --select F403 --quiet
```
See `ruff help` for more on Ruff's top-level commands:
@@ -475,6 +475,8 @@ Miscellaneous:
The name of the file when passing it through stdin
-e, --exit-zero
Exit with status code "0", even upon detecting lint violations
--exit-non-zero-on-fix
Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain
Log levels:
-v, --verbose Enable verbose logging
@@ -533,7 +535,7 @@ By default, Ruff will also skip any files that are omitted via `.ignore`, `.giti
`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](#respect-gitignore)).
Files that are passed to `ruff` directly are always linted, regardless of the above criteria.
For example, `ruff /path/to/excluded/file.py` will always lint `file.py`.
For example, `ruff check /path/to/excluded/file.py` will always lint `file.py`.
### Rule resolution
@@ -557,9 +559,9 @@ select = ["E", "F"]
ignore = ["F401"]
```
Running `ruff --select F401` would result in Ruff enforcing `F401`, and no other rules.
Running `ruff check --select F401` would result in Ruff enforcing `F401`, and no other rules.
Running `ruff --extend-select B` would result in Ruff enforcing the `E`, `F`, and `B` rules, with
Running `ruff check --extend-select B` would result in Ruff enforcing the `E`, `F`, and `B` rules, with
the exception of `F401`.
### Suppressing errors
@@ -604,15 +606,15 @@ Ruff supports several workflows to aid in `noqa` management.
First, Ruff provides a special rule code, `RUF100`, to enforce that your `noqa` directives are
"valid", in that the violations they _say_ they ignore are actually being triggered on that line (and
thus suppressed). You can run `ruff /path/to/file.py --extend-select RUF100` to flag unused `noqa`
thus suppressed). You can run `ruff check /path/to/file.py --extend-select RUF100` to flag unused `noqa`
directives.
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
You can run `ruff /path/to/file.py --extend-select RUF100 --fix` to automatically remove unused
You can run `ruff check /path/to/file.py --extend-select RUF100 --fix` to automatically remove unused
`noqa` directives.
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to Ruff. You can run `ruff /path/to/file.py --add-noqa` to automatically
migrating a new codebase to Ruff. You can run `ruff check /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate rule codes.
#### Action comments
@@ -625,6 +627,25 @@ configuration.
See the [`isort` documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
for more.
#### Exit codes
By default, Ruff exits with the following status codes:
- `0` if no violations were found, or if all present violations were fixed automatically.
- `1` if violations were found.
- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an internal error.
This convention mirrors that of tools like ESLint, Prettier, and RuboCop.
Ruff supports two command-line flags that alter its exit code behavior:
- `--exit-zero` will cause Ruff to exit with a status code of `0` even if violations were found.
Note that Ruff will still exit with a status code of `2` if it terminates abnormally.
- `--exit-non-zero-on-fix` will cause Ruff to exit with a status code of `1` if violations were
found, _even if_ all such violations were fixed automatically. Note that the use of
`--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after
autofixing.
<!-- End section: Configuration -->
## Supported Rules
@@ -957,7 +978,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/) on PyPI
| B014 | duplicate-handler-exception | Exception handler with duplicate exception: `{name}` | 🛠 |
| B015 | useless-comparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
| B016 | cannot-raise-literal | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
| B017 | no-assert-raises-exception | `assertRaises(Exception)` should be considered evil | |
| [B017](https://github.com/charliermarsh/ruff/blob/main/docs/rules/assert-raises-exception.md) | [assert-raises-exception](https://github.com/charliermarsh/ruff/blob/main/docs/rules/assert-raises-exception.md) | `assertRaises(Exception)` should be considered evil | |
| B018 | useless-expression | Found useless expression. Either assign it to a variable or remove it. | |
| B019 | cached-instance-method | Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks | |
| B020 | loop-variable-overrides-iterator | Loop control variable `{name}` overrides iterable it iterates | |
@@ -1076,7 +1097,7 @@ For more, see [flake8-import-conventions](https://github.com/joaopalmeiro/flake8
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ICN001 | import-alias-is-not-conventional | `{name}` should be imported as `{asname}` | |
| [ICN001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unconventional-import-alias.md) | [unconventional-import-alias](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unconventional-import-alias.md) | `{name}` should be imported as `{asname}` | |
### flake8-logging-format (G)
@@ -1099,7 +1120,7 @@ For more, see [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/) on
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| INP001 | implicit-namespace-package | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
| [INP001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/implicit-namespace-package.md) | [implicit-namespace-package](https://github.com/charliermarsh/ruff/blob/main/docs/rules/implicit-namespace-package.md) | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
### flake8-pie (PIE)
@@ -1113,6 +1134,7 @@ For more, see [flake8-pie](https://pypi.org/project/flake8-pie/) on PyPI.
| PIE800 | no-unnecessary-spread | Unnecessary spread `**` | |
| PIE804 | no-unnecessary-dict-kwargs | Unnecessary `dict` kwargs | |
| PIE807 | prefer-list-builtin | Prefer `list` over useless lambda | 🛠 |
| PIE810 | single-starts-ends-with | Call `{attr}` once with a `tuple` | |
### flake8-print (T20)
@@ -1120,8 +1142,8 @@ For more, see [flake8-print](https://pypi.org/project/flake8-print/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T201 | print-found | `print` found | 🛠 |
| T203 | p-print-found | `pprint` found | 🛠 |
| T201 | print-found | `print` found | |
| T203 | p-print-found | `pprint` found | |
### flake8-pytest-style (PT)
@@ -1161,10 +1183,10 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| Q000 | bad-quotes-inline-string | Double quotes found but single quotes preferred | 🛠 |
| Q001 | bad-quotes-multiline-string | Double quote multiline found but single quotes preferred | 🛠 |
| Q002 | bad-quotes-docstring | Double quote docstring found but single quotes preferred | 🛠 |
| Q003 | avoid-quote-escape | Change outer quotes to avoid escaping inner quotes | 🛠 |
| [Q000](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-inline-string.md) | [bad-quotes-inline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-inline-string.md) | Double quotes found but single quotes preferred | 🛠 |
| [Q001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-multiline-string.md) | [bad-quotes-multiline-string](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-multiline-string.md) | Double quote multiline found but single quotes preferred | 🛠 |
| [Q002](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-docstring.md) | [bad-quotes-docstring](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bad-quotes-docstring.md) | Double quote docstring found but single quotes preferred | 🛠 |
| [Q003](https://github.com/charliermarsh/ruff/blob/main/docs/rules/avoid-quote-escape.md) | [avoid-quote-escape](https://github.com/charliermarsh/ruff/blob/main/docs/rules/avoid-quote-escape.md) | Change outer quotes to avoid escaping inner quotes | 🛠 |
### flake8-return (RET)
@@ -1284,7 +1306,7 @@ For more, see [eradicate](https://pypi.org/project/eradicate/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ERA001 | commented-out-code | Found commented-out code | 🛠 |
| [ERA001](https://github.com/charliermarsh/ruff/blob/main/docs/rules/commented-out-code.md) | [commented-out-code](https://github.com/charliermarsh/ruff/blob/main/docs/rules/commented-out-code.md) | Found commented-out code | 🛠 |
### pandas-vet (PD)
@@ -1337,6 +1359,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/) on PyPI.
| PLE0605 | invalid-all-format | Invalid format for `__all__`, must be `tuple` or `list` | |
| PLE1142 | await-outside-async | `await` should be used within an async function | |
| PLE1310 | bad-str-strip-call | String `{strip}` call contains duplicate characters (did you mean `{removal}`?) | |
| PLE2502 | bidirectional-unicode | Avoid using bidirectional unicode | |
#### Refactor (PLR)
@@ -1366,7 +1389,7 @@ For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TRY002 | raise-vanilla-class | Create your own exception | |
| [TRY002](https://github.com/charliermarsh/ruff/blob/main/docs/rules/raise-vanilla-class.md) | [raise-vanilla-class](https://github.com/charliermarsh/ruff/blob/main/docs/rules/raise-vanilla-class.md) | Create your own exception | |
| TRY003 | raise-vanilla-args | Avoid specifying long messages outside the exception class | |
| TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | 🛠 |
| TRY200 | reraise-no-cause | Use `raise from` to specify exception cause | |
@@ -1560,11 +1583,11 @@ let g:ale_fixers = {
```yaml
tools:
python-ruff: &python-ruff
lint-command: "ruff --config ~/myconfigs/linters/ruff.toml --quiet ${INPUT}"
lint-command: "ruff check --config ~/myconfigs/linters/ruff.toml --quiet ${INPUT}"
lint-stdin: true
lint-formats:
- "%f:%l:%c: %m"
format-command: "ruff --stdin-filename ${INPUT} --config ~/myconfigs/linters/ruff.toml --fix --exit-zero --quiet -"
format-command: "ruff check --stdin-filename ${INPUT} --config ~/myconfigs/linters/ruff.toml --fix --exit-zero --quiet -"
format-stdin: true
```
@@ -1626,7 +1649,7 @@ jobs:
pip install ruff
# Include `--format=github` to enable automatic inline annotations.
- name: Run Ruff
run: ruff --format=github .
run: ruff check --format=github .
```
<!-- End section: Editor Integrations -->
@@ -1892,7 +1915,7 @@ matter how they're provided, which avoids accidental incompatibilities and simpl
### How can I tell what settings Ruff is using to check my code?
Run `ruff /path/to/code.py --show-settings` to view the resolved settings for a given file.
Run `ruff check /path/to/code.py --show-settings` to view the resolved settings for a given file.
### I want to use Ruff, but I don't want to use `pyproject.toml`. Is that possible?
@@ -1958,14 +1981,14 @@ If you find a case where Ruff's autofix breaks your code, please file an Issue!
Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md). You
can also join us on [**Discord**](https://discord.gg/Z8KbeK24).
can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Support
Having trouble? Check out the existing issues on [**GitHub**](https://github.com/charliermarsh/ruff/issues),
or feel free to [**open a new one**](https://github.com/charliermarsh/ruff/issues/new).
You can also ask for help on [**Discord**](https://discord.gg/Z8KbeK24).
You can also ask for help on [**Discord**](https://discord.gg/c9MhzV8aU5).
## Reference
@@ -2249,8 +2272,8 @@ fix-only = true
#### [`fixable`](#fixable)
A list of rule codes or prefixes to consider autofixable. By default, all rules are
considered autofixable.
A list of rule codes or prefixes to consider autofixable. By default,
all rules are considered autofixable.
**Default value**: `["A", "ANN", "ARG", "B", "BLE", "C", "COM", "D", "DTZ", "E", "EM", "ERA", "EXE", "F", "FBT", "G", "I", "ICN", "INP", "ISC", "N", "PD", "PGH", "PIE", "PL", "PT", "PTH", "Q", "RET", "RUF", "S", "SIM", "T", "TCH", "TID", "TRY", "UP", "W", "YTT"]`
@@ -2661,6 +2684,25 @@ allow-star-arg-any = true
---
#### [`ignore-fully-untyped`](#ignore-fully-untyped)
Whether to suppress `ANN*` rules for any declaration
that hasn't been typed at all.
This makes it easier to gradually add types to a codebase.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-annotations]
ignore-fully-untyped = true
```
---
#### [`mypy-init-return`](#mypy-init-return)
Whether to allow the omission of a return type hint for `__init__` if at
@@ -2723,8 +2765,9 @@ suppress-none-returning = true
#### [`check-typed-exception`](#check-typed-exception)
Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default,
`try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.
Whether to disallow `try`-`except`-`pass` (`S110`) for specific
exception types. By default, `try`-`except`-`pass` is only
disallowed for `Exception` and `BaseException`.
**Default value**: `false`
@@ -2958,8 +3001,8 @@ The following values are supported:
* `csv` — a comma-separated list, e.g.
`@pytest.mark.parametrize('name1,name2', ...)`
* `tuple` (default) — e.g.
`@pytest.mark.parametrize(('name1', 'name2'), ...)`
* `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'),
...)`
* `list` — e.g. `@pytest.mark.parametrize(['name1', 'name2'], ...)`
**Default value**: `tuple`
@@ -2980,10 +3023,10 @@ parametrize-names-type = "list"
Expected type for each row of values in `@pytest.mark.parametrize` in
case of multiple parameters. The following values are supported:
* `tuple` (default) — e.g.
`@pytest.mark.parametrize(('name1', 'name2'), [(1, 2), (3, 4)])`
* `list` — e.g.
`@pytest.mark.parametrize(('name1', 'name2'), [[1, 2], [3, 4]])`
* `tuple` (default) — e.g. `@pytest.mark.parametrize(('name1', 'name2'),
[(1, 2), (3, 4)])`
* `list` — e.g. `@pytest.mark.parametrize(('name1', 'name2'), [[1, 2],
[3, 4]])`
**Default value**: `tuple`
@@ -3746,7 +3789,8 @@ allow-magic-value-types = ["int"]
#### [`max-args`](#max-args)
Maximum number of arguments allowed for a function or method definition (see: `PLR0913`).
Maximum number of arguments allowed for a function or method definition
(see: `PLR0913`).
**Default value**: `5`
@@ -3763,7 +3807,8 @@ max-args = 5
#### [`max-branches`](#max-branches)
Maximum number of branches allowed for a function or method body (see: `PLR0912`).
Maximum number of branches allowed for a function or method body (see:
`PLR0912`).
**Default value**: `12`
@@ -3780,7 +3825,8 @@ max-branches = 12
#### [`max-returns`](#max-returns)
Maximum number of return statements allowed for a function or method body (see `PLR0911`)
Maximum number of return statements allowed for a function or method
body (see `PLR0911`)
**Default value**: `6`
@@ -3797,7 +3843,8 @@ max-returns = 6
#### [`max-statements`](#max-statements)
Maximum number of statements allowed for a function or method body (see: `PLR0915`).
Maximum number of statements allowed for a function or method body (see:
`PLR0915`).
**Default value**: `50`

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.242"
version = "0.0.244"
edition = "2021"
[dependencies]

View File

@@ -5,7 +5,9 @@ use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use configparser::ini::Ini;
use ruff::flake8_to_ruff::{self, ExternalConfig};
use ruff::logging::{set_up_logging, LogLevel};
#[derive(Parser)]
#[command(
@@ -27,6 +29,8 @@ struct Args {
}
fn main() -> Result<()> {
set_up_logging(&LogLevel::Default)?;
let args = Args::parse();
// Read the INI file.

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.242"
version = "0.0.244"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -39,10 +39,9 @@ num-traits = "0.2.15"
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.242", path = "../ruff_macros" }
ruff_python = { version = "0.0.242", path = "../ruff_python" }
ruff_macros = { version = "0.0.244", path = "../ruff_macros" }
ruff_python = { version = "0.0.244", path = "../ruff_python" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
schemars = { version = "0.8.11" }

View File

@@ -0,0 +1,44 @@
"""Test case expected to be run with `ignore_fully_untyped = True`."""
def ok_fully_untyped_1(a, b):
pass
def ok_fully_untyped_2():
pass
def ok_fully_typed_1(a: int, b: int) -> int:
pass
def ok_fully_typed_2() -> int:
pass
def ok_fully_typed_3(a: int, *args: str, **kwargs: str) -> int:
pass
def error_partially_typed_1(a: int, b):
pass
def error_partially_typed_2(a: int, b) -> int:
pass
def error_partially_typed_3(a: int, b: int):
pass
class X:
def ok_untyped_method_with_arg(self, a):
pass
def ok_untyped_method(self):
pass
def error_typed_self(self: X):
pass

View File

@@ -53,3 +53,8 @@ def foo():
return True
else:
return
# Error (on the argument, but not the return type)
def foo(a):
a = 2 + 2

View File

@@ -57,3 +57,12 @@ dict.fromkeys(("world",), True)
{}.deploy(True, False)
getattr(someobj, attrname, False)
mylist.index(True)
class Registry:
def __init__(self) -> None:
self._switches = [False] * len(Switch)
# FBT001: Boolean positional arg in function definition
def __setitem__(self, switch: Switch, value: bool) -> None:
self._switches[switch.value] = value

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env python3
print("Hello, INP001!")

View File

@@ -17,3 +17,4 @@ foo(**{"1foo": True})
foo(**{buzz: True})
foo(**{"": True})
foo(**{f"buzz__{bar}": True})
abc(**{"for": 3})

View File

@@ -0,0 +1,17 @@
# error
obj.startswith("foo") or obj.startswith("bar")
# error
obj.endswith("foo") or obj.endswith("bar")
# error
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))
# ok
obj.endswith(("foo", "bar"))
# ok
obj.startswith("foo") or obj.endswith("bar")
# ok
obj.startswith("foo") or abc.startswith("bar")

View File

@@ -75,3 +75,9 @@ def test_csv_name_list_of_lists(param1, param2):
)
def test_single_list_of_lists(param):
...
@pytest.mark.parametrize("a", [1, 2])
@pytest.mark.parametrize(("b", "c"), ((3, 4), (5, 6)))
def test_multiple_decorators(a, b, c):
pass

View File

@@ -110,3 +110,11 @@ if True: # Foo
x = 3
else:
x = 5
# OK
def f():
if True:
x = yield 3
else:
x = yield 5

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Tuple, List, Dict
x: Tuple
class C:
x: List
def f():
x: Dict

View File

@@ -0,0 +1,14 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Tuple, List, Dict
x: Tuple
class C:
x: List
def f():
x: Dict

View File

@@ -1,5 +1,5 @@
from abc import abstractmethod
from typing import overload
from typing import overload, cast
from typing_extensions import override
@@ -195,3 +195,10 @@ class C:
def __exit__(self, exc_type, exc_value, traceback) -> None:
print("Hello, world!")
###
# Used arguments on chained cast.
###
def f(x: None) -> None:
_ = cast(Any, _identity)(x=x)

View File

@@ -5,3 +5,7 @@ def func(_, a, A):
class Class:
def method(self, _, a, A):
return _, a, A
def func(_, setUp):
return _, setUp

View File

@@ -46,6 +46,10 @@ class MetaClass(ABCMeta):
def good_method(cls):
pass
@staticmethod
def static_method(not_cls) -> bool:
return False
def func(x):
return x

View File

@@ -51,3 +51,11 @@ class MetaClass(ABCMeta):
def func(x):
return x
class PosOnlyClass:
def good_method_pos_only(self, blah, /, something: str):
pass
def bad_method_pos_only(this, blah, /, self, something: str):
pass

View File

@@ -2,6 +2,7 @@ import collections
from collections import namedtuple
from typing import TypeVar
from typing import NewType
from typing import NamedTuple, TypedDict
GLOBAL: str = "foo"
@@ -20,6 +21,10 @@ def assign():
T = TypeVar("T")
UserId = NewType("UserId", int)
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})
def aug_assign(rank, world_size):
global CURRENT_PORT

View File

@@ -1,5 +1,6 @@
import collections
from collections import namedtuple
from typing import NamedTuple, TypedDict
class C:
@@ -10,3 +11,5 @@ class C:
mixed_Case = 0
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
myObj2 = namedtuple("MyObj2", ["a", "b"])
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})

View File

@@ -1,5 +1,6 @@
import collections
from collections import namedtuple
from typing import NamedTuple, TypedDict
lower = 0
CONSTANT = 0
@@ -8,3 +9,5 @@ _mixedCase = 0
mixed_Case = 0
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
myObj2 = namedtuple("MyObj2", ["a", "b"])
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Point2D = TypedDict('Point2D', {'in': int, 'x-y': int})

View File

@@ -0,0 +1,66 @@
#: E261:1:5
pass # an inline comment
#: E262:1:12
x = x + 1 #Increment x
#: E262:1:12
x = x + 1 # Increment x
#: E262:1:12
x = y + 1 #: Increment x
#: E265:1:1
#Block comment
a = 1
#: E265:2:1
m = 42
#! This is important
mx = 42 - 42
#: E266:3:5 E266:6:5
def how_it_feel(r):
### This is a variable ###
a = 42
### Of course it is unused
return
#: E265:1:1 E266:2:1
##if DEBUG:
## logging.error()
#: W291:1:42
#########################################
#:
#: Okay
#!/usr/bin/env python
pass # an inline comment
x = x + 1 # Increment x
y = y + 1 #: Increment x
# Block comment
a = 1
# Block comment1
# Block comment2
aaa = 1
# example of docstring (not parsed)
def oof():
"""
#foo not parsed
"""
####################################################################
# A SEPARATOR #
####################################################################
# ################################################################ #
# ####################### another separator ###################### #
# ################################################################ #
#: E262:3:9
# -*- coding: utf8 -*-
#  (One space one NBSP) Ok for block comment
a = 42 #  (One space one NBSP)
#: E262:2:9
# (Two spaces) Ok for block comment
a = 42 # (Two spaces)

View File

@@ -0,0 +1,58 @@
#: Okay
True and False
#: E271
True and False
#: E272
True and False
#: E271
if 1:
#: E273
True and False
#: E273 E274
True and False
#: E271
a and b
#: E271
1 and b
#: E271
a and 2
#: E271 E272
1 and b
#: E271 E272
a and 2
#: E272
this and False
#: E273
a and b
#: E274
a and b
#: E273 E274
this and False
#: Okay
from u import (a, b)
from v import c, d
#: E271
from w import (e, f)
#: E275
from w import(e, f)
#: E275
from importable.module import(e, f)
#: E275
try:
from importable.module import(e, f)
except ImportError:
pass
#: E275
if(foo):
pass
else:
pass
#: Okay
matched = {"true": True, "false": False}
#: E275:2:11
if True:
assert(1)
#: Okay
def f():
print((yield))
x = (yield)

View File

@@ -0,0 +1,26 @@
def f():
from typing import Literal
# OK
x: Literal["foo"]
def f():
from .typical import Literal
# OK
x: Literal["foo"]
def f():
from . import typical
# OK
x: typical.Literal["foo"]
def f():
from .atypical import Literal
# F821
x: Literal["foo"]

View File

@@ -0,0 +1,33 @@
def f():
from typing import Literal
# OK
x: Literal["foo"]
def f():
from ..typical import Literal
# OK
x: Literal["foo"]
def f():
from .. import typical
# OK
x: typical.Literal["foo"]
def f():
from .typical import Literal
# F821
x: Literal["foo"]
def f():
from .atypical import Literal
# F821
x: Literal["foo"]

View File

@@ -0,0 +1,24 @@
# E2502
print("\u202B\u202E\u05e9\u05DC\u05D5\u05DD\u202C")
# E2502
print("שלום‬")
# E2502
example = "x" * 100 # "x" is assigned
# E2502
if access_level != "none": # Check if admin ' and access_level != 'user
print("You are an admin.")
# E2502
def subtract_funds(account: str, amount: int):
"""Subtract funds from bank account then """
return
bank[account] -= amount
return
# OK
print("Hello World")

View File

@@ -4,6 +4,9 @@ type(0)
type(0.0)
type(0j)
# OK
type(arg)(" ")
# OK
y = x.dtype.type(0.0)

View File

@@ -66,12 +66,6 @@ x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
x: NamedTuple(typename="X", fields=[("foo", "int")])
x = TypeVar("x", "str", "int")
x = cast("str", x)
X = List["MyClass"]
X: MyCallable("X")
@@ -106,3 +100,9 @@ def foo(*, inplace):
x: Annotated[1:2] = ...
x = TypeVar("x", "str", "int")
x = cast("str", x)
X = List["MyClass"]

View File

@@ -1,9 +1,8 @@
use std::cmp::Ordering;
use rustc_hash::FxHashMap;
use rustpython_ast::ExcepthandlerKind::ExceptHandler;
use rustpython_ast::Stmt;
use rustpython_parser::ast::StmtKind;
use rustpython_parser::ast::ExcepthandlerKind::ExceptHandler;
use rustpython_parser::ast::{Stmt, StmtKind};
use crate::ast::types::RefEquality;

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Expr, Stmt, StmtKind};
use rustpython_parser::ast::{Expr, Stmt, StmtKind};
pub fn name(stmt: &Stmt) -> &str {
match &stmt.node {

View File

@@ -2,7 +2,7 @@
//! ability to compare expressions for equality (via [`Eq`] and [`Hash`]).
use num_bigint::BigInt;
use rustpython_ast::{
use rustpython_parser::ast::{
Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Expr, ExprContext, ExprKind, Keyword,
Operator, Unaryop,
};

View File

@@ -1,4 +1,4 @@
use rustpython_ast::Expr;
use rustpython_parser::ast::Expr;
use crate::ast::helpers::to_call_path;
use crate::ast::types::{Scope, ScopeKind};
@@ -26,8 +26,18 @@ pub fn classify(
let ScopeKind::Class(scope) = &scope.kind else {
return FunctionType::Function;
};
// Special-case class method, like `__new__`.
if CLASS_METHODS.contains(&name)
if decorator_list.iter().any(|expr| {
// The method is decorated with a static method decorator (like
// `@staticmethod`).
checker.resolve_call_path(expr).map_or(false, |call_path| {
staticmethod_decorators
.iter()
.any(|decorator| call_path == to_call_path(decorator))
})
}) {
FunctionType::StaticMethod
} else if CLASS_METHODS.contains(&name)
// Special-case class method, like `__new__`.
|| scope.bases.iter().any(|expr| {
// The class itself extends a known metaclass, so all methods are class methods.
checker.resolve_call_path(expr).map_or(false, |call_path| {
@@ -46,16 +56,6 @@ pub fn classify(
})
{
FunctionType::ClassMethod
} else if decorator_list.iter().any(|expr| {
// The method is decorated with a static method decorator (like
// `@staticmethod`).
checker.resolve_call_path(expr).map_or(false, |call_path| {
staticmethod_decorators
.iter()
.any(|decorator| call_path == to_call_path(decorator))
})
}) {
FunctionType::StaticMethod
} else {
// It's an instance method.
FunctionType::Method

View File

@@ -1,6 +1,6 @@
use std::hash::Hash;
use rustpython_ast::Expr;
use rustpython_parser::ast::Expr;
use crate::ast::comparable::ComparableExpr;

View File

@@ -1,16 +1,18 @@
use std::path::Path;
use itertools::Itertools;
use log::error;
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{
use rustpython_parser::ast::{
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
Located, Location, Stmt, StmtKind,
};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use rustpython_parser::token::StringKind;
use smallvec::smallvec;
use smallvec::{smallvec, SmallVec};
use crate::ast::types::{Binding, BindingKind, CallPath, Range};
use crate::ast::visitor;
@@ -28,23 +30,29 @@ pub fn create_stmt(node: StmtKind) -> Stmt {
Stmt::new(Location::default(), Location::default(), node)
}
/// Generate source code from an `Expr`.
/// Generate source code from an [`Expr`].
pub fn unparse_expr(expr: &Expr, stylist: &Stylist) -> String {
let mut generator: Generator = stylist.into();
generator.unparse_expr(expr, 0);
generator.generate()
}
/// Generate source code from an `Stmt`.
/// Generate source code from a [`Stmt`].
pub fn unparse_stmt(stmt: &Stmt, stylist: &Stylist) -> String {
let mut generator: Generator = stylist.into();
generator.unparse_stmt(stmt);
generator.generate()
}
/// Generate source code from an [`Constant`].
pub fn unparse_constant(constant: &Constant, stylist: &Stylist) -> String {
let mut generator: Generator = stylist.into();
generator.unparse_constant(constant);
generator.generate()
}
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut CallPath<'a>) -> bool {
match &expr.node {
ExprKind::Call { func, .. } => collect_call_path_inner(func, parts),
ExprKind::Attribute { value, attr, .. } => {
if collect_call_path_inner(value, parts) {
parts.push(attr);
@@ -657,6 +665,38 @@ pub fn to_call_path(target: &str) -> CallPath {
}
}
/// Create a module path from a (package, path) pair.
///
/// For example, if the package is `foo/bar` and the path is `foo/bar/baz.py`,
/// the call path is `["baz"]`.
pub fn to_module_path(package: &Path, path: &Path) -> Option<Vec<String>> {
path.strip_prefix(package.parent()?)
.ok()?
.iter()
.map(Path::new)
.map(std::path::Path::file_stem)
.map(|path| path.and_then(|path| path.to_os_string().into_string().ok()))
.collect::<Option<Vec<String>>>()
}
/// Create a call path from a relative import.
pub fn from_relative_import<'a>(module: &'a [String], name: &'a str) -> CallPath<'a> {
let mut call_path: CallPath = SmallVec::with_capacity(module.len() + 1);
// Start with the module path.
call_path.extend(module.iter().map(String::as_str));
// Remove segments based on the number of dots.
for _ in 0..name.chars().take_while(|c| *c == '.').count() {
call_path.pop();
}
// Add the remaining segments.
call_path.extend(name.trim_start_matches('.').split('.'));
call_path
}
/// A [`Visitor`] that collects all return statements in a function or method.
#[derive(Default)]
pub struct ReturnStatementVisitor<'a> {
@@ -1098,7 +1138,7 @@ pub fn is_logger_candidate(func: &Expr) -> bool {
#[cfg(test)]
mod tests {
use anyhow::Result;
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use rustpython_parser::parser;
use crate::ast::helpers::{

View File

@@ -1,7 +1,6 @@
use bitflags::bitflags;
use rustc_hash::FxHashMap;
use rustpython_ast::{Cmpop, Located};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Located, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
@@ -340,7 +339,7 @@ pub fn locate_cmpops(contents: &str) -> Vec<LocatedCmpop> {
#[cfg(test)]
mod tests {
use rustpython_ast::{Cmpop, Location};
use rustpython_parser::ast::{Cmpop, Location};
use crate::ast::operations::{locate_cmpops, LocatedCmpop};

View File

@@ -2,8 +2,7 @@ use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use rustc_hash::FxHashMap;
use rustpython_ast::{Arguments, Expr, Keyword, Stmt};
use rustpython_parser::ast::{Located, Location};
use rustpython_parser::ast::{Arguments, Expr, Keyword, Located, Location, Stmt};
fn id() -> usize {
static COUNTER: AtomicUsize = AtomicUsize::new(1);
@@ -89,8 +88,9 @@ pub struct Scope<'a> {
pub uses_locals: bool,
/// A map from bound name to binding index, for live bindings.
pub bindings: FxHashMap<&'a str, usize>,
/// A map from bound name to binding index, for bindings that were created in the scope but
/// rebound (and thus overridden) later on in the same scope.
/// A map from bound name to binding index, for bindings that were created
/// in the scope but rebound (and thus overridden) later on in the same
/// scope.
pub rebounds: FxHashMap<&'a str, Vec<usize>>,
}

View File

@@ -1,6 +1,5 @@
use rustpython_ast::{Expr, ExprKind};
use ruff_python::typing::{PEP_585_BUILTINS_ELIGIBLE, PEP_593_SUBSCRIPTS, SUBSCRIPTS};
use rustpython_parser::ast::{Expr, ExprKind};
use crate::ast::types::CallPath;

View File

@@ -1,6 +1,6 @@
use std::str::Lines;
use rustpython_ast::{Located, Location};
use rustpython_parser::ast::{Located, Location};
use crate::ast::types::Range;
use crate::source_code::Locator;

View File

@@ -429,7 +429,7 @@ pub fn remove_argument(
#[cfg(test)]
mod tests {
use anyhow::Result;
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use rustpython_parser::parser;
use crate::autofix::helpers::{next_stmt_break, trailing_semicolon};

View File

@@ -1,7 +1,7 @@
use std::collections::BTreeSet;
use itertools::Itertools;
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::fix::Fix;

View File

@@ -6,20 +6,20 @@ use std::path::Path;
use itertools::Itertools;
use log::error;
use nohash_hasher::IntMap;
use ruff_python::builtins::{BUILTINS, MAGIC_GLOBALS};
use ruff_python::typing::TYPING_EXTENSIONS;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Comprehension, Located, Location};
use rustpython_common::cformat::{CFormatError, CFormatErrorType};
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
Arg, Arguments, Comprehension, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext,
ExprKind, KeywordData, Located, Location, Operator, Stmt, StmtKind, Suite,
};
use rustpython_parser::parser;
use smallvec::smallvec;
use ruff_python::builtins::{BUILTINS, MAGIC_GLOBALS};
use ruff_python::typing::TYPING_EXTENSIONS;
use crate::ast::helpers::{binding_range, collect_call_path, extract_handler_names};
use crate::ast::helpers::{
binding_range, collect_call_path, extract_handler_names, from_relative_import, to_module_path,
};
use crate::ast::operations::{extract_all_names, AllNamesFlags};
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
@@ -55,6 +55,7 @@ type DeferralContext<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
pub struct Checker<'a> {
// Input data.
pub(crate) path: &'a Path,
module_path: Option<Vec<String>>,
package: Option<&'a Path>,
autofix: flags::Autofix,
noqa: flags::Noqa,
@@ -119,6 +120,7 @@ impl<'a> Checker<'a> {
noqa: flags::Noqa,
path: &'a Path,
package: Option<&'a Path>,
module_path: Option<Vec<String>>,
locator: &'a Locator,
style: &'a Stylist,
indexer: &'a Indexer,
@@ -130,6 +132,7 @@ impl<'a> Checker<'a> {
noqa,
path,
package,
module_path,
locator,
stylist: style,
indexer,
@@ -234,32 +237,36 @@ impl<'a> Checker<'a> {
if let Some(head) = call_path.first() {
if let Some(binding) = self.find_binding(head) {
match &binding.kind {
BindingKind::Importation(.., name) => {
// Ignore relative imports.
if name.starts_with('.') {
return None;
}
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
return Some(source_path);
}
BindingKind::SubmoduleImportation(name, ..) => {
// Ignore relative imports.
if name.starts_with('.') {
return None;
}
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
return Some(source_path);
BindingKind::Importation(.., name)
| BindingKind::SubmoduleImportation(name, ..) => {
return if name.starts_with('.') {
if let Some(module) = &self.module_path {
let mut source_path = from_relative_import(module, name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
} else {
None
}
} else {
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
};
}
BindingKind::FromImportation(.., name) => {
// Ignore relative imports.
if name.starts_with('.') {
return None;
}
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
return Some(source_path);
return if name.starts_with('.') {
if let Some(module) = &self.module_path {
let mut source_path = from_relative_import(module, name);
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
} else {
None
}
} else {
let mut source_path: CallPath = name.split('.').collect();
source_path.extend(call_path.into_iter().skip(1));
Some(source_path)
};
}
BindingKind::Builtin => {
let mut source_path: CallPath = smallvec![];
@@ -687,6 +694,24 @@ where
flake8_pytest_style::rules::marks(self, decorator_list);
}
if self
.settings
.rules
.enabled(&Rule::BooleanPositionalArgInFunctionDefinition)
{
flake8_boolean_trap::rules::check_positional_boolean_in_def(self, name, args);
}
if self
.settings
.rules
.enabled(&Rule::BooleanDefaultValueInFunctionDefinition)
{
flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition(
self, name, args,
);
}
self.check_builtin_shadowing(name, stmt, true);
// Visit the decorators and arguments, but avoid the body, which will be
@@ -1039,7 +1064,7 @@ where
if self
.settings
.rules
.enabled(&Rule::ImportAliasIsNotConventional)
.enabled(&Rule::UnconventionalImportAlias)
{
if let Some(diagnostic) =
flake8_import_conventions::rules::check_conventional_import(
@@ -1298,7 +1323,7 @@ where
if self
.settings
.rules
.enabled(&Rule::ImportAliasIsNotConventional)
.enabled(&Rule::UnconventionalImportAlias)
{
let full_name = helpers::format_import_from_member(
level.as_ref(),
@@ -1538,7 +1563,7 @@ where
}
}
StmtKind::With { items, body, .. } => {
if self.settings.rules.enabled(&Rule::NoAssertRaisesException) {
if self.settings.rules.enabled(&Rule::AssertRaisesException) {
flake8_bugbear::rules::assert_raises_exception(self, stmt, items);
}
if self
@@ -1920,7 +1945,21 @@ where
value,
..
} => {
self.visit_annotation(annotation);
// If we're in a class or module scope, then the annotation needs to be available
// at runtime.
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
if !self.annotations_future_enabled
&& matches!(
self.current_scope().kind,
ScopeKind::Class(..) | ScopeKind::Module
)
{
self.in_type_definition = true;
self.visit_expr(annotation);
self.in_type_definition = false;
} else {
self.visit_annotation(annotation);
}
if let Some(expr) = value {
if self.match_typing_expr(annotation, "TypeAlias") {
self.in_type_definition = true;
@@ -2276,10 +2315,10 @@ where
pyupgrade::rules::open_alias(self, expr, func);
}
if self.settings.rules.enabled(&Rule::ReplaceUniversalNewlines) {
pyupgrade::rules::replace_universal_newlines(self, expr, keywords);
pyupgrade::rules::replace_universal_newlines(self, func, keywords);
}
if self.settings.rules.enabled(&Rule::ReplaceStdoutStderr) {
pyupgrade::rules::replace_stdout_stderr(self, expr, keywords);
pyupgrade::rules::replace_stdout_stderr(self, expr, func, keywords);
}
if self.settings.rules.enabled(&Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias(self, &expr);
@@ -2310,7 +2349,7 @@ where
.rules
.enabled(&Rule::UselessContextlibSuppress)
{
flake8_bugbear::rules::useless_contextlib_suppress(self, expr, args);
flake8_bugbear::rules::useless_contextlib_suppress(self, expr, func, args);
}
if self
.settings
@@ -3098,6 +3137,9 @@ where
if self.settings.rules.enabled(&Rule::RewriteUnicodeLiteral) {
pyupgrade::rules::rewrite_unicode_literal(self, expr, kind.as_deref());
}
if self.settings.rules.enabled(&Rule::BidirectionalUnicode) {
pylint::rules::bidirectional_unicode(self, expr, value);
}
}
ExprKind::Lambda { args, body, .. } => {
if self.settings.rules.enabled(&Rule::PreferListBuiltin) {
@@ -3178,6 +3220,9 @@ where
{
pylint::rules::merge_isinstance(self, expr, op, values);
}
if self.settings.rules.enabled(&Rule::SingleStartsEndsWith) {
flake8_pie::rules::single_starts_ends_with(self, values, op);
}
if self.settings.rules.enabled(&Rule::DuplicateIsinstanceCall) {
flake8_simplify::rules::duplicate_isinstance_call(self, expr);
}
@@ -3640,24 +3685,6 @@ where
flake8_bugbear::rules::function_call_argument_default(self, arguments);
}
// flake8-boolean-trap
if self
.settings
.rules
.enabled(&Rule::BooleanPositionalArgInFunctionDefinition)
{
flake8_boolean_trap::rules::check_positional_boolean_in_def(self, arguments);
}
if self
.settings
.rules
.enabled(&Rule::BooleanDefaultValueInFunctionDefinition)
{
flake8_boolean_trap::rules::check_boolean_default_value_in_function_definition(
self, arguments,
);
}
// Bind, but intentionally avoid walking default expressions, as we handle them
// upstream.
for arg in &arguments.posonlyargs {
@@ -3702,8 +3729,11 @@ where
}
if self.settings.rules.enabled(&Rule::InvalidArgumentName) {
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(&arg.node.arg, arg)
{
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(
&arg.node.arg,
arg,
&self.settings.pep8_naming.ignore_names,
) {
self.diagnostics.push(diagnostic);
}
}
@@ -3937,8 +3967,8 @@ impl<'a> Checker<'a> {
// Avoid overriding builtins.
binding
} else if matches!(self.bindings[*index].kind, BindingKind::Global) {
// If the original binding was a global, and the new binding conflicts within the
// current scope, then the new binding is also a global.
// If the original binding was a global, and the new binding conflicts within
// the current scope, then the new binding is also a global.
Binding {
runtime_usage: self.bindings[*index].runtime_usage,
synthetic_usage: self.bindings[*index].synthetic_usage,
@@ -3947,8 +3977,8 @@ impl<'a> Checker<'a> {
..binding
}
} else if matches!(self.bindings[*index].kind, BindingKind::Nonlocal) {
// If the original binding was a nonlocal, and the new binding conflicts within the
// current scope, then the new binding is also a nonlocal.
// If the original binding was a nonlocal, and the new binding conflicts within
// the current scope, then the new binding is also a nonlocal.
Binding {
runtime_usage: self.bindings[*index].runtime_usage,
synthetic_usage: self.bindings[*index].synthetic_usage,
@@ -4391,7 +4421,7 @@ impl<'a> Checker<'a> {
self.deferred_string_type_definitions.pop()
{
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
if self.annotations_future_enabled {
if in_annotation && self.annotations_future_enabled {
if self.settings.rules.enabled(&Rule::QuotedAnnotation) {
pyupgrade::rules::quoted_annotation(self, expression, range);
}
@@ -5061,7 +5091,12 @@ impl<'a> Checker<'a> {
&overloaded_name,
)
}) {
flake8_annotations::rules::definition(self, &definition, &visibility);
self.diagnostics
.extend(flake8_annotations::rules::definition(
self,
&definition,
&visibility,
));
}
overloaded_name = flake8_annotations::helpers::overloaded_name(self, &definition);
}
@@ -5285,6 +5320,7 @@ pub fn check_ast(
noqa,
path,
package,
package.and_then(|package| to_module_path(package, path)),
locator,
stylist,
indexer,

View File

@@ -1,12 +1,15 @@
use bisection::bisect_left;
use itertools::Itertools;
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer::LexResult;
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::rules::pycodestyle::logical_lines::iter_logical_lines;
use crate::rules::pycodestyle::rules::{extraneous_whitespace, indentation, space_around_operator};
use crate::rules::pycodestyle::logical_lines::{iter_logical_lines, TokenFlags};
use crate::rules::pycodestyle::rules::{
extraneous_whitespace, indentation, space_around_operator, whitespace_around_keywords,
whitespace_before_comment,
};
use crate::settings::Settings;
use crate::source_code::{Locator, Stylist};
@@ -57,7 +60,7 @@ pub fn check_logical_lines(
// Generate mapping from logical to physical offsets.
let mapping_offsets = line.mapping.iter().map(|(offset, _)| *offset).collect_vec();
if line.operator {
if line.flags.contains(TokenFlags::OPERATOR) {
for (index, kind) in space_around_operator(&line.text) {
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
let location = Location::new(pos.row(), pos.column() + index - token_offset);
@@ -72,7 +75,10 @@ pub fn check_logical_lines(
}
}
}
if line.bracket || line.punctuation {
if line
.flags
.contains(TokenFlags::OPERATOR | TokenFlags::PUNCTUATION)
{
for (index, kind) in extraneous_whitespace(&line.text) {
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
let location = Location::new(pos.row(), pos.column() + index - token_offset);
@@ -87,6 +93,34 @@ pub fn check_logical_lines(
}
}
}
if line.flags.contains(TokenFlags::KEYWORD) {
for (index, kind) in whitespace_around_keywords(&line.text) {
let (token_offset, pos) = line.mapping[bisect_left(&mapping_offsets, &index)];
let location = Location::new(pos.row(), pos.column() + index - token_offset);
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location,
end_location: location,
fix: None,
parent: None,
});
}
}
}
if line.flags.contains(TokenFlags::COMMENT) {
for (range, kind) in whitespace_before_comment(&line.tokens, locator) {
if settings.rules.enabled(kind.rule()) {
diagnostics.push(Diagnostic {
kind,
location: range.location,
end_location: range.end_location,
fix: None,
parent: None,
});
}
}
}
for (index, kind) in indentation(
&line,

View File

@@ -2,7 +2,7 @@
use bitflags::bitflags;
use nohash_hasher::{IntMap, IntSet};
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer::{LexResult, Tok};
use crate::registry::LintSource;

View File

@@ -1,7 +1,7 @@
//! Doc line extraction. In this context, a doc line is a line consisting of a
//! standalone comment or a constant string statement.
use rustpython_ast::{Constant, ExprKind, Stmt, StmtKind, Suite};
use rustpython_parser::ast::{Constant, ExprKind, Stmt, StmtKind, Suite};
use rustpython_parser::lexer::{LexResult, Tok};
use crate::ast::visitor;

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Expr, Stmt};
use rustpython_parser::ast::{Expr, Stmt};
#[derive(Debug, Clone)]
pub enum DefinitionKind<'a> {

View File

@@ -1,6 +1,6 @@
//! Extract docstrings from an AST.
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use crate::visibility::{Modifier, VisibleScope};

View File

@@ -1,4 +1,4 @@
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, Hash)]

View File

@@ -1,6 +1,6 @@
use std::path::Path;
use rustpython_ast::Location;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer::LexResult;
use serde::Serialize;
use wasm_bindgen::prelude::*;

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use anyhow::{anyhow, Result};
use colored::Colorize;
use log::error;
use rustpython_parser::error::ParseError;
use rustpython_parser::lexer::LexResult;
@@ -27,8 +28,9 @@ use crate::{directives, fs, rustpython_helpers};
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
/// A [`Result`]-like type that returns both data and an error. Used to return diagnostics even in
/// the face of parse errors, since many diagnostics can be generated without a full AST.
/// A [`Result`]-like type that returns both data and an error. Used to return
/// diagnostics even in the face of parse errors, since many diagnostics can be
/// generated without a full AST.
pub struct LinterResult<T> {
pub data: T,
pub error: Option<ParseError>,
@@ -246,17 +248,12 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
// Log any parse errors.
if let Some(err) = error {
#[allow(clippy::print_stderr)]
{
eprintln!(
"{}{} {}{}{} {err:?}",
"error".red().bold(),
":".bold(),
"Failed to parse ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);
}
error!(
"{}{}{} {err:?}",
"Failed to parse ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);
}
// Add any missing `# noqa` pragmas.
@@ -270,7 +267,8 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
)
}
/// Generate a [`Message`] for each [`Diagnostic`] triggered by the given source code.
/// Generate a [`Message`] for each [`Diagnostic`] triggered by the given source
/// code.
pub fn lint_only(
contents: &str,
path: &Path,

View File

@@ -1,19 +1,18 @@
use anyhow::Result;
use colored::Colorize;
use fern;
use log::Level;
#[macro_export]
macro_rules! warn_user_once {
($($arg:tt)*) => {
use colored::Colorize;
use log::warn;
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !WARNED.swap(true, std::sync::atomic::Ordering::SeqCst) {
let message = format!("{}", format_args!($($arg)*));
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
message.bold(),
);
warn!("{}", message.bold());
}
};
}
@@ -22,13 +21,10 @@ macro_rules! warn_user_once {
macro_rules! warn_user {
($($arg:tt)*) => {
use colored::Colorize;
use log::warn;
let message = format!("{}", format_args!($($arg)*));
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
message.bold(),
);
warn!("{}", message.bold());
};
}
@@ -50,7 +46,8 @@ macro_rules! notify_user {
pub enum LogLevel {
/// No output ([`log::LevelFilter::Off`]).
Silent,
/// Only show lint violations, with no decorative output ([`log::LevelFilter::Off`]).
/// Only show lint violations, with no decorative output
/// ([`log::LevelFilter::Off`]).
Quiet,
/// All user-facing output ([`log::LevelFilter::Info`]).
#[default]
@@ -73,14 +70,32 @@ impl LogLevel {
pub fn set_up_logging(level: &LogLevel) -> Result<()> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
));
.format(|out, message, record| match record.level() {
Level::Error => {
out.finish(format_args!(
"{}{} {}",
"error".red().bold(),
":".bold(),
message
));
}
Level::Warn => {
out.finish(format_args!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
message
));
}
Level::Info | Level::Debug | Level::Trace => {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
));
}
})
.level(level.level_filter())
.level_for("globset", log::LevelFilter::Warn)

View File

@@ -119,7 +119,8 @@ pub fn detect_package_roots<'a>(
mod tests {
use std::path::PathBuf;
use crate::{packaging::detect_package_root, test::test_resource_path};
use crate::packaging::detect_package_root;
use crate::test::test_resource_path;
#[test]
fn package_detection() {

View File

@@ -41,6 +41,22 @@ ruff_macros::define_rule_mapping!(
E223 => rules::pycodestyle::rules::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
E224 => rules::pycodestyle::rules::TabAfterOperator,
#[cfg(feature = "logical_lines")]
E261 => rules::pycodestyle::rules::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
E262 => rules::pycodestyle::rules::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
E265 => rules::pycodestyle::rules::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
E266 => rules::pycodestyle::rules::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
E271 => rules::pycodestyle::rules::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
E272 => rules::pycodestyle::rules::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
E273 => rules::pycodestyle::rules::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
E274 => rules::pycodestyle::rules::TabBeforeKeyword,
E401 => rules::pycodestyle::rules::MultipleImportsOnOneLine,
E402 => rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
E501 => rules::pycodestyle::rules::LineTooLong,
@@ -107,6 +123,7 @@ ruff_macros::define_rule_mapping!(
// pylint
PLE0604 => rules::pylint::rules::InvalidAllObject,
PLE0605 => rules::pylint::rules::InvalidAllFormat,
PLE2502 => rules::pylint::rules::BidirectionalUnicode,
PLE1310 => rules::pylint::rules::BadStrStripCall,
PLC0414 => rules::pylint::rules::UselessImportAlias,
PLC3002 => rules::pylint::rules::UnnecessaryDirectLambdaCall,
@@ -145,7 +162,7 @@ ruff_macros::define_rule_mapping!(
B014 => rules::flake8_bugbear::rules::DuplicateHandlerException,
B015 => rules::flake8_bugbear::rules::UselessComparison,
B016 => rules::flake8_bugbear::rules::CannotRaiseLiteral,
B017 => rules::flake8_bugbear::rules::NoAssertRaisesException,
B017 => rules::flake8_bugbear::rules::AssertRaisesException,
B018 => rules::flake8_bugbear::rules::UselessExpression,
B019 => rules::flake8_bugbear::rules::CachedInstanceMethod,
B020 => rules::flake8_bugbear::rules::LoopVariableOverridesIterator,
@@ -387,7 +404,7 @@ ruff_macros::define_rule_mapping!(
ARG004 => rules::flake8_unused_arguments::rules::UnusedStaticMethodArgument,
ARG005 => rules::flake8_unused_arguments::rules::UnusedLambdaArgument,
// flake8-import-conventions
ICN001 => rules::flake8_import_conventions::rules::ImportAliasIsNotConventional,
ICN001 => rules::flake8_import_conventions::rules::UnconventionalImportAlias,
// flake8-datetimez
DTZ001 => rules::flake8_datetimez::rules::CallDatetimeWithoutTzinfo,
DTZ002 => rules::flake8_datetimez::rules::CallDatetimeToday,
@@ -453,6 +470,7 @@ ruff_macros::define_rule_mapping!(
PIE800 => rules::flake8_pie::rules::NoUnnecessarySpread,
PIE804 => rules::flake8_pie::rules::NoUnnecessaryDictKwargs,
PIE807 => rules::flake8_pie::rules::PreferListBuiltin,
PIE810 => rules::flake8_pie::rules::SingleStartsEndsWith,
// flake8-commas
COM812 => rules::flake8_commas::rules::TrailingCommaMissing,
COM818 => rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
@@ -747,13 +765,21 @@ impl Rule {
#[cfg(feature = "logical_lines")]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
| Rule::MultipleLeadingHashesForBlockComment
| Rule::MultipleSpacesAfterKeyword
| Rule::MultipleSpacesAfterOperator
| Rule::MultipleSpacesBeforeKeyword
| Rule::MultipleSpacesBeforeOperator
| Rule::NoIndentedBlock
| Rule::NoIndentedBlockComment
| Rule::NoSpaceAfterBlockComment
| Rule::NoSpaceAfterInlineComment
| Rule::OverIndented
| Rule::TabAfterKeyword
| Rule::TabAfterOperator
| Rule::TabBeforeKeyword
| Rule::TabBeforeOperator
| Rule::TooFewSpacesBeforeInlineComment
| Rule::UnexpectedIndentation
| Rule::UnexpectedIndentationComment
| Rule::WhitespaceAfterOpenBracket

View File

@@ -587,15 +587,16 @@ mod tests {
&Relativity::Parent,
&NoOpProcessor,
)?);
// src/app.py should not be excluded even if it lives in a hierarchy that should be
// excluded by virtue of the pyproject.toml having `resources/*` in it.
// src/app.py should not be excluded even if it lives in a hierarchy that should
// be excluded by virtue of the pyproject.toml having `resources/*` in
// it.
assert!(!is_file_excluded(
&package_root.join("src/app.py"),
&resolver,
&ppd,
));
// However, resources/ignored.py should be ignored, since that `resources` is beneath
// the package root.
// However, resources/ignored.py should be ignored, since that `resources` is
// beneath the package root.
assert!(is_file_excluded(
&package_root.join("resources/ignored.py"),
&resolver,

View File

@@ -1,9 +1,8 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::Location;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::Location;
use super::detection::comment_contains_code;
use crate::ast::types::Range;
use crate::define_violation;
use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule};
use crate::settings::{flags, Settings};
@@ -11,6 +10,17 @@ use crate::source_code::Locator;
use crate::violation::AlwaysAutofixableViolation;
define_violation!(
/// ### What it does
/// Checks for commented-out Python code.
///
/// ### Why is this bad?
/// Commented-out code is dead code, and is often included inadvertently.
/// It should be removed.
///
/// ### Example
/// ```python
/// # print('foo')
/// ```
pub struct CommentedOutCode;
);
impl AlwaysAutofixableViolation for CommentedOutCode {

View File

@@ -1,14 +1,12 @@
use num_bigint::BigInt;
use rustpython_ast::{Cmpop, Constant, Expr, ExprKind, Located};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Located};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::{Diagnostic, Rule};
use crate::violation::Violation;
use crate::define_violation;
use ruff_macros::derive_message_formats;
define_violation!(
pub struct SysVersionSlice3Referenced;
);

View File

@@ -1,5 +1,5 @@
use anyhow::{bail, Result};
use rustpython_ast::Stmt;
use rustpython_parser::ast::Stmt;
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Arguments, Expr, Stmt, StmtKind};
use rustpython_parser::ast::{Arguments, Expr, Stmt, StmtKind};
use crate::ast::cast;
use crate::checkers::ast::Checker;

View File

@@ -39,16 +39,42 @@ mod tests {
Ok(())
}
#[test]
fn ignore_fully_untyped() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_annotations/ignore_fully_untyped.py"),
&Settings {
flake8_annotations: super::settings::Settings {
ignore_fully_untyped: true,
..Default::default()
},
..Settings::for_rules(vec![
Rule::MissingTypeFunctionArgument,
Rule::MissingTypeArgs,
Rule::MissingTypeKwargs,
Rule::MissingTypeSelf,
Rule::MissingTypeCls,
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
Rule::MissingReturnTypeClassMethod,
Rule::DynamicallyTypedExpression,
])
},
)?;
assert_yaml_snapshot!(diagnostics);
Ok(())
}
#[test]
fn suppress_dummy_args() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_annotations/suppress_dummy_args.py"),
&Settings {
flake8_annotations: super::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: true,
suppress_none_returning: false,
allow_star_arg_any: false,
..Default::default()
},
..Settings::for_rules(vec![
Rule::MissingTypeFunctionArgument,
@@ -70,9 +96,7 @@ mod tests {
&Settings {
flake8_annotations: super::settings::Settings {
mypy_init_return: true,
suppress_dummy_args: false,
suppress_none_returning: false,
allow_star_arg_any: false,
..Default::default()
},
..Settings::for_rules(vec![
Rule::MissingReturnTypePublicFunction,
@@ -83,7 +107,7 @@ mod tests {
])
},
)?;
assert_yaml_snapshot!(diagnostics);
insta::assert_yaml_snapshot!(diagnostics);
Ok(())
}
@@ -93,17 +117,21 @@ mod tests {
Path::new("flake8_annotations/suppress_none_returning.py"),
&Settings {
flake8_annotations: super::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: false,
suppress_none_returning: true,
allow_star_arg_any: false,
..Default::default()
},
..Settings::for_rules(vec![
Rule::MissingTypeFunctionArgument,
Rule::MissingTypeArgs,
Rule::MissingTypeKwargs,
Rule::MissingTypeSelf,
Rule::MissingTypeCls,
Rule::MissingReturnTypePublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
Rule::MissingReturnTypeClassMethod,
Rule::DynamicallyTypedExpression,
])
},
)?;
@@ -117,10 +145,8 @@ mod tests {
Path::new("flake8_annotations/allow_star_arg_any.py"),
&Settings {
flake8_annotations: super::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: false,
suppress_none_returning: false,
allow_star_arg_any: true,
..Default::default()
},
..Settings::for_rules(vec![Rule::DynamicallyTypedExpression])
},

View File

@@ -1,23 +1,20 @@
use log::error;
use rustpython_ast::{Constant, Expr, ExprKind, Stmt};
use ruff_macros::derive_message_formats;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt};
use super::fixes;
use super::helpers::match_function_def;
use crate::ast::helpers::ReturnStatementVisitor;
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::ast::{cast, helpers};
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::registry::{Diagnostic, Rule};
use crate::violation::{AlwaysAutofixableViolation, Violation};
use crate::visibility;
use crate::visibility::Visibility;
use super::fixes;
use super::helpers::match_function_def;
define_violation!(
pub struct MissingTypeFunctionArgument {
pub name: String,
@@ -185,12 +182,16 @@ fn is_none_returning(body: &[Stmt]) -> bool {
}
/// ANN401
fn check_dynamically_typed<F>(checker: &mut Checker, annotation: &Expr, func: F)
where
fn check_dynamically_typed<F>(
checker: &Checker,
annotation: &Expr,
func: F,
diagnostics: &mut Vec<Diagnostic>,
) where
F: FnOnce() -> String,
{
if checker.match_typing_expr(annotation, "Any") {
checker.diagnostics.push(Diagnostic::new(
diagnostics.push(Diagnostic::new(
DynamicallyTypedExpression { name: func() },
Range::from_located(annotation),
));
@@ -198,273 +199,301 @@ where
}
/// Generate flake8-annotation checks for a given `Definition`.
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
pub fn definition(
checker: &Checker,
definition: &Definition,
visibility: &Visibility,
) -> Vec<Diagnostic> {
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
// We could adhere more closely to `flake8-annotations` by defining public
// vs. secret vs. protected.
match &definition.kind {
DefinitionKind::Module => {}
DefinitionKind::Package => {}
DefinitionKind::Class(_) => {}
DefinitionKind::NestedClass(_) => {}
DefinitionKind::Function(stmt)
| DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) => {
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
let (name, args, returns, body) = match_function_def(stmt);
let mut has_any_typed_arg = false;
if let DefinitionKind::Function(stmt)
| DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = &definition.kind
{
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
let (name, args, returns, body) = match_function_def(stmt);
// Keep track of whether we've seen any typed arguments or return values.
let mut has_any_typed_arg = false; // Any argument has been typed?
let mut has_typed_return = false; // Return value has been typed?
let mut has_typed_self_or_cls = false; // Has a typed `self` or `cls` argument?
// ANN001, ANN401
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(
is_method
&& !visibility::is_staticmethod(checker, cast::decorator_list(stmt)),
),
)
{
// ANN401 for dynamically typed arguments
if let Some(annotation) = &arg.node.annotation {
has_any_typed_arg = true;
if checker
.settings
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
check_dynamically_typed(checker, annotation, || arg.node.arg.to_string());
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker
.settings
.rules
.enabled(&Rule::MissingTypeFunctionArgument)
{
checker.diagnostics.push(Diagnostic::new(
MissingTypeFunctionArgument {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
}
}
// Temporary storage for diagnostics; we emit them at the end
// unless configured to suppress ANN* for declarations that are fully untyped.
let mut diagnostics = Vec::new();
// ANN002, ANN401
if let Some(arg) = &args.vararg {
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker
.settings
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("*{name}"));
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.rules.enabled(&Rule::MissingTypeArgs) {
checker.diagnostics.push(Diagnostic::new(
MissingTypeArgs {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
}
}
// ANN003, ANN401
if let Some(arg) = &args.kwarg {
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker
.settings
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("**{name}"));
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) {
checker.diagnostics.push(Diagnostic::new(
MissingTypeKwargs {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
}
}
// ANN101, ANN102
if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.rules.enabled(&Rule::MissingTypeCls) {
checker.diagnostics.push(Diagnostic::new(
MissingTypeCls {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
} else {
if checker.settings.rules.enabled(&Rule::MissingTypeSelf) {
checker.diagnostics.push(Diagnostic::new(
MissingTypeSelf {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
}
}
}
// ANN201, ANN202, ANN401
if let Some(expr) = &returns {
// ANN001, ANN401
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(
is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)),
),
)
{
// ANN401 for dynamically typed arguments
if let Some(annotation) = &arg.node.annotation {
has_any_typed_arg = true;
if checker
.settings
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
check_dynamically_typed(checker, expr, || name.to_string());
check_dynamically_typed(
checker,
annotation,
|| arg.node.arg.to_string(),
&mut diagnostics,
);
}
} else {
// Allow omission of return annotation if the function only returns `None`
// (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(body)
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
return;
if checker
.settings
.rules
.enabled(&Rule::MissingTypeFunctionArgument)
{
diagnostics.push(Diagnostic::new(
MissingTypeFunctionArgument {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
}
}
if is_method && visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
// ANN002, ANN401
if let Some(arg) = &args.vararg {
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeClassMethod)
.enabled(&Rule::DynamicallyTypedExpression)
{
checker.diagnostics.push(Diagnostic::new(
MissingReturnTypeClassMethod {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
let name = &arg.node.arg;
check_dynamically_typed(
checker,
expr,
|| format!("*{name}"),
&mut diagnostics,
);
}
} else if is_method
&& visibility::is_staticmethod(checker, cast::decorator_list(stmt))
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeStaticMethod)
{
checker.diagnostics.push(Diagnostic::new(
MissingReturnTypeStaticMethod {
name: name.to_string(),
if checker.settings.rules.enabled(&Rule::MissingTypeArgs) {
diagnostics.push(Diagnostic::new(
MissingTypeArgs {
name: arg.node.arg.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
Range::from_located(arg),
));
}
} else if is_method && visibility::is_init(cast::name(stmt)) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
}
}
}
// ANN003, ANN401
if let Some(arg) = &args.kwarg {
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeSpecialMethod)
.enabled(&Rule::DynamicallyTypedExpression)
{
if !(checker.settings.flake8_annotations.mypy_init_return
&& has_any_typed_arg)
{
let mut diagnostic = Diagnostic::new(
MissingReturnTypeSpecialMethod {
name: name.to_string(),
let name = &arg.node.arg;
check_dynamically_typed(
checker,
expr,
|| format!("**{name}"),
&mut diagnostics,
);
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) {
diagnostics.push(Diagnostic::new(
MissingTypeKwargs {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
}
}
// ANN101, ANN102
if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.rules.enabled(&Rule::MissingTypeCls) {
diagnostics.push(Diagnostic::new(
MissingTypeCls {
name: arg.node.arg.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
);
if checker.patch(diagnostic.kind.rule()) {
match fixes::add_return_none_annotation(checker.locator, stmt) {
Ok(fix) => {
diagnostic.amend(fix);
}
Err(e) => error!("Failed to generate fix: {e}"),
}
}
checker.diagnostics.push(diagnostic);
Range::from_located(arg),
));
}
} else {
if checker.settings.rules.enabled(&Rule::MissingTypeSelf) {
diagnostics.push(Diagnostic::new(
MissingTypeSelf {
name: arg.node.arg.to_string(),
},
Range::from_located(arg),
));
}
}
} else if is_method && visibility::is_magic(cast::name(stmt)) {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeSpecialMethod)
} else {
has_typed_self_or_cls = true;
}
}
}
// ANN201, ANN202, ANN401
if let Some(expr) = &returns {
has_typed_return = true;
if checker
.settings
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
}
} else if !(
// Allow omission of return annotation if the function only returns `None`
// (explicitly or implicitly).
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
) {
if is_method && visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeClassMethod)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypeClassMethod {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
} else if is_method && visibility::is_staticmethod(checker, cast::decorator_list(stmt))
{
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeStaticMethod)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypeStaticMethod {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
} else if is_method && visibility::is_init(cast::name(stmt)) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeSpecialMethod)
{
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg)
{
checker.diagnostics.push(Diagnostic::new(
let mut diagnostic = Diagnostic::new(
MissingReturnTypeSpecialMethod {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
} else {
match visibility {
Visibility::Public => {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypePublicFunction)
{
checker.diagnostics.push(Diagnostic::new(
MissingReturnTypePublicFunction {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
);
if checker.patch(diagnostic.kind.rule()) {
match fixes::add_return_none_annotation(checker.locator, stmt) {
Ok(fix) => {
diagnostic.amend(fix);
}
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Visibility::Private => {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypePrivateFunction)
{
checker.diagnostics.push(Diagnostic::new(
MissingReturnTypePrivateFunction {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
diagnostics.push(diagnostic);
}
}
} else if is_method && visibility::is_magic(cast::name(stmt)) {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeSpecialMethod)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypeSpecialMethod {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
} else {
match visibility {
Visibility::Public => {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypePublicFunction)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypePublicFunction {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
}
Visibility::Private => {
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypePrivateFunction)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypePrivateFunction {
name: name.to_string(),
},
helpers::identifier_range(stmt, checker.locator),
));
}
}
}
}
}
// If settings say so, don't report any of the
// diagnostics gathered here if there were no type annotations at all.
if checker.settings.flake8_annotations.ignore_fully_untyped
&& !(has_any_typed_arg || has_typed_self_or_cls || has_typed_return)
{
vec![]
} else {
diagnostics
}
} else {
vec![]
}
}

View File

@@ -49,6 +49,15 @@ pub struct Options {
/// Whether to suppress `ANN401` for dynamically typed `*args` and
/// `**kwargs` arguments.
pub allow_star_arg_any: Option<bool>,
#[option(
default = "false",
value_type = "bool",
example = "ignore-fully-untyped = true"
)]
/// Whether to suppress `ANN*` rules for any declaration
/// that hasn't been typed at all.
/// This makes it easier to gradually add types to a codebase.
pub ignore_fully_untyped: Option<bool>,
}
#[derive(Debug, Default, Hash)]
@@ -58,6 +67,7 @@ pub struct Settings {
pub suppress_dummy_args: bool,
pub suppress_none_returning: bool,
pub allow_star_arg_any: bool,
pub ignore_fully_untyped: bool,
}
impl From<Options> for Settings {
@@ -67,6 +77,7 @@ impl From<Options> for Settings {
suppress_dummy_args: options.suppress_dummy_args.unwrap_or(false),
suppress_none_returning: options.suppress_none_returning.unwrap_or(false),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or(false),
ignore_fully_untyped: options.ignore_fully_untyped.unwrap_or(false),
}
}
}
@@ -78,6 +89,7 @@ impl From<Settings> for Options {
suppress_dummy_args: Some(settings.suppress_dummy_args),
suppress_none_returning: Some(settings.suppress_none_returning),
allow_star_arg_any: Some(settings.allow_star_arg_any),
ignore_fully_untyped: Some(settings.ignore_fully_untyped),
}
}
}

View File

@@ -0,0 +1,60 @@
---
source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
MissingReturnTypePublicFunction:
name: error_partially_typed_1
location:
row: 24
column: 4
end_location:
row: 24
column: 27
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument:
name: b
location:
row: 24
column: 36
end_location:
row: 24
column: 37
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument:
name: b
location:
row: 28
column: 36
end_location:
row: 28
column: 37
fix: ~
parent: ~
- kind:
MissingReturnTypePublicFunction:
name: error_partially_typed_3
location:
row: 32
column: 4
end_location:
row: 32
column: 27
fix: ~
parent: ~
- kind:
MissingReturnTypePublicFunction:
name: error_typed_self
location:
row: 43
column: 8
end_location:
row: 43
column: 24
fix: ~
parent: ~

View File

@@ -1,5 +1,5 @@
---
source: src/rules/flake8_annotations/mod.rs
source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
@@ -12,8 +12,7 @@ expression: diagnostics
row: 5
column: 16
fix:
content:
- " -> None"
content: " -> None"
location:
row: 5
column: 22
@@ -31,8 +30,7 @@ expression: diagnostics
row: 11
column: 16
fix:
content:
- " -> None"
content: " -> None"
location:
row: 11
column: 27
@@ -61,8 +59,7 @@ expression: diagnostics
row: 47
column: 16
fix:
content:
- " -> None"
content: " -> None"
location:
row: 47
column: 28

View File

@@ -1,5 +1,5 @@
---
source: src/rules/flake8_annotations/mod.rs
source: crates/ruff/src/rules/flake8_annotations/mod.rs
expression: diagnostics
---
- kind:
@@ -24,4 +24,15 @@ expression: diagnostics
column: 7
fix: ~
parent: ~
- kind:
MissingTypeFunctionArgument:
name: a
location:
row: 59
column: 8
end_location:
row: 59
column: 9
fix: ~
parent: ~

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use rustpython_parser::ast::{Constant, Expr, ExprKind};
const PASSWORD_NAMES: [&str; 7] = [
"password", "pass", "passwd", "pwd", "secret", "token", "secrete",

View File

@@ -1,10 +1,9 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Located, StmtKind};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Located, StmtKind};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct AssertUsed;

View File

@@ -1,15 +1,14 @@
use crate::define_violation;
use crate::violation::Violation;
use num_traits::ToPrimitive;
use once_cell::sync::Lazy;
use ruff_macros::derive_message_formats;
use ruff_macros::{define_violation, derive_message_formats};
use rustc_hash::FxHashMap;
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Operator};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword, Operator};
use crate::ast::helpers::{compose_call_path, SimpleCallArgs};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct BadFilePermissions {

View File

@@ -1,10 +1,9 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct ExecUsed;

View File

@@ -1,10 +1,9 @@
use ruff_macros::{define_violation, derive_message_formats};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use crate::define_violation;
use ruff_macros::derive_message_formats;
define_violation!(
pub struct HardcodedBindAllInterfaces;
);

View File

@@ -1,11 +1,10 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{ArgData, Arguments, Expr, Located};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{ArgData, Arguments, Expr, Located};
use super::super::helpers::{matches_password_name, string_literal};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct HardcodedPasswordDefault {

View File

@@ -1,11 +1,10 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::Keyword;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::Keyword;
use super::super::helpers::{matches_password_name, string_literal};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct HardcodedPasswordFuncArg {

View File

@@ -1,11 +1,10 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Constant, Expr, ExprKind};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind};
use super::super::helpers::{matches_password_name, string_literal};
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct HardcodedPasswordString {

View File

@@ -1,10 +1,9 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::Expr;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::Expr;
use crate::ast::types::Range;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct HardcodedTempFile {

View File

@@ -1,13 +1,12 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Constant, Expr, ExprKind, Keyword};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use super::super::helpers::string_literal;
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct HashlibInsecureHashFunction {

View File

@@ -1,13 +1,11 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind, Keyword};
use rustpython_parser::ast::Constant;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct Jinja2AutoescapeFalse {

View File

@@ -1,11 +1,9 @@
use rustpython_ast::{Expr, Keyword};
use ruff_macros::derive_message_formats;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;

View File

@@ -1,13 +1,11 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind, Keyword};
use rustpython_parser::ast::Constant;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct RequestWithNoCertValidation {

View File

@@ -1,13 +1,11 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind, Keyword};
use rustpython_parser::ast::Constant;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::helpers::{unparse_constant, SimpleCallArgs};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct RequestWithoutTimeout {
@@ -47,7 +45,7 @@ pub fn request_without_timeout(
ExprKind::Constant {
value: value @ Constant::None,
..
} => Some(value.to_string()),
} => Some(unparse_constant(value, checker.stylist)),
_ => None,
} {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -1,14 +1,12 @@
use crate::define_violation;
use crate::violation::Violation;
use num_traits::{One, Zero};
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind, Keyword};
use rustpython_parser::ast::Constant;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct SnmpInsecureVersion;

View File

@@ -1,12 +1,11 @@
use crate::define_violation;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, Keyword};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct SnmpWeakCryptography;

View File

@@ -1,9 +1,8 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, Stmt, StmtKind};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;

View File

@@ -1,11 +1,9 @@
use rustpython_ast::{Expr, ExprKind, Keyword};
use ruff_macros::derive_message_formats;
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, ExprKind, Keyword};
use crate::ast::helpers::SimpleCallArgs;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;

View File

@@ -39,8 +39,9 @@ pub struct Options {
value_type = "bool",
example = "check-typed-exception = true"
)]
/// Whether to disallow `try`-`except`-`pass` (`S110`) for specific exception types. By default,
/// `try`-`except`-`pass` is only disallowed for `Exception` and `BaseException`.
/// Whether to disallow `try`-`except`-`pass` (`S110`) for specific
/// exception types. By default, `try`-`except`-`pass` is only
/// disallowed for `Exception` and `BaseException`.
pub check_typed_exception: Option<bool>,
}

View File

@@ -1,12 +1,12 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, ExprKind, Stmt, StmtKind};
use crate::ast::helpers;
use crate::ast::helpers::{find_keyword, is_const_true};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
define_violation!(
pub struct BlindExcept {

View File

@@ -1,10 +1,8 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::{Arguments, ExprKind};
use rustpython_parser::ast::{Constant, Expr};
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Arguments, Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::{Diagnostic, DiagnosticKind};
use crate::violation::Violation;
@@ -38,7 +36,7 @@ impl Violation for BooleanPositionalValueInFunctionCall {
}
}
const FUNC_NAME_ALLOWLIST: &[&str] = &[
const FUNC_CALL_NAME_ALLOWLIST: &[&str] = &[
"assertEqual",
"assertEquals",
"assertNotEqual",
@@ -54,16 +52,18 @@ const FUNC_NAME_ALLOWLIST: &[&str] = &[
"setdefault",
];
const FUNC_DEF_NAME_ALLOWLIST: &[&str] = &["__setitem__"];
/// Returns `true` if an argument is allowed to use a boolean trap. To return
/// `true`, the function name must be explicitly allowed, and the argument must
/// be either the first or second argument in the call.
fn allow_boolean_trap(func: &Expr) -> bool {
if let ExprKind::Attribute { attr, .. } = &func.node {
return FUNC_NAME_ALLOWLIST.contains(&attr.as_ref());
return FUNC_CALL_NAME_ALLOWLIST.contains(&attr.as_ref());
}
if let ExprKind::Name { id, .. } = &func.node {
return FUNC_NAME_ALLOWLIST.contains(&id.as_ref());
return FUNC_CALL_NAME_ALLOWLIST.contains(&id.as_ref());
}
false
@@ -87,7 +87,10 @@ fn add_if_boolean(checker: &mut Checker, arg: &Expr, kind: DiagnosticKind) {
}
}
pub fn check_positional_boolean_in_def(checker: &mut Checker, arguments: &Arguments) {
pub fn check_positional_boolean_in_def(checker: &mut Checker, name: &str, arguments: &Arguments) {
if FUNC_DEF_NAME_ALLOWLIST.contains(&name) {
return;
}
for arg in arguments.posonlyargs.iter().chain(arguments.args.iter()) {
if arg.node.annotation.is_none() {
continue;
@@ -117,8 +120,12 @@ pub fn check_positional_boolean_in_def(checker: &mut Checker, arguments: &Argume
pub fn check_boolean_default_value_in_function_definition(
checker: &mut Checker,
name: &str,
arguments: &Arguments,
) {
if FUNC_DEF_NAME_ALLOWLIST.contains(&name) {
return;
}
for arg in &arguments.defaults {
add_if_boolean(checker, arg, BooleanDefaultValueInFunctionDefinition.into());
}
@@ -129,10 +136,10 @@ pub fn check_boolean_positional_value_in_function_call(
args: &[Expr],
func: &Expr,
) {
if allow_boolean_trap(func) {
return;
}
for arg in args {
if allow_boolean_trap(func) {
continue;
}
add_if_boolean(checker, arg, BooleanPositionalValueInFunctionCall.into());
}
}

View File

@@ -29,7 +29,7 @@ mod tests {
#[test_case(Rule::DuplicateHandlerException, Path::new("B014.py"); "B014")]
#[test_case(Rule::UselessComparison, Path::new("B015.py"); "B015")]
#[test_case(Rule::CannotRaiseLiteral, Path::new("B016.py"); "B016")]
#[test_case(Rule::NoAssertRaisesException, Path::new("B017.py"); "B017")]
#[test_case(Rule::AssertRaisesException, Path::new("B017.py"); "B017")]
#[test_case(Rule::UselessExpression, Path::new("B018.py"); "B018")]
#[test_case(Rule::CachedInstanceMethod, Path::new("B019.py"); "B019")]
#[test_case(Rule::LoopVariableOverridesIterator, Path::new("B020.py"); "B020")]

View File

@@ -1,11 +1,11 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::{Diagnostic, Rule};
use crate::violation::Violation;
use crate::visibility::{is_abstract, is_overload};
use ruff_macros::derive_message_formats;
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
define_violation!(
pub struct AbstractBaseClassWithoutAbstractMethod {

View File

@@ -1,12 +1,12 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
use crate::ast::helpers::unparse_stmt;
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::fix::Fix;
use crate::registry::Diagnostic;
use crate::violation::AlwaysAutofixableViolation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
define_violation!(
pub struct DoNotAssertFalse;

View File

@@ -1,24 +1,34 @@
//! Checks for `self.assertRaises(Exception)`.
//!
//! ## Why is this bad?
//!
//! `assertRaises(Exception)` should be considered evil. It can lead to your
//! test passing even if the code being tested is never executed due to a
//! typo. Either assert for a more specific exception (builtin or
//! custom), use `assertRaisesRegex`, or use the context manager form of
//! `assertRaises`.
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{ExprKind, Stmt, Withitem};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{ExprKind, Stmt, Withitem};
define_violation!(
pub struct NoAssertRaisesException;
/// ### What it does
/// Checks for `self.assertRaises(Exception)`.
///
/// ### Why is this bad?
/// `assertRaises(Exception)` can lead to your test passing even if the
/// code being tested is never executed due to a typo.
///
/// Either assert for a more specific exception (builtin or custom), use
/// `assertRaisesRegex` or the context manager form of `assertRaises`.
///
/// ### Example
/// ```python
/// self.assertRaises(Exception, foo)
/// ```
///
/// Use instead:
/// ```python
/// self.assertRaises(SomeSpecificException, foo)
/// ```
pub struct AssertRaisesException;
);
impl Violation for NoAssertRaisesException {
impl Violation for AssertRaisesException {
#[derive_message_formats]
fn message(&self) -> String {
format!("`assertRaises(Exception)` should be considered evil")
@@ -51,7 +61,7 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
}
checker.diagnostics.push(Diagnostic::new(
NoAssertRaisesException,
AssertRaisesException,
Range::from_located(stmt),
));
}

View File

@@ -1,10 +1,10 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind};
define_violation!(
pub struct AssignmentToOsEnviron;

View File

@@ -1,10 +1,10 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, ExprKind};
use crate::ast::types::{Range, ScopeKind};
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind};
define_violation!(
pub struct CachedInstanceMethod;

View File

@@ -1,10 +1,10 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, ExprKind};
define_violation!(
pub struct CannotRaiseLiteral;

View File

@@ -1,15 +1,17 @@
use itertools::Itertools;
use ruff_macros::{define_violation, derive_message_formats};
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_parser::ast::{
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location,
};
use crate::ast::helpers;
use crate::ast::helpers::unparse_expr;
use crate::ast::types::{CallPath, Range};
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule};
use crate::violation::{AlwaysAutofixableViolation, Violation};
use itertools::Itertools;
use ruff_macros::derive_message_formats;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location};
define_violation!(
pub struct DuplicateTryBlockException {

View File

@@ -1,10 +1,10 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{ExprKind, Stmt, StmtKind};
use crate::ast::helpers;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{ExprKind, Stmt, StmtKind};
define_violation!(
pub struct FStringDocstring;

View File

@@ -1,14 +1,14 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustpython_parser::ast::{Arguments, Constant, Expr, ExprKind};
use super::mutable_argument_default::is_mutable_func;
use crate::ast::helpers::{compose_call_path, to_call_path};
use crate::ast::types::{CallPath, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::{Diagnostic, DiagnosticKind};
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustpython_ast::{Arguments, Constant, Expr, ExprKind};
define_violation!(
pub struct FunctionCallArgumentDefault {
@@ -37,8 +37,8 @@ const IMMUTABLE_FUNCS: &[&[&str]] = &[
&["re", "compile"],
];
fn is_immutable_func(checker: &Checker, expr: &Expr, extend_immutable_calls: &[CallPath]) -> bool {
checker.resolve_call_path(expr).map_or(false, |call_path| {
fn is_immutable_func(checker: &Checker, func: &Expr, extend_immutable_calls: &[CallPath]) -> bool {
checker.resolve_call_path(func).map_or(false, |call_path| {
IMMUTABLE_FUNCS
.iter()
.any(|target| call_path.as_slice() == *target)
@@ -67,7 +67,7 @@ where
{
self.diagnostics.push((
FunctionCallArgumentDefault {
name: compose_call_path(expr),
name: compose_call_path(func),
}
.into(),
Range::from_located(expr),

View File

@@ -1,14 +1,14 @@
use ruff_macros::{define_violation, derive_message_formats};
use rustc_hash::FxHashSet;
use rustpython_parser::ast::{Comprehension, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::helpers::collect_arg_names;
use crate::ast::types::{Node, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use rustc_hash::FxHashSet;
use rustpython_ast::{Comprehension, Expr, ExprContext, ExprKind, Stmt, StmtKind};
define_violation!(
pub struct FunctionUsesLoopVariable {

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