Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21a5912b9 | ||
|
|
48a5cd1dd9 | ||
|
|
63f3d5e610 | ||
|
|
7dab4807d0 | ||
|
|
83f6e52c92 | ||
|
|
5ce7ce5bc3 | ||
|
|
749d197119 | ||
|
|
46c184600f | ||
|
|
e2051ef72f | ||
|
|
1abaece9ed | ||
|
|
8b35b052b8 | ||
|
|
5a34504149 | ||
|
|
0e53ddc2b3 | ||
|
|
1f07ad6e61 | ||
|
|
1666e8ba1e | ||
|
|
c399b3e6c1 | ||
|
|
9089ef74bc | ||
|
|
28c9263722 | ||
|
|
fc4c927788 | ||
|
|
26f39cac2f | ||
|
|
02897a141b | ||
|
|
fc465cc2af | ||
|
|
ca8a122889 | ||
|
|
6769a5bce7 | ||
|
|
fda93c6245 | ||
|
|
099d5414f2 | ||
|
|
9ddd5e4cfe | ||
|
|
b8835c2e35 | ||
|
|
1d4422f004 | ||
|
|
2dccb7611a | ||
|
|
f8ac6d7bf0 | ||
|
|
0123425be1 | ||
|
|
c53f91d943 | ||
|
|
4a12ebb9b1 | ||
|
|
0e4d5eeea7 | ||
|
|
bbe44360e8 | ||
|
|
37e80d98ab | ||
|
|
306393063d | ||
|
|
f5a3c90288 | ||
|
|
8289ede00f | ||
|
|
77e65c9ff5 | ||
|
|
d827a9156e | ||
|
|
418808895e | ||
|
|
ac4e212ed2 | ||
|
|
551b810aeb | ||
|
|
1b61d4e18b | ||
|
|
752c0150e1 | ||
|
|
81651a8479 | ||
|
|
86d0749ed7 | ||
|
|
19fc410683 | ||
|
|
5a70a573cd | ||
|
|
74731a3456 | ||
|
|
863e39fe5f | ||
|
|
d0f9ee33ec | ||
|
|
1cf3d880a7 | ||
|
|
97dcb738fa | ||
|
|
ffb4e89a98 | ||
|
|
43b7ee215c | ||
|
|
77099dcd4d | ||
|
|
70ff65154d | ||
|
|
7db6a2d6d4 | ||
|
|
42924c0d9a | ||
|
|
31d00936ee | ||
|
|
c3c5d9a852 | ||
|
|
7e5c19385c | ||
|
|
5b54325c81 | ||
|
|
e6538a7969 |
14
.github/workflows/ci.yaml
vendored
14
.github/workflows/ci.yaml
vendored
@@ -40,9 +40,20 @@ jobs:
|
||||
run: rustup component add rustfmt
|
||||
- run: cargo fmt --all --check
|
||||
|
||||
cargo_clippy:
|
||||
cargo-clippy:
|
||||
name: "cargo clippy"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Rust toolchain"
|
||||
run: |
|
||||
rustup component add clippy
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
cargo-clippy-wasm:
|
||||
name: "cargo clippy (wasm)"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -50,7 +61,6 @@ jobs:
|
||||
rustup component add clippy
|
||||
rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
- run: cargo clippy -p ruff --target wasm32-unknown-unknown --all-features -- -D warnings
|
||||
|
||||
cargo-test:
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/setup-python@v4
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
pip install "mkdocs~=1.4.2" "mkdocs-material~=9.0.6"
|
||||
pip install -r docs/requirements.txt
|
||||
- name: "Copy README File"
|
||||
run: |
|
||||
python scripts/transform_readme.py --target mkdocs
|
||||
|
||||
5
.github/workflows/ruff.yaml
vendored
5
.github/workflows/ruff.yaml
vendored
@@ -2,8 +2,9 @@ name: "[ruff] Release"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- '**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,6 +3,8 @@
|
||||
crates/ruff/resources/test/cpython
|
||||
docs/*
|
||||
!docs/rules
|
||||
!docs/assets
|
||||
!docs/requirements.txt
|
||||
mkdocs.yml
|
||||
.overrides
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.246
|
||||
|
||||
### `multiple-statements-on-one-line-def` (`E704`) was removed ([#2773](https://github.com/charliermarsh/ruff/pull/2773))
|
||||
|
||||
This rule was introduced in v0.0.245. However, it turns out that pycodestyle and Flake8 ignore this
|
||||
rule by default, as it is not part of PEP 8. As such, we've removed it from Ruff.
|
||||
|
||||
## 0.0.245
|
||||
|
||||
### Ruff's public `check` method was removed ([#2709](https://github.com/charliermarsh/ruff/pull/2709))
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
- [Our Pledge](#our-pledge)
|
||||
- [Our Standards](#our-standards)
|
||||
- [Enforcement Responsibilities](#enforcement-responsibilities)
|
||||
- [Scope](#scope)
|
||||
- [Enforcement](#enforcement)
|
||||
- [Enforcement Guidelines](#enforcement-guidelines)
|
||||
- [1. Correction](#1-correction)
|
||||
- [2. Warning](#2-warning)
|
||||
- [3. Temporary Ban](#3-temporary-ban)
|
||||
- [4. Permanent Ban](#4-permanent-ban)
|
||||
- [Attribution](#attribution)
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
|
||||
@@ -2,7 +2,18 @@
|
||||
|
||||
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
|
||||
|
||||
## The basics
|
||||
- [The Basics](#the-basics)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Development](#development)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
|
||||
- [Rule naming convention](#rule-naming-convention)
|
||||
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
|
||||
- [MkDocs](#mkdocs)
|
||||
- [Release Process](#release-process)
|
||||
- [Benchmarks](#benchmarks)
|
||||
|
||||
## The Basics
|
||||
|
||||
Ruff welcomes contributions in the form of Pull Requests.
|
||||
|
||||
@@ -73,7 +84,7 @@ pre-commit run --all-files
|
||||
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
|
||||
prior to merging.
|
||||
|
||||
### Project structure
|
||||
### Project Structure
|
||||
|
||||
Ruff is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html),
|
||||
such that all crates are contained in a flat `crates` directory.
|
||||
@@ -170,7 +181,27 @@ lives in `crates/ruff/src/flake8_to_ruff/converter.rs`.
|
||||
|
||||
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
|
||||
|
||||
## Release process
|
||||
## MkDocs
|
||||
|
||||
To preview any changes to the documentation locally:
|
||||
|
||||
1. Install MkDocs and Material for MkDocs with:
|
||||
```shell
|
||||
pip install -r docs/requirements.txt
|
||||
```
|
||||
2. Generate the MkDocs site with:
|
||||
```shell
|
||||
python scripts/generate_mkdocs.py
|
||||
```
|
||||
3. Run the development server with:
|
||||
```shell
|
||||
mkdocs serve
|
||||
```
|
||||
|
||||
The documentation should then be available locally at
|
||||
[http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
|
||||
|
||||
## Release Process
|
||||
|
||||
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
Actions, which automatically generates the appropriate wheels across architectures and publishes
|
||||
|
||||
899
Cargo.lock
generated
899
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,8 @@ default-members = ["crates/ruff", "crates/ruff_cli"]
|
||||
|
||||
[workspace.dependencies]
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "d94d0ac72072eb60bd9363e69b96ff1d5eb401b3" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "d94d0ac72072eb60bd9363e69b96ff1d5eb401b3" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "61b48f108982d865524f86624a9d5bc2ae3bccef" }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1060,3 +1060,5 @@ are:
|
||||
"""
|
||||
Freely Distributable
|
||||
"""
|
||||
|
||||
- flake8-django, licensed under the GPL license.
|
||||
|
||||
147
README.md
147
README.md
@@ -137,6 +137,7 @@ This README is also available as [documentation](https://beta.ruff.rs/docs/).
|
||||
1. [flake8-comprehensions (C4)](#flake8-comprehensions-c4)
|
||||
1. [flake8-datetimez (DTZ)](#flake8-datetimez-dtz)
|
||||
1. [flake8-debugger (T10)](#flake8-debugger-t10)
|
||||
1. [flake8-django (DJ)](#flake8-django-dj)
|
||||
1. [flake8-errmsg (EM)](#flake8-errmsg-em)
|
||||
1. [flake8-executable (EXE)](#flake8-executable-exe)
|
||||
1. [flake8-implicit-str-concat (ISC)](#flake8-implicit-str-concat-isc)
|
||||
@@ -231,11 +232,22 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.245'
|
||||
rev: 'v0.0.246'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
Or, to enable autofix:
|
||||
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.246'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
```
|
||||
|
||||
<!-- End section: Installation and Usage -->
|
||||
|
||||
## Configuration
|
||||
@@ -393,6 +405,7 @@ Usage: ruff [OPTIONS] <COMMAND>
|
||||
Commands:
|
||||
check Run Ruff on the given files or directories (default)
|
||||
rule Explain a rule
|
||||
config List or describe the available configuration options
|
||||
linter List all supported upstream linters
|
||||
clean Clear any caches in the current directory and any subdirectories
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
@@ -426,6 +439,7 @@ Arguments:
|
||||
Options:
|
||||
--fix Attempt to automatically fix lint violations
|
||||
--show-source Show violations with source code
|
||||
--show-fixes Show an enumeration of all autofixed lint violations
|
||||
--diff Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
|
||||
-w, --watch Run in watch mode by re-running whenever files change
|
||||
--fix-only Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
|
||||
@@ -620,7 +634,7 @@ configuration.
|
||||
See the [`isort` documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
|
||||
for more.
|
||||
|
||||
#### Exit codes
|
||||
### Exit codes
|
||||
|
||||
By default, Ruff exits with the following status codes:
|
||||
|
||||
@@ -639,6 +653,25 @@ Ruff supports two command-line flags that alter its exit code behavior:
|
||||
`--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after
|
||||
autofixing.
|
||||
|
||||
### Autocompletion
|
||||
|
||||
Ruff supports autocompletion for most shells. A shell-specific completion script can be generated
|
||||
by `ruff completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`,
|
||||
`powershell`, or `zsh`.
|
||||
|
||||
The exact steps required to enable autocompletion will vary by shell. For example instructions,
|
||||
see the [Poetry](https://python-poetry.org/docs/#enable-tab-completion-for-bash-fish-or-zsh) or
|
||||
[ripgrep](https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#complete) documentation.
|
||||
|
||||
As an example: to enable autocompletion for Zsh, run
|
||||
`ruff generate-shell-completion zsh > ~/.zfunc/_ruff`. Then add the following line to your
|
||||
`~/.zshrc` file, if they're not already present:
|
||||
|
||||
```zsh
|
||||
fpath+=~/.zfunc
|
||||
autoload -Uz compinit && compinit
|
||||
```
|
||||
|
||||
<!-- End section: Configuration -->
|
||||
|
||||
## Supported Rules
|
||||
@@ -683,7 +716,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/) on PyPI.
|
||||
| F523 | string-dot-format-extra-positional-arguments | `.format` call has unused arguments at position(s): {message} | |
|
||||
| F524 | string-dot-format-missing-arguments | `.format` call is missing argument(s) for placeholder(s): {message} | |
|
||||
| F525 | string-dot-format-mixing-automatic | `.format` string mixes automatic and manual numbering | |
|
||||
| F541 | f-string-missing-placeholders | f-string without any placeholders | 🛠 |
|
||||
| F541 | [f-string-missing-placeholders](https://github.com/charliermarsh/ruff/blob/main/docs/rules/f-string-missing-placeholders.md) | f-string without any placeholders | 🛠 |
|
||||
| F601 | multi-value-repeated-key-literal | Dictionary key literal `{name}` repeated | 🛠 |
|
||||
| F602 | multi-value-repeated-key-variable | Dictionary key `{name}` repeated | 🛠 |
|
||||
| F621 | expressions-in-star-assignment | Too many expressions in star-unpacking assignment | |
|
||||
@@ -702,7 +735,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/) on PyPI.
|
||||
| F821 | undefined-name | Undefined name `{name}` | |
|
||||
| F822 | undefined-export | Undefined name `{name}` in `__all__` | |
|
||||
| F823 | undefined-local | Local variable `{name}` referenced before assignment | |
|
||||
| F841 | unused-variable | Local variable `{name}` is assigned to but never used | 🛠 |
|
||||
| F841 | [unused-variable](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unused-variable.md) | Local variable `{name}` is assigned to but never used | 🛠 |
|
||||
| F842 | unused-annotation | Local variable `{name}` is annotated but never used | |
|
||||
| F901 | raise-not-implemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
|
||||
|
||||
@@ -721,13 +754,12 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/) on PyPI.
|
||||
| E701 | multiple-statements-on-one-line-colon | Multiple statements on one line (colon) | |
|
||||
| E702 | multiple-statements-on-one-line-semicolon | Multiple statements on one line (semicolon) | |
|
||||
| E703 | useless-semicolon | Statement ends with an unnecessary semicolon | |
|
||||
| E704 | multiple-statements-on-one-line-def | Multiple statements on one line (def) | |
|
||||
| E711 | none-comparison | Comparison to `None` should be `cond is None` | 🛠 |
|
||||
| E712 | true-false-comparison | Comparison to `True` should be `cond is True` | 🛠 |
|
||||
| E713 | not-in-test | Test for membership should be `not in` | 🛠 |
|
||||
| E714 | not-is-test | Test for object identity should be `is not` | 🛠 |
|
||||
| E721 | type-comparison | Do not compare types, use `isinstance()` | |
|
||||
| E722 | bare-except | Do not use bare `except` | |
|
||||
| E722 | [bare-except](https://github.com/charliermarsh/ruff/blob/main/docs/rules/bare-except.md) | Do not use bare `except` | |
|
||||
| E731 | lambda-assignment | Do not assign a `lambda` expression, use a `def` | 🛠 |
|
||||
| E741 | ambiguous-variable-name | Ambiguous variable name: `{name}` | |
|
||||
| E742 | ambiguous-class-name | Ambiguous class name: `{name}` | |
|
||||
@@ -749,7 +781,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| C901 | [function-is-too-complex](https://github.com/charliermarsh/ruff/blob/main/docs/rules/function-is-too-complex.md) | `{name}` is too complex ({complexity}) | |
|
||||
| C901 | [complex-structure](https://github.com/charliermarsh/ruff/blob/main/docs/rules/complex-structure.md) | `{name}` is too complex ({complexity}) | |
|
||||
|
||||
### isort (I)
|
||||
|
||||
@@ -818,7 +850,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/) on PyPI.
|
||||
| D401 | non-imperative-mood | First line of docstring should be in imperative mood: "{first_line}" | |
|
||||
| D402 | no-signature | First line should not be the function's signature | |
|
||||
| D403 | first-line-capitalized | First word of the first line should be properly capitalized | |
|
||||
| D404 | no-this-prefix | First word of the docstring should not be "This" | |
|
||||
| D404 | docstring-starts-with-this | First word of the docstring should not be "This" | |
|
||||
| D405 | capitalize-section-name | Section name should be properly capitalized ("{name}") | 🛠 |
|
||||
| D406 | new-line-after-section-name | Section name should end with a newline ("{name}") | 🛠 |
|
||||
| D407 | dashed-underline-after-section | Missing dashed underline after section ("{name}") | 🛠 |
|
||||
@@ -828,12 +860,12 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/) on PyPI.
|
||||
| D411 | blank-line-before-section | Missing blank line before section ("{name}") | 🛠 |
|
||||
| D412 | no-blank-lines-between-header-and-content | No blank lines allowed between a section header and its content ("{name}") | 🛠 |
|
||||
| D413 | blank-line-after-last-section | Missing blank line after last section ("{name}") | 🛠 |
|
||||
| D414 | non-empty-section | Section has no content ("{name}") | |
|
||||
| D414 | empty-docstring-section | Section has no content ("{name}") | |
|
||||
| D415 | ends-in-punctuation | First line should end with a period, question mark, or exclamation point | 🛠 |
|
||||
| D416 | section-name-ends-in-colon | Section name should end with a colon ("{name}") | 🛠 |
|
||||
| D417 | document-all-arguments | Missing argument description in the docstring: `{name}` | |
|
||||
| D418 | skip-docstring | Function decorated with `@overload` shouldn't contain a docstring | |
|
||||
| D419 | non-empty | Docstring is empty | |
|
||||
| D417 | undocumented-param | Missing argument description in the docstring: `{name}` | |
|
||||
| D418 | overload-with-docstring | Function decorated with `@overload` shouldn't contain a docstring | |
|
||||
| D419 | empty-docstring | Docstring is empty | |
|
||||
|
||||
### pyupgrade (UP)
|
||||
|
||||
@@ -845,10 +877,10 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/) on PyPI.
|
||||
| UP003 | type-of-primitive | Use `{}` instead of `type(...)` | 🛠 |
|
||||
| UP004 | useless-object-inheritance | Class `{name}` inherits from `object` | 🛠 |
|
||||
| UP005 | deprecated-unittest-alias | `{alias}` is deprecated, use `{target}` | 🛠 |
|
||||
| UP006 | use-pep585-annotation | Use `{}` instead of `{}` for type annotations | 🛠 |
|
||||
| UP007 | use-pep604-annotation | Use `X \| Y` for type annotations | 🛠 |
|
||||
| UP006 | deprecated-collection-type | Use `{}` instead of `{}` for type annotations | 🛠 |
|
||||
| UP007 | typing-union | Use `X \| Y` for type annotations | 🛠 |
|
||||
| UP008 | super-call-with-parameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
|
||||
| UP009 | pep3120-unnecessary-coding-comment | UTF-8 encoding declaration is unnecessary | 🛠 |
|
||||
| UP009 | utf8-encoding-declaration | UTF-8 encoding declaration is unnecessary | 🛠 |
|
||||
| UP010 | unnecessary-future-import | Unnecessary `__future__` import `{import}` for target Python version | 🛠 |
|
||||
| UP011 | lru-cache-without-parameters | Unnecessary parameters to `functools.lru_cache` | 🛠 |
|
||||
| UP012 | unnecessary-encode-utf8 | Unnecessary call to `encode` as UTF-8 | 🛠 |
|
||||
@@ -934,7 +966,7 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/) on PyPI.
|
||||
| S506 | unsafe-yaml-load | Probable use of unsafe loader `{name}` with `yaml.load`. Allows instantiation of arbitrary objects. Consider `yaml.safe_load`. | |
|
||||
| S508 | snmp-insecure-version | The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. | |
|
||||
| S509 | snmp-weak-cryptography | You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. | |
|
||||
| S608 | [hardcoded-sql-expression](https://github.com/charliermarsh/ruff/blob/main/docs/rules/hardcoded-sql-expression.md) | Possible SQL injection vector through string-based query construction: "{}" | |
|
||||
| S608 | [hardcoded-sql-expression](https://github.com/charliermarsh/ruff/blob/main/docs/rules/hardcoded-sql-expression.md) | Possible SQL injection vector through string-based query construction | |
|
||||
| S612 | logging-config-insecure-listen | Use of insecure `logging.config.listen` detected | |
|
||||
| S701 | jinja2-autoescape-false | Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function. | |
|
||||
|
||||
@@ -1017,9 +1049,9 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| C400 | unnecessary-generator-list | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
|
||||
| C401 | unnecessary-generator-set | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
|
||||
| C402 | unnecessary-generator-dict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
|
||||
| C400 | [unnecessary-generator-list](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-list.md) | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
|
||||
| C401 | [unnecessary-generator-set](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-set.md) | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
|
||||
| C402 | [unnecessary-generator-dict](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-dict.md) | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
|
||||
| C403 | unnecessary-list-comprehension-set | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
|
||||
| C404 | unnecessary-list-comprehension-dict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
|
||||
| C405 | unnecessary-literal-set | Unnecessary `{obj_type}` literal (rewrite as a `set` literal) | 🛠 |
|
||||
@@ -1028,11 +1060,11 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
|
||||
| C409 | unnecessary-literal-within-tuple-call | Unnecessary `{literal}` literal passed to `tuple()` (rewrite as a `tuple` literal) | 🛠 |
|
||||
| C410 | unnecessary-literal-within-list-call | Unnecessary `{literal}` literal passed to `list()` (remove the outer call to `list()`) | 🛠 |
|
||||
| C411 | unnecessary-list-call | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
|
||||
| C413 | unnecessary-call-around-sorted | Unnecessary `{func}` call around `sorted()` | 🛠 |
|
||||
| C414 | unnecessary-double-cast-or-process | Unnecessary `{inner}` call within `{outer}()` | |
|
||||
| C413 | [unnecessary-call-around-sorted](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-call-around-sorted.md) | Unnecessary `{func}` call around `sorted()` | 🛠 |
|
||||
| C414 | [unnecessary-double-cast-or-process](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-double-cast-or-process.md) | Unnecessary `{inner}` call within `{outer}()` | 🛠 |
|
||||
| C415 | unnecessary-subscript-reversal | Unnecessary subscript reversal of iterable within `{func}()` | |
|
||||
| C416 | unnecessary-comprehension | Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`) | 🛠 |
|
||||
| C417 | unnecessary-map | Unnecessary `map` usage (rewrite using a generator expression) | |
|
||||
| C417 | [unnecessary-map](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-map.md) | Unnecessary `map` usage (rewrite using a generator expression) | 🛠 |
|
||||
|
||||
### flake8-datetimez (DTZ)
|
||||
|
||||
@@ -1058,6 +1090,16 @@ For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/) on Py
|
||||
| ---- | ---- | ------- | --- |
|
||||
| T100 | debugger | Trace found: `{name}` used | |
|
||||
|
||||
### flake8-django (DJ)
|
||||
|
||||
For more, see [flake8-django](https://pypi.org/project/flake8-django/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| DJ001 | [model-string-field-nullable](https://github.com/charliermarsh/ruff/blob/main/docs/rules/model-string-field-nullable.md) | Avoid using `null=True` on string-based fields such as {field_name} | |
|
||||
| DJ008 | [model-dunder-str](https://github.com/charliermarsh/ruff/blob/main/docs/rules/model-dunder-str.md) | Model does not define `__str__` method | |
|
||||
| DJ013 | [receiver-decorator-checker](https://github.com/charliermarsh/ruff/blob/main/docs/rules/receiver-decorator-checker.md) | `@receiver` decorator must be on top of all the other decorators | |
|
||||
|
||||
### flake8-errmsg (EM)
|
||||
|
||||
For more, see [flake8-errmsg](https://pypi.org/project/flake8-errmsg/) on PyPI.
|
||||
@@ -1127,11 +1169,11 @@ For more, see [flake8-pie](https://pypi.org/project/flake8-pie/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PIE790 | no-unnecessary-pass | Unnecessary `pass` statement | 🛠 |
|
||||
| PIE790 | unnecessary-pass | Unnecessary `pass` statement | 🛠 |
|
||||
| PIE794 | dupe-class-field-definitions | Class field `{name}` is defined multiple times | 🛠 |
|
||||
| PIE796 | prefer-unique-enums | Enum contains duplicate value: `{value}` | |
|
||||
| PIE800 | no-unnecessary-spread | Unnecessary spread `**` | |
|
||||
| PIE804 | no-unnecessary-dict-kwargs | Unnecessary `dict` kwargs | |
|
||||
| PIE800 | unnecessary-spread | Unnecessary spread `**` | |
|
||||
| PIE804 | 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` | |
|
||||
|
||||
@@ -1151,6 +1193,8 @@ For more, see [flake8-pyi](https://pypi.org/project/flake8-pyi/) on PyPI.
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PYI001 | [prefix-type-params](https://github.com/charliermarsh/ruff/blob/main/docs/rules/prefix-type-params.md) | Name of private `{kind}` must start with _ | |
|
||||
| PYI007 | [unrecognized-platform-check](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unrecognized-platform-check.md) | Unrecognized sys.platform check | |
|
||||
| PYI008 | [unrecognized-platform-name](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unrecognized-platform-name.md) | Unrecognized platform `{platform}` | |
|
||||
|
||||
### flake8-pytest-style (PT)
|
||||
|
||||
@@ -1217,8 +1261,8 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/) on Py
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| SIM101 | duplicate-isinstance-call | Multiple `isinstance` calls for `{name}`, merge into a single call | 🛠 |
|
||||
| SIM102 | nested-if-statements | Use a single `if` statement instead of nested `if` statements | 🛠 |
|
||||
| SIM103 | return-bool-condition-directly | Return the condition `{condition}` directly | 🛠 |
|
||||
| SIM102 | collapsible-if | Use a single `if` statement instead of nested `if` statements | 🛠 |
|
||||
| SIM103 | needless-bool | Return the condition `{condition}` directly | 🛠 |
|
||||
| SIM105 | use-contextlib-suppress | Use `contextlib.suppress({exception})` instead of try-except-pass | |
|
||||
| SIM107 | return-in-try-except-finally | Don't use `return` in `try`/`except` and `finally` | |
|
||||
| SIM108 | use-ternary-operator | Use ternary operator `{contents}` instead of if-else-block | 🛠 |
|
||||
@@ -1226,6 +1270,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/) on Py
|
||||
| SIM110 | convert-loop-to-any | Use `{any}` instead of `for` loop | 🛠 |
|
||||
| SIM111 | convert-loop-to-all | Use `{all}` instead of `for` loop | 🛠 |
|
||||
| SIM112 | use-capital-environment-variables | Use capitalized environment variable `{expected}` instead of `{original}` | 🛠 |
|
||||
| SIM114 | [if-with-same-arms](https://github.com/charliermarsh/ruff/blob/main/docs/rules/if-with-same-arms.md) | Combine `if` branches using logical `or` operator | |
|
||||
| SIM115 | open-file-with-context-handler | Use context handler for opening files | |
|
||||
| SIM117 | multiple-with-statements | Use a single `with` statement with multiple contexts instead of nested `with` statements | 🛠 |
|
||||
| SIM118 | key-in-dict | Use `{key} in {dict}` instead of `{key} in {dict}.keys()` | 🛠 |
|
||||
@@ -1248,7 +1293,7 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| TID251 | banned-api | `{name}` is banned: {message} | |
|
||||
| TID251 | [banned-api](https://github.com/charliermarsh/ruff/blob/main/docs/rules/banned-api.md) | `{name}` is banned: {message} | |
|
||||
| TID252 | [relative-imports](https://github.com/charliermarsh/ruff/blob/main/docs/rules/relative-imports.md) | Relative imports from parent modules are banned | 🛠 |
|
||||
|
||||
### flake8-type-checking (TCH)
|
||||
@@ -1321,7 +1366,7 @@ For more, see [pandas-vet](https://pypi.org/project/pandas-vet/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PD002 | use-of-inplace-argument | `inplace=True` should be avoided; it has inconsistent behavior | 🛠 |
|
||||
| PD002 | [use-of-inplace-argument](https://github.com/charliermarsh/ruff/blob/main/docs/rules/use-of-inplace-argument.md) | `inplace=True` should be avoided; it has inconsistent behavior | 🛠 |
|
||||
| PD003 | use-of-dot-is-null | `.isna` is preferred to `.isnull`; functionality is equivalent | |
|
||||
| PD004 | use-of-dot-not-null | `.notna` is preferred to `.notnull`; functionality is equivalent | |
|
||||
| PD007 | use-of-dot-ix | `.ix` is deprecated; use more explicit `.loc` or `.iloc` | |
|
||||
@@ -1703,6 +1748,7 @@ natively, including:
|
||||
* [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
* [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
* [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
* [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
|
||||
* [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
|
||||
* [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
* [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
@@ -1801,6 +1847,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
* [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
* [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
* [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
* [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
|
||||
* [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
|
||||
* [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
|
||||
* [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
@@ -2521,6 +2568,25 @@ select = ["E", "F", "B", "Q"]
|
||||
|
||||
---
|
||||
|
||||
#### [`show-fixes`](#show-fixes)
|
||||
|
||||
Whether to show an enumeration of all autofixed lint violations
|
||||
(overridden by the `--show-fixes` command-line flag).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# By default, always enumerate fixed violations.
|
||||
show-fixes = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`show-source`](#show-source)
|
||||
|
||||
Whether to show source code snippets when reporting lint violations
|
||||
@@ -3480,8 +3546,7 @@ known-first-party = ["src"]
|
||||
#### [`known-local-folder`](#known-local-folder)
|
||||
|
||||
A list of modules to consider being a local folder.
|
||||
Generally, this is reserved for relative
|
||||
imports (from . import module).
|
||||
Generally, this is reserved for relative imports (`from . import module`).
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
@@ -3517,7 +3582,7 @@ known-third-party = ["src"]
|
||||
#### [`lines-after-imports`](#lines-after-imports)
|
||||
|
||||
The number of blank lines to place after imports.
|
||||
-1 for automatic determination.
|
||||
Use `-1` for automatic determination.
|
||||
|
||||
**Default value**: `-1`
|
||||
|
||||
@@ -3533,6 +3598,24 @@ lines-after-imports = 1
|
||||
|
||||
---
|
||||
|
||||
#### [`lines-between-types`](#lines-between-types)
|
||||
|
||||
The number of lines to place between "direct" and `import from` imports.
|
||||
|
||||
**Default value**: `0`
|
||||
|
||||
**Type**: `int`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
# Use a single line between direct and from import
|
||||
lines-between-types = 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`no-lines-before`](#no-lines-before)
|
||||
|
||||
A list of sections that should _not_ be delineated from the previous
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.245"
|
||||
version = "0.0.246"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.245"
|
||||
version = "0.0.246"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
@@ -39,8 +39,8 @@ 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.245", path = "../ruff_macros" }
|
||||
ruff_python = { version = "0.0.245", path = "../ruff_python" }
|
||||
ruff_macros = { version = "0.0.246", path = "../ruff_macros" }
|
||||
ruff_python = { version = "0.0.246", path = "../ruff_python" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-common = { workspace = true }
|
||||
rustpython-parser = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,9 @@ x = set(x for x in range(3))
|
||||
x = set(
|
||||
x for x in range(3)
|
||||
)
|
||||
|
||||
y = f'{set(a if a < 6 else 0 for a in range(3))}'
|
||||
_ = '{}'.format(set(a if a < 6 else 0 for a in range(3)))
|
||||
print(f'Hello {set(a for a in range(3))} World')
|
||||
|
||||
def set(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@@ -3,3 +3,5 @@ dict(
|
||||
(x, x) for x in range(3)
|
||||
)
|
||||
dict(((x, x) for x in range(3)), z=3)
|
||||
y = f'{dict((x, x) for x in range(3))}'
|
||||
print(f'Hello {dict((x, x) for x in range(3))} World')
|
||||
|
||||
@@ -4,7 +4,9 @@ list(sorted(x))
|
||||
reversed(sorted(x))
|
||||
reversed(sorted(x, key=lambda e: e))
|
||||
reversed(sorted(x, reverse=True))
|
||||
|
||||
reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
reversed(sorted(x, reverse=False))
|
||||
|
||||
def reversed(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@@ -12,3 +12,9 @@ sorted(list(x))
|
||||
sorted(tuple(x))
|
||||
sorted(sorted(x))
|
||||
sorted(reversed(x))
|
||||
tuple(
|
||||
list(
|
||||
[x, 3, "hell"\
|
||||
"o"]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
# Errors.
|
||||
nums = [1, 2, 3]
|
||||
map(lambda x: x + 1, nums)
|
||||
map(lambda x: str(x), nums)
|
||||
list(map(lambda x: x * 2, nums))
|
||||
set(map(lambda x: x % 2 == 0, nums))
|
||||
dict(map(lambda v: (v, v**2), nums))
|
||||
map(lambda: "const", nums)
|
||||
map(lambda _: 3.0, nums)
|
||||
_ = "".join(map(lambda x: x in nums and "1" or "0", range(123)))
|
||||
all(map(lambda v: isinstance(v, dict), nums))
|
||||
filter(func, map(lambda v: v, nums))
|
||||
|
||||
# When inside f-string, then the fix should be surrounded by whitespace
|
||||
_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
_ = f"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
|
||||
# Error, but unfixable.
|
||||
# For simple expressions, this could be: `(x if x else 1 for x in nums)`.
|
||||
# For more complex expressions, this would differ: `(x + 2 if x else 3 for x in nums)`.
|
||||
map(lambda x=1: x, nums)
|
||||
|
||||
# False negatives.
|
||||
map(lambda x=2, y=1: x + y, nums, nums)
|
||||
set(map(lambda x, y: x, nums, nums))
|
||||
|
||||
|
||||
def myfunc(arg1: int, arg2: int = 4):
|
||||
return 2 * arg1 + arg2
|
||||
|
||||
|
||||
list(map(myfunc, nums))
|
||||
|
||||
[x for x in nums]
|
||||
|
||||
39
crates/ruff/resources/test/fixtures/flake8_django/DJ001.py
vendored
Normal file
39
crates/ruff/resources/test/fixtures/flake8_django/DJ001.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.db.models import Model as DjangoModel
|
||||
from django.db import models
|
||||
from django.db.models import CharField as SmthCharField
|
||||
|
||||
|
||||
class IncorrectModel(models.Model):
|
||||
charfield = models.CharField(max_length=255, null=True)
|
||||
textfield = models.TextField(max_length=255, null=True)
|
||||
slugfield = models.SlugField(max_length=255, null=True)
|
||||
emailfield = models.EmailField(max_length=255, null=True)
|
||||
filepathfield = models.FilePathField(max_length=255, null=True)
|
||||
urlfield = models.URLField(max_length=255, null=True)
|
||||
|
||||
|
||||
class IncorrectModelWithAliasedBase(DjangoModel):
|
||||
charfield = DjangoModel.CharField(max_length=255, null=True)
|
||||
textfield = SmthCharField(max_length=255, null=True)
|
||||
slugfield = models.SlugField(max_length=255, null=True)
|
||||
emailfield = models.EmailField(max_length=255, null=True)
|
||||
filepathfield = models.FilePathField(max_length=255, null=True)
|
||||
urlfield = models.URLField(max_length=255, null=True)
|
||||
|
||||
|
||||
class CorrectModel(models.Model):
|
||||
charfield = models.CharField(max_length=255, null=False, blank=True)
|
||||
textfield = models.TextField(max_length=255, null=False, blank=True)
|
||||
slugfield = models.SlugField(max_length=255, null=False, blank=True)
|
||||
emailfield = models.EmailField(max_length=255, null=False, blank=True)
|
||||
filepathfield = models.FilePathField(max_length=255, null=False, blank=True)
|
||||
urlfield = models.URLField(max_length=255, null=False, blank=True)
|
||||
|
||||
charfieldu = models.CharField(max_length=255, null=True, blank=True, unique=True)
|
||||
textfieldu = models.TextField(max_length=255, null=True, blank=True, unique=True)
|
||||
slugfieldu = models.SlugField(max_length=255, null=True, blank=True, unique=True)
|
||||
emailfieldu = models.EmailField(max_length=255, null=True, blank=True, unique=True)
|
||||
filepathfieldu = models.FilePathField(
|
||||
max_length=255, null=True, blank=True, unique=True
|
||||
)
|
||||
urlfieldu = models.URLField(max_length=255, null=True, blank=True, unique=True)
|
||||
167
crates/ruff/resources/test/fixtures/flake8_django/DJ008.py
vendored
Normal file
167
crates/ruff/resources/test/fixtures/flake8_django/DJ008.py
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
from django.db import models
|
||||
from django.db.models import Model
|
||||
|
||||
|
||||
# Models without __str__
|
||||
class TestModel1(models.Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test model"
|
||||
verbose_name_plural = "test models"
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
class TestModel2(Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test model"
|
||||
verbose_name_plural = "test models"
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
class TestModel3(Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
# Models with __str__
|
||||
class TestModel4(Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test model"
|
||||
verbose_name_plural = "test models"
|
||||
|
||||
def __str__(self):
|
||||
return self.new_field
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
class TestModel5(models.Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test model"
|
||||
verbose_name_plural = "test models"
|
||||
|
||||
def __str__(self):
|
||||
return self.new_field
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
# Abstract models without str
|
||||
class AbstractTestModel1(models.Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
class AbstractTestModel2(Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
# Abstract models with __str__
|
||||
|
||||
|
||||
class AbstractTestModel3(Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.new_field
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
class AbstractTestModel4(models.Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.new_field
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
|
||||
|
||||
class AbstractTestModel5(models.Model):
|
||||
new_field = models.CharField(max_length=10)
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
|
||||
def __str__(self):
|
||||
return self.new_field
|
||||
|
||||
@property
|
||||
def my_brand_new_property(self):
|
||||
return 1
|
||||
|
||||
def my_beautiful_method(self):
|
||||
return 2
|
||||
17
crates/ruff/resources/test/fixtures/flake8_django/DJ013.py
vendored
Normal file
17
crates/ruff/resources/test/fixtures/flake8_django/DJ013.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
from myapp.models import MyModel
|
||||
|
||||
test_decorator = lambda func: lambda *args, **kwargs: func(*args, **kwargs)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=MyModel)
|
||||
@test_decorator
|
||||
def correct_pre_save_handler():
|
||||
pass
|
||||
|
||||
|
||||
@test_decorator
|
||||
@receiver(pre_save, sender=MyModel)
|
||||
def incorrect_pre_save_handler():
|
||||
pass
|
||||
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI007.py
vendored
Normal file
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI007.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
if sys.platform == "platform_name_1": ... # OK
|
||||
|
||||
if sys.platform != "platform_name_2": ... # OK
|
||||
|
||||
if sys.platform in ["linux"]: ... # OK
|
||||
|
||||
if sys.platform > 3: ... # OK
|
||||
|
||||
if sys.platform == 10.12: ... # OK
|
||||
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI007.pyi
vendored
Normal file
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI007.pyi
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
if sys.platform == "platform_name_1": ... # OK
|
||||
|
||||
if sys.platform != "platform_name_2": ... # OK
|
||||
|
||||
if sys.platform in ["linux"]: ... # Error: PYI007 Unrecognized sys.platform check
|
||||
|
||||
if sys.platform > 3: ... # Error: PYI007 Unrecognized sys.platform check
|
||||
|
||||
if sys.platform == 10.12: ... # Error: PYI007 Unrecognized sys.platform check
|
||||
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI008.py
vendored
Normal file
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI008.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
if sys.platform == "linus": ... # OK
|
||||
|
||||
if sys.platform != "linux": ... # OK
|
||||
|
||||
if sys.platform == "win32": ... # OK
|
||||
|
||||
if sys.platform != "darwin": ... # OK
|
||||
|
||||
if sys.platform == "cygwin": ... # OK
|
||||
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI008.pyi
vendored
Normal file
11
crates/ruff/resources/test/fixtures/flake8_pyi/PYI008.pyi
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
if sys.platform == "linus": ... # Error: PYI008 Unrecognized platform `linus`
|
||||
|
||||
if sys.platform != "linux": ... # OK
|
||||
|
||||
if sys.platform == "win32": ... # OK
|
||||
|
||||
if sys.platform != "darwin": ... # OK
|
||||
|
||||
if sys.platform == "cygwin": ... # OK
|
||||
@@ -38,13 +38,13 @@ class Foo(metaclass=BazMeta):
|
||||
return self.bar
|
||||
|
||||
def public_func(self):
|
||||
pass
|
||||
super().public_func()
|
||||
|
||||
def _private_func(self):
|
||||
pass
|
||||
super()._private_func()
|
||||
|
||||
def __really_private_func(self, arg):
|
||||
pass
|
||||
super().__really_private_func(arg)
|
||||
|
||||
|
||||
foo = Foo()
|
||||
|
||||
@@ -155,3 +155,19 @@ def f():
|
||||
if check(x):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if x not in y:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def f():
|
||||
# SIM111
|
||||
for x in iterable:
|
||||
if x > y:
|
||||
return False
|
||||
return True
|
||||
|
||||
96
crates/ruff/resources/test/fixtures/flake8_simplify/SIM114.py
vendored
Normal file
96
crates/ruff/resources/test/fixtures/flake8_simplify/SIM114.py
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
# Errors
|
||||
if a:
|
||||
b
|
||||
elif c:
|
||||
b
|
||||
|
||||
if x == 1:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
elif x == 2:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
|
||||
if x == 1:
|
||||
if True:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
elif x == 2:
|
||||
if True:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
|
||||
if x == 1:
|
||||
if True:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
elif False:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
elif x == 2:
|
||||
if True:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
elif False:
|
||||
for _ in range(20):
|
||||
print("hello")
|
||||
|
||||
if (
|
||||
x == 1
|
||||
and y == 2
|
||||
and z == 3
|
||||
and a == 4
|
||||
and b == 5
|
||||
and c == 6
|
||||
and d == 7
|
||||
and e == 8
|
||||
and f == 9
|
||||
and g == 10
|
||||
and h == 11
|
||||
and i == 12
|
||||
and j == 13
|
||||
and k == 14
|
||||
):
|
||||
pass
|
||||
elif 1 == 2:
|
||||
pass
|
||||
|
||||
if result.eofs == "O":
|
||||
pass
|
||||
elif result.eofs == "S":
|
||||
skipped = 1
|
||||
elif result.eofs == "F":
|
||||
errors = 1
|
||||
elif result.eofs == "E":
|
||||
errors = 1
|
||||
|
||||
|
||||
# OK
|
||||
def complicated_calc(*arg, **kwargs):
|
||||
return 42
|
||||
|
||||
|
||||
def foo(p):
|
||||
if p == 2:
|
||||
return complicated_calc(microsecond=0)
|
||||
elif p == 3:
|
||||
return complicated_calc(microsecond=0, second=0)
|
||||
return None
|
||||
|
||||
|
||||
a = False
|
||||
b = True
|
||||
c = True
|
||||
|
||||
if a:
|
||||
z = 1
|
||||
elif b:
|
||||
z = 2
|
||||
elif c:
|
||||
z = 1
|
||||
|
||||
# False negative (or arguably a different rule)
|
||||
if result.eofs == "F":
|
||||
errors = 1
|
||||
else:
|
||||
errors = 1
|
||||
@@ -44,9 +44,9 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
import pandas as pd
|
||||
import pandas as pd # TCH002
|
||||
|
||||
x = dict["pd.DataFrame", "pd.DataFrame"] # TCH002
|
||||
x = dict["pd.DataFrame", "pd.DataFrame"]
|
||||
|
||||
|
||||
def f():
|
||||
|
||||
8
crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_12.py
vendored
Normal file
8
crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_12.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, TYPE_CHECKING, TypeAlias
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
AnyCallable: TypeAlias = Callable[..., Any]
|
||||
16
crates/ruff/resources/test/fixtures/isort/lines_between_types.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/isort/lines_between_types.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
|
||||
from binascii import hexlify
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
from sanic import Sanic
|
||||
from loguru import Logger
|
||||
|
||||
from . import config
|
||||
from .data import Data
|
||||
@@ -3,4 +3,5 @@ line-length = 88
|
||||
|
||||
[tool.ruff.isort]
|
||||
lines-after-imports = 3
|
||||
lines-between-types = 2
|
||||
known-local-folder = ["ruff"]
|
||||
|
||||
@@ -44,3 +44,13 @@ a: List[str] = []
|
||||
#:
|
||||
if a := 1:
|
||||
pass
|
||||
#:
|
||||
func = lambda x: x** 2 if cond else lambda x:x
|
||||
#:
|
||||
class C: ...
|
||||
#:
|
||||
def f(): ...
|
||||
#: E701:1:8 E702:1:13
|
||||
class C: ...; x = 1
|
||||
#: E701:1:8 E702:1:13
|
||||
class C: ...; ...
|
||||
|
||||
@@ -5,17 +5,16 @@ f = lambda x: 2 * x
|
||||
#: E731
|
||||
while False:
|
||||
this = lambda y, z: 2 * x
|
||||
|
||||
#: E731
|
||||
f = lambda: (yield 1)
|
||||
#: E731
|
||||
f = lambda: (yield from g())
|
||||
|
||||
f = object()
|
||||
f.method = lambda: "Method"
|
||||
|
||||
f = {}
|
||||
f["a"] = lambda x: x ** 2
|
||||
|
||||
f["a"] = lambda x: x**2
|
||||
f = []
|
||||
f.append(lambda x: x ** 2)
|
||||
|
||||
f = g = lambda x: x ** 2
|
||||
|
||||
f.append(lambda x: x**2)
|
||||
f = g = lambda x: x**2
|
||||
lambda: "no-op"
|
||||
|
||||
@@ -66,3 +66,40 @@ def f(a, b):
|
||||
|
||||
y = \
|
||||
a if a is not None else b
|
||||
|
||||
|
||||
def f():
|
||||
with Nested(m) as (cm):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
with (Nested(m) as (cm),):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
with Nested(m) as (x, y):
|
||||
pass
|
||||
|
||||
|
||||
def f():
|
||||
toplevel = tt = lexer.get_token()
|
||||
if not tt:
|
||||
break
|
||||
|
||||
|
||||
def f():
|
||||
toplevel = tt = lexer.get_token()
|
||||
|
||||
|
||||
def f():
|
||||
toplevel = (a, b) = lexer.get_token()
|
||||
|
||||
|
||||
def f():
|
||||
(a, b) = toplevel = lexer.get_token()
|
||||
|
||||
|
||||
def f():
|
||||
toplevel = tt = 1
|
||||
|
||||
@@ -27,6 +27,7 @@ def good():
|
||||
logger.exception("process failed")
|
||||
raise
|
||||
|
||||
|
||||
def still_good():
|
||||
try:
|
||||
process()
|
||||
@@ -35,6 +36,14 @@ def still_good():
|
||||
raise
|
||||
|
||||
|
||||
def still_good_too():
|
||||
try:
|
||||
process()
|
||||
except MyException as e:
|
||||
print(e)
|
||||
raise e from None
|
||||
|
||||
|
||||
def still_actually_good():
|
||||
try:
|
||||
process()
|
||||
@@ -60,5 +69,6 @@ def bad_that_needs_recursion_2():
|
||||
except MyException as e:
|
||||
logger.exception("process failed")
|
||||
if True:
|
||||
|
||||
def foo():
|
||||
raise e
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Expr, ExprContext, ExprKind, Keyword,
|
||||
Operator, Unaryop,
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, Operator, Stmt, StmtKind, Unaryop,
|
||||
Withitem,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -126,6 +127,36 @@ impl From<&Cmpop> for ComparableCmpop {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableAlias<'a> {
|
||||
pub name: &'a str,
|
||||
pub asname: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Alias> for ComparableAlias<'a> {
|
||||
fn from(alias: &'a Alias) -> Self {
|
||||
Self {
|
||||
name: &alias.node.name,
|
||||
asname: alias.node.asname.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ComparableWithitem<'a> {
|
||||
pub context_expr: ComparableExpr<'a>,
|
||||
pub optional_vars: Option<ComparableExpr<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Withitem> for ComparableWithitem<'a> {
|
||||
fn from(withitem: &'a Withitem) -> Self {
|
||||
Self {
|
||||
context_expr: (&withitem.context_expr).into(),
|
||||
optional_vars: withitem.optional_vars.as_ref().map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableConstant<'a> {
|
||||
None,
|
||||
@@ -147,9 +178,7 @@ impl<'a> From<&'a Constant> for ComparableConstant<'a> {
|
||||
Constant::Str(value) => Self::Str(value),
|
||||
Constant::Bytes(value) => Self::Bytes(value),
|
||||
Constant::Int(value) => Self::Int(value),
|
||||
Constant::Tuple(value) => {
|
||||
Self::Tuple(value.iter().map(std::convert::Into::into).collect())
|
||||
}
|
||||
Constant::Tuple(value) => Self::Tuple(value.iter().map(Into::into).collect()),
|
||||
Constant::Float(value) => Self::Float(value.to_bits()),
|
||||
Constant::Complex { real, imag } => Self::Complex {
|
||||
real: real.to_bits(),
|
||||
@@ -174,37 +203,23 @@ pub struct ComparableArguments<'a> {
|
||||
impl<'a> From<&'a Arguments> for ComparableArguments<'a> {
|
||||
fn from(arguments: &'a Arguments) -> Self {
|
||||
Self {
|
||||
posonlyargs: arguments
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
args: arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
vararg: arguments.vararg.as_ref().map(std::convert::Into::into),
|
||||
kwonlyargs: arguments
|
||||
.kwonlyargs
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
kw_defaults: arguments
|
||||
.kw_defaults
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
kwarg: arguments.vararg.as_ref().map(std::convert::Into::into),
|
||||
defaults: arguments
|
||||
.defaults
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
posonlyargs: arguments.posonlyargs.iter().map(Into::into).collect(),
|
||||
args: arguments.args.iter().map(Into::into).collect(),
|
||||
vararg: arguments.vararg.as_ref().map(Into::into),
|
||||
kwonlyargs: arguments.kwonlyargs.iter().map(Into::into).collect(),
|
||||
kw_defaults: arguments.kw_defaults.iter().map(Into::into).collect(),
|
||||
kwarg: arguments.vararg.as_ref().map(Into::into),
|
||||
defaults: arguments.defaults.iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<Arguments>> for ComparableArguments<'a> {
|
||||
fn from(arguments: &'a Box<Arguments>) -> Self {
|
||||
(&**arguments).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<Arg>> for ComparableArg<'a> {
|
||||
fn from(arg: &'a Box<Arg>) -> Self {
|
||||
(&**arg).into()
|
||||
@@ -222,7 +237,7 @@ impl<'a> From<&'a Arg> for ComparableArg<'a> {
|
||||
fn from(arg: &'a Arg) -> Self {
|
||||
Self {
|
||||
arg: &arg.node.arg,
|
||||
annotation: arg.node.annotation.as_ref().map(std::convert::Into::into),
|
||||
annotation: arg.node.annotation.as_ref().map(Into::into),
|
||||
type_comment: arg.node.type_comment.as_deref(),
|
||||
}
|
||||
}
|
||||
@@ -256,16 +271,32 @@ impl<'a> From<&'a Comprehension> for ComparableComprehension<'a> {
|
||||
Self {
|
||||
target: (&comprehension.target).into(),
|
||||
iter: (&comprehension.iter).into(),
|
||||
ifs: comprehension
|
||||
.ifs
|
||||
.iter()
|
||||
.map(std::convert::Into::into)
|
||||
.collect(),
|
||||
ifs: comprehension.ifs.iter().map(Into::into).collect(),
|
||||
is_async: &comprehension.is_async,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableExcepthandler<'a> {
|
||||
ExceptHandler {
|
||||
type_: Option<ComparableExpr<'a>>,
|
||||
name: Option<&'a str>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Excepthandler> for ComparableExcepthandler<'a> {
|
||||
fn from(excepthandler: &'a Excepthandler) -> Self {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, name, body } = &excepthandler.node;
|
||||
Self::ExceptHandler {
|
||||
type_: type_.as_ref().map(Into::into),
|
||||
name: name.as_deref(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableExpr<'a> {
|
||||
BoolOp {
|
||||
@@ -399,7 +430,7 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
match &expr.node {
|
||||
ExprKind::BoolOp { op, values } => Self::BoolOp {
|
||||
op: op.into(),
|
||||
values: values.iter().map(std::convert::Into::into).collect(),
|
||||
values: values.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::NamedExpr { target, value } => Self::NamedExpr {
|
||||
target: target.into(),
|
||||
@@ -426,20 +457,20 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
ExprKind::Dict { keys, values } => Self::Dict {
|
||||
keys: keys
|
||||
.iter()
|
||||
.map(|expr| expr.as_ref().map(std::convert::Into::into))
|
||||
.map(|expr| expr.as_ref().map(Into::into))
|
||||
.collect(),
|
||||
values: values.iter().map(std::convert::Into::into).collect(),
|
||||
values: values.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::Set { elts } => Self::Set {
|
||||
elts: elts.iter().map(std::convert::Into::into).collect(),
|
||||
elts: elts.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::ListComp { elt, generators } => Self::ListComp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
generators: generators.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::SetComp { elt, generators } => Self::SetComp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
generators: generators.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::DictComp {
|
||||
key,
|
||||
@@ -448,17 +479,17 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
} => Self::DictComp {
|
||||
key: key.into(),
|
||||
value: value.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
generators: generators.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::GeneratorExp { elt, generators } => Self::GeneratorExp {
|
||||
elt: elt.into(),
|
||||
generators: generators.iter().map(std::convert::Into::into).collect(),
|
||||
generators: generators.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::Await { value } => Self::Await {
|
||||
value: value.into(),
|
||||
},
|
||||
ExprKind::Yield { value } => Self::Yield {
|
||||
value: value.as_ref().map(std::convert::Into::into),
|
||||
value: value.as_ref().map(Into::into),
|
||||
},
|
||||
ExprKind::YieldFrom { value } => Self::YieldFrom {
|
||||
value: value.into(),
|
||||
@@ -469,8 +500,8 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
comparators,
|
||||
} => Self::Compare {
|
||||
left: left.into(),
|
||||
ops: ops.iter().map(std::convert::Into::into).collect(),
|
||||
comparators: comparators.iter().map(std::convert::Into::into).collect(),
|
||||
ops: ops.iter().map(Into::into).collect(),
|
||||
comparators: comparators.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::Call {
|
||||
func,
|
||||
@@ -478,8 +509,8 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
keywords,
|
||||
} => Self::Call {
|
||||
func: func.into(),
|
||||
args: args.iter().map(std::convert::Into::into).collect(),
|
||||
keywords: keywords.iter().map(std::convert::Into::into).collect(),
|
||||
args: args.iter().map(Into::into).collect(),
|
||||
keywords: keywords.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::FormattedValue {
|
||||
value,
|
||||
@@ -488,10 +519,10 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
} => Self::FormattedValue {
|
||||
value: value.into(),
|
||||
conversion,
|
||||
format_spec: format_spec.as_ref().map(std::convert::Into::into),
|
||||
format_spec: format_spec.as_ref().map(Into::into),
|
||||
},
|
||||
ExprKind::JoinedStr { values } => Self::JoinedStr {
|
||||
values: values.iter().map(std::convert::Into::into).collect(),
|
||||
values: values.iter().map(Into::into).collect(),
|
||||
},
|
||||
ExprKind::Constant { value, kind } => Self::Constant {
|
||||
value: value.into(),
|
||||
@@ -516,18 +547,314 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::List { elts, ctx } => Self::List {
|
||||
elts: elts.iter().map(std::convert::Into::into).collect(),
|
||||
elts: elts.iter().map(Into::into).collect(),
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Tuple { elts, ctx } => Self::Tuple {
|
||||
elts: elts.iter().map(std::convert::Into::into).collect(),
|
||||
elts: elts.iter().map(Into::into).collect(),
|
||||
ctx: ctx.into(),
|
||||
},
|
||||
ExprKind::Slice { lower, upper, step } => Self::Slice {
|
||||
lower: lower.as_ref().map(std::convert::Into::into),
|
||||
upper: upper.as_ref().map(std::convert::Into::into),
|
||||
step: step.as_ref().map(std::convert::Into::into),
|
||||
lower: lower.as_ref().map(Into::into),
|
||||
upper: upper.as_ref().map(Into::into),
|
||||
step: step.as_ref().map(Into::into),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ComparableStmt<'a> {
|
||||
FunctionDef {
|
||||
name: &'a str,
|
||||
args: ComparableArguments<'a>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
decorator_list: Vec<ComparableExpr<'a>>,
|
||||
returns: Option<ComparableExpr<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
AsyncFunctionDef {
|
||||
name: &'a str,
|
||||
args: ComparableArguments<'a>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
decorator_list: Vec<ComparableExpr<'a>>,
|
||||
returns: Option<ComparableExpr<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
ClassDef {
|
||||
name: &'a str,
|
||||
bases: Vec<ComparableExpr<'a>>,
|
||||
keywords: Vec<ComparableKeyword<'a>>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
decorator_list: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
Return {
|
||||
value: Option<ComparableExpr<'a>>,
|
||||
},
|
||||
Delete {
|
||||
targets: Vec<ComparableExpr<'a>>,
|
||||
},
|
||||
Assign {
|
||||
targets: Vec<ComparableExpr<'a>>,
|
||||
value: ComparableExpr<'a>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
AugAssign {
|
||||
target: ComparableExpr<'a>,
|
||||
op: ComparableOperator,
|
||||
value: ComparableExpr<'a>,
|
||||
},
|
||||
AnnAssign {
|
||||
target: ComparableExpr<'a>,
|
||||
annotation: ComparableExpr<'a>,
|
||||
value: Option<ComparableExpr<'a>>,
|
||||
simple: usize,
|
||||
},
|
||||
For {
|
||||
target: ComparableExpr<'a>,
|
||||
iter: ComparableExpr<'a>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
orelse: Vec<ComparableStmt<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
AsyncFor {
|
||||
target: ComparableExpr<'a>,
|
||||
iter: ComparableExpr<'a>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
orelse: Vec<ComparableStmt<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
While {
|
||||
test: ComparableExpr<'a>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
orelse: Vec<ComparableStmt<'a>>,
|
||||
},
|
||||
If {
|
||||
test: ComparableExpr<'a>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
orelse: Vec<ComparableStmt<'a>>,
|
||||
},
|
||||
With {
|
||||
items: Vec<ComparableWithitem<'a>>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
AsyncWith {
|
||||
items: Vec<ComparableWithitem<'a>>,
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
type_comment: Option<&'a str>,
|
||||
},
|
||||
Raise {
|
||||
exc: Option<ComparableExpr<'a>>,
|
||||
cause: Option<ComparableExpr<'a>>,
|
||||
},
|
||||
Try {
|
||||
body: Vec<ComparableStmt<'a>>,
|
||||
handlers: Vec<ComparableExcepthandler<'a>>,
|
||||
orelse: Vec<ComparableStmt<'a>>,
|
||||
finalbody: Vec<ComparableStmt<'a>>,
|
||||
},
|
||||
Assert {
|
||||
test: ComparableExpr<'a>,
|
||||
msg: Option<ComparableExpr<'a>>,
|
||||
},
|
||||
Import {
|
||||
names: Vec<ComparableAlias<'a>>,
|
||||
},
|
||||
ImportFrom {
|
||||
module: Option<&'a str>,
|
||||
names: Vec<ComparableAlias<'a>>,
|
||||
level: Option<usize>,
|
||||
},
|
||||
Global {
|
||||
names: Vec<&'a str>,
|
||||
},
|
||||
Nonlocal {
|
||||
names: Vec<&'a str>,
|
||||
},
|
||||
Expr {
|
||||
value: ComparableExpr<'a>,
|
||||
},
|
||||
Pass,
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Stmt> for ComparableStmt<'a> {
|
||||
fn from(stmt: &'a Stmt) -> Self {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_comment,
|
||||
} => Self::FunctionDef {
|
||||
name,
|
||||
args: args.into(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
decorator_list: decorator_list.iter().map(Into::into).collect(),
|
||||
returns: returns.as_ref().map(Into::into),
|
||||
type_comment: type_comment.as_ref().map(std::string::String::as_str),
|
||||
},
|
||||
StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_comment,
|
||||
} => Self::AsyncFunctionDef {
|
||||
name,
|
||||
args: args.into(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
decorator_list: decorator_list.iter().map(Into::into).collect(),
|
||||
returns: returns.as_ref().map(Into::into),
|
||||
type_comment: type_comment.as_ref().map(std::string::String::as_str),
|
||||
},
|
||||
StmtKind::ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
body,
|
||||
decorator_list,
|
||||
} => Self::ClassDef {
|
||||
name,
|
||||
bases: bases.iter().map(Into::into).collect(),
|
||||
keywords: keywords.iter().map(Into::into).collect(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
decorator_list: decorator_list.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::Return { value } => Self::Return {
|
||||
value: value.as_ref().map(Into::into),
|
||||
},
|
||||
StmtKind::Delete { targets } => Self::Delete {
|
||||
targets: targets.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::Assign {
|
||||
targets,
|
||||
value,
|
||||
type_comment,
|
||||
} => Self::Assign {
|
||||
targets: targets.iter().map(Into::into).collect(),
|
||||
value: value.into(),
|
||||
type_comment: type_comment.as_ref().map(std::string::String::as_str),
|
||||
},
|
||||
StmtKind::AugAssign { target, op, value } => Self::AugAssign {
|
||||
target: target.into(),
|
||||
op: op.into(),
|
||||
value: value.into(),
|
||||
},
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
annotation,
|
||||
value,
|
||||
simple,
|
||||
} => Self::AnnAssign {
|
||||
target: target.into(),
|
||||
annotation: annotation.into(),
|
||||
value: value.as_ref().map(Into::into),
|
||||
simple: *simple,
|
||||
},
|
||||
StmtKind::For {
|
||||
target,
|
||||
iter,
|
||||
body,
|
||||
orelse,
|
||||
type_comment,
|
||||
} => Self::For {
|
||||
target: target.into(),
|
||||
iter: iter.into(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
orelse: orelse.iter().map(Into::into).collect(),
|
||||
type_comment: type_comment.as_ref().map(String::as_str),
|
||||
},
|
||||
StmtKind::AsyncFor {
|
||||
target,
|
||||
iter,
|
||||
body,
|
||||
orelse,
|
||||
type_comment,
|
||||
} => Self::AsyncFor {
|
||||
target: target.into(),
|
||||
iter: iter.into(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
orelse: orelse.iter().map(Into::into).collect(),
|
||||
type_comment: type_comment.as_ref().map(String::as_str),
|
||||
},
|
||||
StmtKind::While { test, body, orelse } => Self::While {
|
||||
test: test.into(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
orelse: orelse.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::If { test, body, orelse } => Self::If {
|
||||
test: test.into(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
orelse: orelse.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::With {
|
||||
items,
|
||||
body,
|
||||
type_comment,
|
||||
} => Self::With {
|
||||
items: items.iter().map(Into::into).collect(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
type_comment: type_comment.as_ref().map(String::as_str),
|
||||
},
|
||||
StmtKind::AsyncWith {
|
||||
items,
|
||||
body,
|
||||
type_comment,
|
||||
} => Self::AsyncWith {
|
||||
items: items.iter().map(Into::into).collect(),
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
type_comment: type_comment.as_ref().map(String::as_str),
|
||||
},
|
||||
StmtKind::Match { .. } => unreachable!("StmtKind::Match is not supported"),
|
||||
StmtKind::Raise { exc, cause } => Self::Raise {
|
||||
exc: exc.as_ref().map(Into::into),
|
||||
cause: cause.as_ref().map(Into::into),
|
||||
},
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
} => Self::Try {
|
||||
body: body.iter().map(Into::into).collect(),
|
||||
handlers: handlers.iter().map(Into::into).collect(),
|
||||
orelse: orelse.iter().map(Into::into).collect(),
|
||||
finalbody: finalbody.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::Assert { test, msg } => Self::Assert {
|
||||
test: test.into(),
|
||||
msg: msg.as_ref().map(Into::into),
|
||||
},
|
||||
StmtKind::Import { names } => Self::Import {
|
||||
names: names.iter().map(Into::into).collect(),
|
||||
},
|
||||
StmtKind::ImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
} => Self::ImportFrom {
|
||||
module: module.as_ref().map(String::as_str),
|
||||
names: names.iter().map(Into::into).collect(),
|
||||
level: *level,
|
||||
},
|
||||
StmtKind::Global { names } => Self::Global {
|
||||
names: names.iter().map(String::as_str).collect(),
|
||||
},
|
||||
StmtKind::Nonlocal { names } => Self::Nonlocal {
|
||||
names: names.iter().map(String::as_str).collect(),
|
||||
},
|
||||
StmtKind::Expr { value } => Self::Expr {
|
||||
value: value.into(),
|
||||
},
|
||||
StmtKind::Pass => Self::Pass,
|
||||
StmtKind::Break => Self::Break,
|
||||
StmtKind::Continue => Self::Continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,49 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::fix::Fix;
|
||||
use crate::linter::FixTable;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::Locator;
|
||||
|
||||
pub mod helpers;
|
||||
|
||||
/// Auto-fix errors in a file, and write the fixed source code to disk.
|
||||
pub fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<(String, usize)> {
|
||||
pub fn fix_file(diagnostics: &[Diagnostic], locator: &Locator) -> Option<(String, FixTable)> {
|
||||
if diagnostics.iter().all(|check| check.fix.is_none()) {
|
||||
return None;
|
||||
None
|
||||
} else {
|
||||
Some(apply_fixes(diagnostics.iter(), locator))
|
||||
}
|
||||
|
||||
Some(apply_fixes(
|
||||
diagnostics.iter().filter_map(|check| check.fix.as_ref()),
|
||||
locator,
|
||||
))
|
||||
}
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(
|
||||
fixes: impl Iterator<Item = &'a Fix>,
|
||||
diagnostics: impl Iterator<Item = &'a Diagnostic>,
|
||||
locator: &'a Locator<'a>,
|
||||
) -> (String, usize) {
|
||||
) -> (String, FixTable) {
|
||||
let mut output = String::with_capacity(locator.len());
|
||||
let mut last_pos: Location = Location::new(1, 0);
|
||||
let mut applied: BTreeSet<&Fix> = BTreeSet::default();
|
||||
let mut num_fixed: usize = 0;
|
||||
let mut fixed = FxHashMap::default();
|
||||
|
||||
for fix in fixes.sorted_by_key(|fix| fix.location) {
|
||||
for (rule, fix) in diagnostics
|
||||
.filter_map(|diagnostic| {
|
||||
diagnostic
|
||||
.fix
|
||||
.as_ref()
|
||||
.map(|fix| (diagnostic.kind.rule(), fix))
|
||||
})
|
||||
.sorted_by_key(|(.., fix)| fix.location)
|
||||
{
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
// any re-application.
|
||||
if applied.contains(&fix) {
|
||||
num_fixed += 1;
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -56,14 +63,14 @@ fn apply_fixes<'a>(
|
||||
// Track that the fix was applied.
|
||||
last_pos = fix.end_location;
|
||||
applied.insert(fix);
|
||||
num_fixed += 1;
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
}
|
||||
|
||||
// Add the remaining content.
|
||||
let slice = locator.slice_source_code_at(last_pos);
|
||||
output.push_str(slice);
|
||||
|
||||
(output, num_fixed)
|
||||
(output, fixed)
|
||||
}
|
||||
|
||||
/// Apply a single fix.
|
||||
@@ -90,24 +97,41 @@ mod tests {
|
||||
|
||||
use crate::autofix::{apply_fix, apply_fixes};
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::pycodestyle::rules::NoNewLineAtEndOfFile;
|
||||
|
||||
use crate::source_code::Locator;
|
||||
|
||||
#[test]
|
||||
fn empty_file() {
|
||||
let fixes = vec![];
|
||||
let fixes: Vec<Diagnostic> = vec![];
|
||||
let locator = Locator::new(r#""#);
|
||||
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
|
||||
assert_eq!(contents, "");
|
||||
assert_eq!(fixed, 0);
|
||||
assert_eq!(fixed.values().sum::<usize>(), 0);
|
||||
}
|
||||
|
||||
impl From<Fix> for Diagnostic {
|
||||
fn from(fix: Fix) -> Self {
|
||||
Diagnostic {
|
||||
// The choice of rule here is arbitrary.
|
||||
kind: NoNewLineAtEndOfFile.into(),
|
||||
location: fix.location,
|
||||
end_location: fix.end_location,
|
||||
fix: Some(fix),
|
||||
parent: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_one_replacement() {
|
||||
let fixes = vec![Fix {
|
||||
let fixes: Vec<Diagnostic> = vec![Fix {
|
||||
content: "Bar".to_string(),
|
||||
location: Location::new(1, 8),
|
||||
end_location: Location::new(1, 14),
|
||||
}];
|
||||
}
|
||||
.into()];
|
||||
let locator = Locator::new(
|
||||
r#"
|
||||
class A(object):
|
||||
@@ -124,16 +148,17 @@ class A(Bar):
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
assert_eq!(fixed, 1);
|
||||
assert_eq!(fixed.values().sum::<usize>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_one_removal() {
|
||||
let fixes = vec![Fix {
|
||||
let fixes: Vec<Diagnostic> = vec![Fix {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
}];
|
||||
}
|
||||
.into()];
|
||||
let locator = Locator::new(
|
||||
r#"
|
||||
class A(object):
|
||||
@@ -150,22 +175,24 @@ class A:
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(fixed, 1);
|
||||
assert_eq!(fixed.values().sum::<usize>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_two_removals() {
|
||||
let fixes = vec![
|
||||
let fixes: Vec<Diagnostic> = vec![
|
||||
Fix {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 16),
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
Fix {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 16),
|
||||
end_location: Location::new(1, 23),
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
let locator = Locator::new(
|
||||
r#"
|
||||
@@ -184,22 +211,24 @@ class A:
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
assert_eq!(fixed, 2);
|
||||
assert_eq!(fixed.values().sum::<usize>(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_overlapping_fixes() {
|
||||
let fixes = vec![
|
||||
let fixes: Vec<Diagnostic> = vec![
|
||||
Fix {
|
||||
content: String::new(),
|
||||
location: Location::new(1, 7),
|
||||
end_location: Location::new(1, 15),
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
Fix {
|
||||
content: "ignored".to_string(),
|
||||
location: Location::new(1, 9),
|
||||
end_location: Location::new(1, 11),
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
let locator = Locator::new(
|
||||
r#"
|
||||
@@ -217,7 +246,7 @@ class A:
|
||||
"#
|
||||
.trim(),
|
||||
);
|
||||
assert_eq!(fixed, 1);
|
||||
assert_eq!(fixed.values().sum::<usize>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -35,9 +35,9 @@ use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rules::{
|
||||
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
|
||||
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
|
||||
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_logging_format,
|
||||
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise, flake8_return,
|
||||
flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
|
||||
flake8_django, flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions,
|
||||
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_raise,
|
||||
flake8_return, flake8_self, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
|
||||
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
|
||||
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
};
|
||||
@@ -464,6 +464,15 @@ where
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
if self.settings.rules.enabled(&Rule::ReceiverDecoratorChecker) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_django::rules::receiver_decorator_checker(decorator_list, |expr| {
|
||||
self.resolve_call_path(expr)
|
||||
})
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::AmbiguousFunctionName) {
|
||||
if let Some(diagnostic) =
|
||||
pycodestyle::rules::ambiguous_function_name(name, || {
|
||||
@@ -566,7 +575,7 @@ where
|
||||
flake8_return::rules::function(self, body);
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::FunctionIsTooComplex) {
|
||||
if self.settings.rules.enabled(&Rule::ComplexStructure) {
|
||||
if let Some(diagnostic) = mccabe::rules::function_is_too_complex(
|
||||
stmt,
|
||||
name,
|
||||
@@ -771,6 +780,19 @@ where
|
||||
decorator_list,
|
||||
body,
|
||||
} => {
|
||||
if self.settings.rules.enabled(&Rule::ModelStringFieldNullable) {
|
||||
self.diagnostics
|
||||
.extend(flake8_django::rules::model_string_field_nullable(
|
||||
self, bases, body,
|
||||
));
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::ModelDunderStr) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_django::rules::model_dunder_str(self, bases, body, stmt)
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::UselessObjectInheritance) {
|
||||
pyupgrade::rules::useless_object_inheritance(self, stmt, name, bases, keywords);
|
||||
}
|
||||
@@ -1480,7 +1502,7 @@ where
|
||||
if self.settings.rules.enabled(&Rule::IfTuple) {
|
||||
pyflakes::rules::if_tuple(self, stmt, test);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::NestedIfStatements) {
|
||||
if self.settings.rules.enabled(&Rule::CollapsibleIf) {
|
||||
flake8_simplify::rules::nested_if_statements(
|
||||
self,
|
||||
stmt,
|
||||
@@ -1490,11 +1512,14 @@ where
|
||||
self.current_stmt_parent().map(Into::into),
|
||||
);
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::ReturnBoolConditionDirectly)
|
||||
{
|
||||
if self.settings.rules.enabled(&Rule::IfWithSameArms) {
|
||||
flake8_simplify::rules::if_with_same_arms(
|
||||
self,
|
||||
stmt,
|
||||
self.current_stmt_parent().map(std::convert::Into::into),
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::NeedlessBool) {
|
||||
flake8_simplify::rules::return_bool_condition_directly(self, stmt);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::UseTernaryOperator) {
|
||||
@@ -2066,7 +2091,7 @@ where
|
||||
// Ex) Optional[...]
|
||||
if !self.in_deferred_string_type_definition
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||
&& self.settings.rules.enabled(&Rule::UsePEP604Annotation)
|
||||
&& self.settings.rules.enabled(&Rule::TypingUnion)
|
||||
&& (self.settings.target_version >= PythonVersion::Py310
|
||||
|| (self.settings.target_version >= PythonVersion::Py37
|
||||
&& self.annotations_future_enabled
|
||||
@@ -2121,7 +2146,7 @@ where
|
||||
// Ex) List[...]
|
||||
if !self.in_deferred_string_type_definition
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||
&& self.settings.rules.enabled(&Rule::UsePEP585Annotation)
|
||||
&& self.settings.rules.enabled(&Rule::DeprecatedCollectionType)
|
||||
&& (self.settings.target_version >= PythonVersion::Py39
|
||||
|| (self.settings.target_version >= PythonVersion::Py37
|
||||
&& self.annotations_future_enabled
|
||||
@@ -2166,7 +2191,7 @@ where
|
||||
// Ex) typing.List[...]
|
||||
if !self.in_deferred_string_type_definition
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||
&& self.settings.rules.enabled(&Rule::UsePEP585Annotation)
|
||||
&& self.settings.rules.enabled(&Rule::DeprecatedCollectionType)
|
||||
&& (self.settings.target_version >= PythonVersion::Py39
|
||||
|| (self.settings.target_version >= PythonVersion::Py37
|
||||
&& self.annotations_future_enabled
|
||||
@@ -2367,7 +2392,7 @@ where
|
||||
}
|
||||
|
||||
// flake8-pie
|
||||
if self.settings.rules.enabled(&Rule::NoUnnecessaryDictKwargs) {
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryDictKwargs) {
|
||||
flake8_pie::rules::no_unnecessary_dict_kwargs(self, expr, keywords);
|
||||
}
|
||||
|
||||
@@ -2438,12 +2463,22 @@ where
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorSet) {
|
||||
flake8_comprehensions::rules::unnecessary_generator_set(
|
||||
self, expr, func, args, keywords,
|
||||
self,
|
||||
expr,
|
||||
self.current_expr_parent().map(Into::into),
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorDict) {
|
||||
flake8_comprehensions::rules::unnecessary_generator_dict(
|
||||
self, expr, func, args, keywords,
|
||||
self,
|
||||
expr,
|
||||
self.current_expr_parent().map(Into::into),
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
);
|
||||
}
|
||||
if self
|
||||
@@ -2532,7 +2567,13 @@ where
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryMap) {
|
||||
flake8_comprehensions::rules::unnecessary_map(self, expr, func, args);
|
||||
flake8_comprehensions::rules::unnecessary_map(
|
||||
self,
|
||||
expr,
|
||||
self.current_expr_parent().map(Into::into),
|
||||
func,
|
||||
args,
|
||||
);
|
||||
}
|
||||
|
||||
// flake8-boolean-trap
|
||||
@@ -2785,7 +2826,7 @@ where
|
||||
pyflakes::rules::repeated_keys(self, keys, values);
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::NoUnnecessarySpread) {
|
||||
if self.settings.rules.enabled(&Rule::UnnecessarySpread) {
|
||||
flake8_pie::rules::no_unnecessary_spread(self, keys, values);
|
||||
}
|
||||
}
|
||||
@@ -3121,6 +3162,23 @@ where
|
||||
if self.settings.rules.enabled(&Rule::YodaConditions) {
|
||||
flake8_simplify::rules::yoda_conditions(self, expr, left, ops, comparators);
|
||||
}
|
||||
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UnrecognizedPlatformCheck)
|
||||
|| self.settings.rules.enabled(&Rule::UnrecognizedPlatformName)
|
||||
{
|
||||
if self.path.extension().map_or(false, |ext| ext == "pyi") {
|
||||
flake8_pyi::rules::unrecognized_platform(
|
||||
self,
|
||||
expr,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
@@ -3771,7 +3829,7 @@ where
|
||||
}
|
||||
|
||||
fn visit_body(&mut self, body: &'b [Stmt]) {
|
||||
if self.settings.rules.enabled(&Rule::NoUnnecessaryPass) {
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryPass) {
|
||||
flake8_pie::rules::no_unnecessary_pass(self, body);
|
||||
}
|
||||
|
||||
@@ -3907,7 +3965,6 @@ impl<'a> Checker<'a> {
|
||||
if self.in_type_checking_block
|
||||
|| self.in_annotation
|
||||
|| self.in_deferred_string_type_definition
|
||||
|| self.in_deferred_type_definition
|
||||
{
|
||||
ExecutionContext::Typing
|
||||
} else {
|
||||
@@ -5085,7 +5142,7 @@ impl<'a> Checker<'a> {
|
||||
|| self.settings.rules.enabled(&Rule::NonImperativeMood)
|
||||
|| self.settings.rules.enabled(&Rule::NoSignature)
|
||||
|| self.settings.rules.enabled(&Rule::FirstLineCapitalized)
|
||||
|| self.settings.rules.enabled(&Rule::NoThisPrefix)
|
||||
|| self.settings.rules.enabled(&Rule::DocstringStartsWithThis)
|
||||
|| self.settings.rules.enabled(&Rule::CapitalizeSectionName)
|
||||
|| self.settings.rules.enabled(&Rule::NewLineAfterSectionName)
|
||||
|| self
|
||||
@@ -5110,12 +5167,12 @@ impl<'a> Checker<'a> {
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::BlankLineAfterLastSection)
|
||||
|| self.settings.rules.enabled(&Rule::NonEmptySection)
|
||||
|| self.settings.rules.enabled(&Rule::EmptyDocstringSection)
|
||||
|| self.settings.rules.enabled(&Rule::EndsInPunctuation)
|
||||
|| self.settings.rules.enabled(&Rule::SectionNameEndsInColon)
|
||||
|| self.settings.rules.enabled(&Rule::DocumentAllArguments)
|
||||
|| self.settings.rules.enabled(&Rule::SkipDocstring)
|
||||
|| self.settings.rules.enabled(&Rule::NonEmpty);
|
||||
|| self.settings.rules.enabled(&Rule::UndocumentedParam)
|
||||
|| self.settings.rules.enabled(&Rule::OverloadWithDocstring)
|
||||
|| self.settings.rules.enabled(&Rule::EmptyDocstring);
|
||||
|
||||
let mut overloaded_name: Option<String> = None;
|
||||
self.definitions.reverse();
|
||||
@@ -5246,13 +5303,13 @@ impl<'a> Checker<'a> {
|
||||
if self.settings.rules.enabled(&Rule::FirstLineCapitalized) {
|
||||
pydocstyle::rules::capitalized(self, &docstring);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::NoThisPrefix) {
|
||||
if self.settings.rules.enabled(&Rule::DocstringStartsWithThis) {
|
||||
pydocstyle::rules::starts_with_this(self, &docstring);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::EndsInPunctuation) {
|
||||
pydocstyle::rules::ends_with_punctuation(self, &docstring);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::SkipDocstring) {
|
||||
if self.settings.rules.enabled(&Rule::OverloadWithDocstring) {
|
||||
pydocstyle::rules::if_needed(self, &docstring);
|
||||
}
|
||||
if self
|
||||
@@ -5288,9 +5345,9 @@ impl<'a> Checker<'a> {
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::BlankLineAfterLastSection)
|
||||
|| self.settings.rules.enabled(&Rule::NonEmptySection)
|
||||
|| self.settings.rules.enabled(&Rule::EmptyDocstringSection)
|
||||
|| self.settings.rules.enabled(&Rule::SectionNameEndsInColon)
|
||||
|| self.settings.rules.enabled(&Rule::DocumentAllArguments)
|
||||
|| self.settings.rules.enabled(&Rule::UndocumentedParam)
|
||||
{
|
||||
pydocstyle::rules::sections(
|
||||
self,
|
||||
|
||||
@@ -38,16 +38,12 @@ pub fn check_physical_lines(
|
||||
let enforce_doc_line_too_long = settings.rules.enabled(&Rule::DocLineTooLong);
|
||||
let enforce_line_too_long = settings.rules.enabled(&Rule::LineTooLong);
|
||||
let enforce_no_newline_at_end_of_file = settings.rules.enabled(&Rule::NoNewLineAtEndOfFile);
|
||||
let enforce_unnecessary_coding_comment = settings
|
||||
.rules
|
||||
.enabled(&Rule::PEP3120UnnecessaryCodingComment);
|
||||
let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration);
|
||||
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
|
||||
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
|
||||
|
||||
let fix_unnecessary_coding_comment = matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings
|
||||
.rules
|
||||
.should_fix(&Rule::PEP3120UnnecessaryCodingComment);
|
||||
&& settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
|
||||
let fix_shebang_whitespace = matches!(autofix, flags::Autofix::Enabled)
|
||||
&& settings.rules.should_fix(&Rule::ShebangWhitespace);
|
||||
|
||||
|
||||
@@ -40,10 +40,7 @@ pub fn check_tokens(
|
||||
|| settings
|
||||
.rules
|
||||
.enabled(&Rule::MultipleStatementsOnOneLineSemicolon)
|
||||
|| settings.rules.enabled(&Rule::UselessSemicolon)
|
||||
|| settings
|
||||
.rules
|
||||
.enabled(&Rule::MultipleStatementsOnOneLineDef);
|
||||
|| settings.rules.enabled(&Rule::UselessSemicolon);
|
||||
let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence);
|
||||
let enforce_implicit_string_concatenation = settings
|
||||
.rules
|
||||
@@ -117,7 +114,7 @@ pub fn check_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
// E701, E702, E703, E704
|
||||
// E701, E702, E703
|
||||
if enforce_compound_statements {
|
||||
diagnostics.extend(
|
||||
pycodestyle::rules::compound_statements(tokens)
|
||||
|
||||
@@ -59,6 +59,7 @@ pub fn extract_directives(lxr: &[LexResult], flags: Flags) -> Directives {
|
||||
/// Extract a mapping from logical line to noqa line.
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
|
||||
let mut noqa_line_for: IntMap<usize, usize> = IntMap::default();
|
||||
let mut prev_non_newline: Option<(&Location, &Tok, &Location)> = None;
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
break;
|
||||
@@ -70,6 +71,21 @@ pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
|
||||
noqa_line_for.insert(i, end.row());
|
||||
}
|
||||
}
|
||||
// For continuations, we expect `noqa` directives on the last line of the
|
||||
// continuation.
|
||||
if matches!(
|
||||
tok,
|
||||
Tok::Newline | Tok::NonLogicalNewline | Tok::Comment(..)
|
||||
) {
|
||||
if let Some((.., end)) = prev_non_newline {
|
||||
for i in end.row()..start.row() {
|
||||
noqa_line_for.insert(i, start.row());
|
||||
}
|
||||
}
|
||||
prev_non_newline = None;
|
||||
} else if prev_non_newline.is_none() {
|
||||
prev_non_newline = Some((start, tok, end));
|
||||
}
|
||||
}
|
||||
noqa_line_for
|
||||
}
|
||||
@@ -193,11 +209,11 @@ z = x + 1",
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
z = 2",
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
z = 2",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
@@ -207,16 +223,51 @@ z = x + 1",
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''",
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
|
||||
);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
r#"x = \
|
||||
1"#,
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), IntMap::from_iter([(1, 2)]));
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
r#"from foo import \
|
||||
bar as baz, \
|
||||
qux as quux"#,
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(1, 3), (2, 3)])
|
||||
);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
r#"
|
||||
# Foo
|
||||
from foo import \
|
||||
bar as baz, \
|
||||
qux as quux # Baz
|
||||
x = \
|
||||
1
|
||||
y = \
|
||||
2"#,
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(3, 5), (4, 5), (6, 7), (8, 9)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -282,10 +282,6 @@ mod tests {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: RuleCodePrefix::F841.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "*.pyi".to_string(),
|
||||
prefix: RuleCodePrefix::E704.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
show_fixes: None,
|
||||
show_source: None,
|
||||
src: None,
|
||||
task_tags: None,
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::Path;
|
||||
use anyhow::{anyhow, Result};
|
||||
use colored::Colorize;
|
||||
use log::error;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::error::ParseError;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
@@ -46,6 +47,8 @@ impl<T> LinterResult<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub type FixTable = FxHashMap<&'static Rule, usize>;
|
||||
|
||||
/// Generate `Diagnostic`s from the source code contents at the
|
||||
/// given `Path`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -217,8 +220,8 @@ pub fn check_path(
|
||||
|
||||
const MAX_ITERATIONS: usize = 100;
|
||||
|
||||
/// Add any missing `#noqa` pragmas to the source code at the given `Path`.
|
||||
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
/// Add any missing `# noqa` pragmas to the source code at the given `Path`.
|
||||
pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings) -> Result<usize> {
|
||||
// Read the file from disk.
|
||||
let contents = fs::read_file(path)?;
|
||||
|
||||
@@ -244,7 +247,7 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
|
||||
error,
|
||||
} = check_path(
|
||||
path,
|
||||
None,
|
||||
package,
|
||||
&contents,
|
||||
tokens,
|
||||
&locator,
|
||||
@@ -341,11 +344,11 @@ pub fn lint_fix<'a>(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
settings: &Settings,
|
||||
) -> Result<(LinterResult<Vec<Message>>, Cow<'a, str>, usize)> {
|
||||
) -> Result<(LinterResult<Vec<Message>>, Cow<'a, str>, FixTable)> {
|
||||
let mut transformed = Cow::Borrowed(contents);
|
||||
|
||||
// Track the number of fixed errors across iterations.
|
||||
let mut fixed = 0;
|
||||
let mut fixed = FxHashMap::default();
|
||||
|
||||
// As an escape hatch, bail after 100 iterations.
|
||||
let mut iterations = 0;
|
||||
@@ -419,7 +422,9 @@ This indicates a bug in `{}`. If you could open an issue at:
|
||||
if let Some((fixed_contents, applied)) = fix_file(&result.data, &locator) {
|
||||
if iterations < MAX_ITERATIONS {
|
||||
// Count the number of fixed errors.
|
||||
fixed += applied;
|
||||
for (rule, count) in applied {
|
||||
*fixed.entry(rule).or_default() += count;
|
||||
}
|
||||
|
||||
// Store the fixed contents.
|
||||
transformed = Cow::Owned(fixed_contents);
|
||||
|
||||
@@ -63,7 +63,6 @@ ruff_macros::define_rule_mapping!(
|
||||
E701 => rules::pycodestyle::rules::MultipleStatementsOnOneLineColon,
|
||||
E702 => rules::pycodestyle::rules::MultipleStatementsOnOneLineSemicolon,
|
||||
E703 => rules::pycodestyle::rules::UselessSemicolon,
|
||||
E704 => rules::pycodestyle::rules::MultipleStatementsOnOneLineDef,
|
||||
E711 => rules::pycodestyle::rules::NoneComparison,
|
||||
E712 => rules::pycodestyle::rules::TrueFalseComparison,
|
||||
E713 => rules::pycodestyle::rules::NotInTest,
|
||||
@@ -203,7 +202,7 @@ ruff_macros::define_rule_mapping!(
|
||||
// flake8-debugger
|
||||
T100 => rules::flake8_debugger::rules::Debugger,
|
||||
// mccabe
|
||||
C901 => rules::mccabe::rules::FunctionIsTooComplex,
|
||||
C901 => rules::mccabe::rules::ComplexStructure,
|
||||
// flake8-tidy-imports
|
||||
TID251 => rules::flake8_tidy_imports::banned_api::BannedApi,
|
||||
TID252 => rules::flake8_tidy_imports::relative_imports::RelativeImports,
|
||||
@@ -252,10 +251,9 @@ ruff_macros::define_rule_mapping!(
|
||||
YTT302 => rules::flake8_2020::rules::SysVersionCmpStr10,
|
||||
YTT303 => rules::flake8_2020::rules::SysVersionSlice1Referenced,
|
||||
// flake8-simplify
|
||||
SIM115 => rules::flake8_simplify::rules::OpenFileWithContextHandler,
|
||||
SIM101 => rules::flake8_simplify::rules::DuplicateIsinstanceCall,
|
||||
SIM102 => rules::flake8_simplify::rules::NestedIfStatements,
|
||||
SIM103 => rules::flake8_simplify::rules::ReturnBoolConditionDirectly,
|
||||
SIM102 => rules::flake8_simplify::rules::CollapsibleIf,
|
||||
SIM103 => rules::flake8_simplify::rules::NeedlessBool,
|
||||
SIM105 => rules::flake8_simplify::rules::UseContextlibSuppress,
|
||||
SIM107 => rules::flake8_simplify::rules::ReturnInTryExceptFinally,
|
||||
SIM108 => rules::flake8_simplify::rules::UseTernaryOperator,
|
||||
@@ -263,6 +261,8 @@ ruff_macros::define_rule_mapping!(
|
||||
SIM110 => rules::flake8_simplify::rules::ConvertLoopToAny,
|
||||
SIM111 => rules::flake8_simplify::rules::ConvertLoopToAll,
|
||||
SIM112 => rules::flake8_simplify::rules::UseCapitalEnvironmentVariables,
|
||||
SIM114 => rules::flake8_simplify::rules::IfWithSameArms,
|
||||
SIM115 => rules::flake8_simplify::rules::OpenFileWithContextHandler,
|
||||
SIM117 => rules::flake8_simplify::rules::MultipleWithStatements,
|
||||
SIM118 => rules::flake8_simplify::rules::KeyInDict,
|
||||
SIM201 => rules::flake8_simplify::rules::NegateEqualOp,
|
||||
@@ -282,10 +282,10 @@ ruff_macros::define_rule_mapping!(
|
||||
UP003 => rules::pyupgrade::rules::TypeOfPrimitive,
|
||||
UP004 => rules::pyupgrade::rules::UselessObjectInheritance,
|
||||
UP005 => rules::pyupgrade::rules::DeprecatedUnittestAlias,
|
||||
UP006 => rules::pyupgrade::rules::UsePEP585Annotation,
|
||||
UP007 => rules::pyupgrade::rules::UsePEP604Annotation,
|
||||
UP006 => rules::pyupgrade::rules::DeprecatedCollectionType,
|
||||
UP007 => rules::pyupgrade::rules::TypingUnion,
|
||||
UP008 => rules::pyupgrade::rules::SuperCallWithParameters,
|
||||
UP009 => rules::pyupgrade::rules::PEP3120UnnecessaryCodingComment,
|
||||
UP009 => rules::pyupgrade::rules::UTF8EncodingDeclaration,
|
||||
UP010 => rules::pyupgrade::rules::UnnecessaryFutureImport,
|
||||
UP011 => rules::pyupgrade::rules::LRUCacheWithoutParameters,
|
||||
UP012 => rules::pyupgrade::rules::UnnecessaryEncodeUTF8,
|
||||
@@ -344,7 +344,7 @@ ruff_macros::define_rule_mapping!(
|
||||
D401 => rules::pydocstyle::rules::NonImperativeMood,
|
||||
D402 => rules::pydocstyle::rules::NoSignature,
|
||||
D403 => rules::pydocstyle::rules::FirstLineCapitalized,
|
||||
D404 => rules::pydocstyle::rules::NoThisPrefix,
|
||||
D404 => rules::pydocstyle::rules::DocstringStartsWithThis,
|
||||
D405 => rules::pydocstyle::rules::CapitalizeSectionName,
|
||||
D406 => rules::pydocstyle::rules::NewLineAfterSectionName,
|
||||
D407 => rules::pydocstyle::rules::DashedUnderlineAfterSection,
|
||||
@@ -354,12 +354,12 @@ ruff_macros::define_rule_mapping!(
|
||||
D411 => rules::pydocstyle::rules::BlankLineBeforeSection,
|
||||
D412 => rules::pydocstyle::rules::NoBlankLinesBetweenHeaderAndContent,
|
||||
D413 => rules::pydocstyle::rules::BlankLineAfterLastSection,
|
||||
D414 => rules::pydocstyle::rules::NonEmptySection,
|
||||
D414 => rules::pydocstyle::rules::EmptyDocstringSection,
|
||||
D415 => rules::pydocstyle::rules::EndsInPunctuation,
|
||||
D416 => rules::pydocstyle::rules::SectionNameEndsInColon,
|
||||
D417 => rules::pydocstyle::rules::DocumentAllArguments,
|
||||
D418 => rules::pydocstyle::rules::SkipDocstring,
|
||||
D419 => rules::pydocstyle::rules::NonEmpty,
|
||||
D417 => rules::pydocstyle::rules::UndocumentedParam,
|
||||
D418 => rules::pydocstyle::rules::OverloadWithDocstring,
|
||||
D419 => rules::pydocstyle::rules::EmptyDocstring,
|
||||
// pep8-naming
|
||||
N801 => rules::pep8_naming::rules::InvalidClassName,
|
||||
N802 => rules::pep8_naming::rules::InvalidFunctionName,
|
||||
@@ -447,6 +447,8 @@ ruff_macros::define_rule_mapping!(
|
||||
EM103 => rules::flake8_errmsg::rules::DotFormatInException,
|
||||
// flake8-pyi
|
||||
PYI001 => rules::flake8_pyi::rules::PrefixTypeParams,
|
||||
PYI007 => rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
|
||||
PYI008 => rules::flake8_pyi::rules::UnrecognizedPlatformName,
|
||||
// flake8-pytest-style
|
||||
PT001 => rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
|
||||
PT002 => rules::flake8_pytest_style::rules::FixturePositionalArgs,
|
||||
@@ -474,11 +476,11 @@ ruff_macros::define_rule_mapping!(
|
||||
PT025 => rules::flake8_pytest_style::rules::ErroneousUseFixturesOnFixture,
|
||||
PT026 => rules::flake8_pytest_style::rules::UseFixturesWithoutParameters,
|
||||
// flake8-pie
|
||||
PIE790 => rules::flake8_pie::rules::NoUnnecessaryPass,
|
||||
PIE790 => rules::flake8_pie::rules::UnnecessaryPass,
|
||||
PIE794 => rules::flake8_pie::rules::DupeClassFieldDefinitions,
|
||||
PIE796 => rules::flake8_pie::rules::PreferUniqueEnums,
|
||||
PIE800 => rules::flake8_pie::rules::NoUnnecessarySpread,
|
||||
PIE804 => rules::flake8_pie::rules::NoUnnecessaryDictKwargs,
|
||||
PIE800 => rules::flake8_pie::rules::UnnecessarySpread,
|
||||
PIE804 => rules::flake8_pie::rules::UnnecessaryDictKwargs,
|
||||
PIE807 => rules::flake8_pie::rules::PreferListBuiltin,
|
||||
PIE810 => rules::flake8_pie::rules::SingleStartsEndsWith,
|
||||
// flake8-commas
|
||||
@@ -554,6 +556,10 @@ ruff_macros::define_rule_mapping!(
|
||||
RUF004 => rules::ruff::rules::KeywordArgumentBeforeStarArgument,
|
||||
RUF005 => rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
|
||||
RUF100 => rules::ruff::rules::UnusedNOQA,
|
||||
// flake8-django
|
||||
DJ001 => rules::flake8_django::rules::ModelStringFieldNullable,
|
||||
DJ008 => rules::flake8_django::rules::ModelDunderStr,
|
||||
DJ013 => rules::flake8_django::rules::ReceiverDecoratorChecker,
|
||||
);
|
||||
|
||||
#[derive(EnumIter, Debug, PartialEq, Eq, RuleNamespace)]
|
||||
@@ -613,6 +619,9 @@ pub enum Linter {
|
||||
/// [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
#[prefix = "T10"]
|
||||
Flake8Debugger,
|
||||
/// [flake8-django](https://pypi.org/project/flake8-django/)
|
||||
#[prefix = "DJ"]
|
||||
Flake8Django,
|
||||
/// [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
#[prefix = "EM"]
|
||||
Flake8ErrMsg,
|
||||
@@ -751,7 +760,7 @@ impl Rule {
|
||||
| Rule::LineTooLong
|
||||
| Rule::MixedSpacesAndTabs
|
||||
| Rule::NoNewLineAtEndOfFile
|
||||
| Rule::PEP3120UnnecessaryCodingComment
|
||||
| Rule::UTF8EncodingDeclaration
|
||||
| Rule::ShebangMissingExecutableFile
|
||||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNewline
|
||||
@@ -774,7 +783,6 @@ impl Rule {
|
||||
| Rule::TrailingCommaOnBareTupleProhibited
|
||||
| Rule::MultipleStatementsOnOneLineColon
|
||||
| Rule::UselessSemicolon
|
||||
| Rule::MultipleStatementsOnOneLineDef
|
||||
| Rule::MultipleStatementsOnOneLineSemicolon
|
||||
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
|
||||
Rule::IOError => &LintSource::Io,
|
||||
@@ -898,7 +906,7 @@ mod tests {
|
||||
for rule in Rule::iter() {
|
||||
let code = rule.code();
|
||||
let (linter, rest) =
|
||||
Linter::parse_code(code).unwrap_or_else(|| panic!("couldn't parse {:?}", code));
|
||||
Linter::parse_code(code).unwrap_or_else(|| panic!("couldn't parse {code:?}"));
|
||||
assert_eq!(code, format!("{}{rest}", linter.common_prefix()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,18 +35,12 @@ define_violation!(
|
||||
/// ## References
|
||||
/// * [B608: Test for SQL injection](https://bandit.readthedocs.io/en/latest/plugins/b608_hardcoded_sql_expressions.html)
|
||||
/// * [psycopg3: Server-side binding](https://www.psycopg.org/psycopg3/docs/basic/from_pg2.html#server-side-binding)
|
||||
pub struct HardcodedSQLExpression {
|
||||
pub string: String,
|
||||
}
|
||||
pub struct HardcodedSQLExpression;
|
||||
);
|
||||
impl Violation for HardcodedSQLExpression {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let HardcodedSQLExpression { string } = self;
|
||||
format!(
|
||||
"Possible SQL injection vector through string-based query construction: \"{}\"",
|
||||
string.escape_debug()
|
||||
)
|
||||
format!("Possible SQL injection vector through string-based query construction")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +96,7 @@ pub fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
|
||||
match unparse_string_format_expression(checker, expr) {
|
||||
Some(string) if matches_sql_statement(&string) => {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
HardcodedSQLExpression { string },
|
||||
HardcodedSQLExpression,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT %s FROM table\" % (var,)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 2
|
||||
column: 9
|
||||
@@ -14,8 +13,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT var FROM \" + table"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 9
|
||||
@@ -25,8 +23,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT \" + val + \" FROM \" + table"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 4
|
||||
column: 9
|
||||
@@ -36,8 +33,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT {} FROM table;\".format(var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 9
|
||||
@@ -47,8 +43,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"SELECT * FROM table WHERE var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 9
|
||||
@@ -58,8 +53,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM table WHERE var = %s\" % (var,)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 8
|
||||
column: 9
|
||||
@@ -69,8 +63,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM table WHERE VAR = \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 9
|
||||
@@ -80,8 +73,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM \" + table + \"WHERE var = \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 10
|
||||
column: 9
|
||||
@@ -91,8 +83,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"DELETE FROM table WHERE var = {}\".format(var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 11
|
||||
column: 9
|
||||
@@ -102,8 +93,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"DELETE FROM table WHERE var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 12
|
||||
column: 10
|
||||
@@ -113,8 +103,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"INSERT INTO table VALUES (%s)\" % (var,)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 14
|
||||
column: 10
|
||||
@@ -124,8 +113,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"INSERT INTO TABLE VALUES (\" + var + \")\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 15
|
||||
column: 10
|
||||
@@ -135,8 +123,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"INSERT INTO {} VALUES ({})\".format(table, var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
@@ -146,8 +133,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"INSERT INTO {table} VALUES var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 17
|
||||
column: 10
|
||||
@@ -157,8 +143,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"UPDATE %s SET var = %s\" % (table, var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 19
|
||||
column: 10
|
||||
@@ -168,8 +153,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"UPDATE \" + table + \" SET var = \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 20
|
||||
column: 10
|
||||
@@ -179,8 +163,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"UPDATE {} SET var = {}\".format(table, var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 21
|
||||
column: 10
|
||||
@@ -190,8 +173,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"UPDATE {table} SET var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 22
|
||||
column: 10
|
||||
@@ -201,8 +183,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select %s from table\" % (var,)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 24
|
||||
column: 10
|
||||
@@ -212,8 +193,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select var from \" + table"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 25
|
||||
column: 10
|
||||
@@ -223,8 +203,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select \" + val + \" from \" + table"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 26
|
||||
column: 10
|
||||
@@ -234,8 +213,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"select {} from table;\".format(var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 27
|
||||
column: 10
|
||||
@@ -245,8 +223,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"select * from table where var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 28
|
||||
column: 10
|
||||
@@ -256,8 +233,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from table where var = %s\" % (var,)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 30
|
||||
column: 10
|
||||
@@ -267,8 +243,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from table where var = \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 31
|
||||
column: 10
|
||||
@@ -278,8 +253,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from \" + table + \"where var = \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 32
|
||||
column: 10
|
||||
@@ -289,8 +263,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"delete from table where var = {}\".format(var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 33
|
||||
column: 10
|
||||
@@ -300,8 +273,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"delete from table where var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 34
|
||||
column: 10
|
||||
@@ -311,8 +283,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"insert into table values (%s)\" % (var,)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 36
|
||||
column: 10
|
||||
@@ -322,8 +293,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"insert into table values (\" + var + \")\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 37
|
||||
column: 10
|
||||
@@ -333,8 +303,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"insert into {} values ({})\".format(table, var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 38
|
||||
column: 10
|
||||
@@ -344,8 +313,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"insert into {table} values var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 39
|
||||
column: 10
|
||||
@@ -355,8 +323,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"update %s set var = %s\" % (table, var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 41
|
||||
column: 10
|
||||
@@ -366,8 +333,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"update \" + table + \" set var = \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 42
|
||||
column: 10
|
||||
@@ -377,8 +343,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"update {} set var = {}\".format(table, var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 43
|
||||
column: 10
|
||||
@@ -388,8 +353,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"update {table} set var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 44
|
||||
column: 10
|
||||
@@ -399,8 +363,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"\\n SELECT *\\n FROM table\\n WHERE var = %s\\n \" % var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 48
|
||||
column: 11
|
||||
@@ -410,8 +373,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"\\n SELECT *\\n FROM TABLE\\n WHERE var =\\n \" + var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 55
|
||||
column: 11
|
||||
@@ -421,8 +383,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"\\n SELECT *\\n FROM table\\n WHERE var = {}\\n \".format(var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 62
|
||||
column: 11
|
||||
@@ -432,8 +393,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"\\n SELECT *\\n FROM table\\n WHERE var = {var}\\n \""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 69
|
||||
column: 11
|
||||
@@ -443,8 +403,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"SELECT *FROM tableWHERE var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 77
|
||||
column: 8
|
||||
@@ -454,8 +413,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT * FROM table WHERE var = %s\" % var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 83
|
||||
column: 25
|
||||
@@ -465,8 +423,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "f\"SELECT * FROM table WHERE var = {var}\""
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 84
|
||||
column: 25
|
||||
@@ -476,8 +433,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT * FROM table WHERE var = {}\".format(var)"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 85
|
||||
column: 25
|
||||
@@ -487,8 +443,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
HardcodedSQLExpression:
|
||||
string: "\"SELECT * FROM table WHERE var = %s\" % var"
|
||||
HardcodedSQLExpression: ~
|
||||
location:
|
||||
row: 86
|
||||
column: 29
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
use libcst_native::{
|
||||
Arg, AssignEqual, Call, Codegen, CodegenState, Dict, DictComp, DictElement, Element, Expr,
|
||||
Expression, LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name,
|
||||
ParenthesizableWhitespace, RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp,
|
||||
SimpleString, SimpleWhitespace, Tuple,
|
||||
Arg, AssignEqual, AssignTargetExpression, Call, Codegen, CodegenState, CompFor, Dict, DictComp,
|
||||
DictElement, Element, Expr, Expression, GeneratorExp, LeftCurlyBrace, LeftParen,
|
||||
LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace, RightCurlyBrace,
|
||||
RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace, Tuple,
|
||||
};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -78,6 +79,7 @@ pub fn fix_unnecessary_generator_set(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &rustpython_parser::ast::Expr,
|
||||
parent: Option<&rustpython_parser::ast::Expr>,
|
||||
) -> Result<Fix> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
@@ -112,8 +114,18 @@ pub fn fix_unnecessary_generator_set(
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
let mut content = state.to_string();
|
||||
|
||||
// If the expression is embedded in an f-string, surround it with spaces to avoid
|
||||
// syntax errors.
|
||||
if let Some(parent_element) = parent {
|
||||
if let &rustpython_parser::ast::ExprKind::FormattedValue { .. } = &parent_element.node {
|
||||
content = format!(" {content} ");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
@@ -125,6 +137,7 @@ pub fn fix_unnecessary_generator_dict(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &rustpython_parser::ast::Expr,
|
||||
parent: Option<&rustpython_parser::ast::Expr>,
|
||||
) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(module_text)?;
|
||||
@@ -175,8 +188,18 @@ pub fn fix_unnecessary_generator_dict(
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
let mut content = state.to_string();
|
||||
|
||||
// If the expression is embedded in an f-string, surround it with spaces to avoid
|
||||
// syntax errors.
|
||||
if let Some(parent_element) = parent {
|
||||
if let &rustpython_parser::ast::ExprKind::FormattedValue { .. } = &parent_element.node {
|
||||
content = format!(" {content} ");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
@@ -742,6 +765,7 @@ pub fn fix_unnecessary_call_around_sorted(
|
||||
if outer_name.value == "list" {
|
||||
body.value = Expression::Call(inner_call.clone());
|
||||
} else {
|
||||
// If the `reverse` argument is used
|
||||
let args = if inner_call.args.iter().any(|arg| {
|
||||
matches!(
|
||||
arg.keyword,
|
||||
@@ -751,7 +775,46 @@ pub fn fix_unnecessary_call_around_sorted(
|
||||
})
|
||||
)
|
||||
}) {
|
||||
inner_call.args.clone()
|
||||
// Negate the `reverse` argument
|
||||
inner_call
|
||||
.args
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|mut arg| {
|
||||
if matches!(
|
||||
arg.keyword,
|
||||
Some(Name {
|
||||
value: "reverse",
|
||||
..
|
||||
})
|
||||
) {
|
||||
if let Expression::Name(ref val) = arg.value {
|
||||
if val.value == "True" {
|
||||
// TODO: even better would be to drop the argument, as False is the default
|
||||
arg.value = Expression::Name(Box::new(Name {
|
||||
value: "False",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
arg
|
||||
} else if val.value == "False" {
|
||||
arg.value = Expression::Name(Box::new(Name {
|
||||
value: "True",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
arg
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.collect_vec()
|
||||
} else {
|
||||
let mut args = inner_call.args.clone();
|
||||
args.push(Arg {
|
||||
@@ -802,6 +865,45 @@ pub fn fix_unnecessary_call_around_sorted(
|
||||
))
|
||||
}
|
||||
|
||||
/// (C414) Convert `sorted(list(foo))` to `sorted(foo)`
|
||||
pub fn fix_unnecessary_double_cast_or_process(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &rustpython_parser::ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(module_text)?;
|
||||
let body = match_expr(&mut tree)?;
|
||||
let mut outer_call = match_call(body)?;
|
||||
let inner_call = match &outer_call.args[..] {
|
||||
[arg] => {
|
||||
if let Expression::Call(call) = &arg.value {
|
||||
&call.args
|
||||
} else {
|
||||
bail!("Expected Expression::Call ");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
bail!("Expected one argument in outer function call");
|
||||
}
|
||||
};
|
||||
|
||||
outer_call.args = inner_call.clone();
|
||||
|
||||
let mut state = CodegenState {
|
||||
default_newline: stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
Ok(Fix::replacement(
|
||||
state.to_string(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C416) Convert `[i for i in x]` to `list(x)`.
|
||||
pub fn fix_unnecessary_comprehension(
|
||||
locator: &Locator,
|
||||
@@ -875,3 +977,173 @@ pub fn fix_unnecessary_comprehension(
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// (C417) Convert `map(lambda x: x * 2, bar)` to `(x * 2 for x in bar)`.
|
||||
pub fn fix_unnecessary_map(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &rustpython_parser::ast::Expr,
|
||||
parent: Option<&rustpython_parser::ast::Expr>,
|
||||
kind: &str,
|
||||
) -> Result<Fix> {
|
||||
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
|
||||
let mut tree = match_module(module_text)?;
|
||||
let mut body = match_expr(&mut tree)?;
|
||||
let call = match_call(body)?;
|
||||
let arg = match_arg(call)?;
|
||||
|
||||
let (args, lambda_func) = match &arg.value {
|
||||
Expression::Call(outer_call) => {
|
||||
let inner_lambda = outer_call.args.first().unwrap().value.clone();
|
||||
match &inner_lambda {
|
||||
Expression::Lambda(..) => (outer_call.args.clone(), inner_lambda),
|
||||
_ => {
|
||||
bail!("Expected a lambda function")
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::Lambda(..) => (call.args.clone(), arg.value.clone()),
|
||||
_ => {
|
||||
bail!("Expected a lambda or call")
|
||||
}
|
||||
};
|
||||
|
||||
let Expression::Lambda(func_body) = &lambda_func else {
|
||||
bail!("Expected a lambda")
|
||||
};
|
||||
|
||||
if args.len() == 2 {
|
||||
if func_body.params.params.iter().any(|f| f.default.is_some()) {
|
||||
bail!("Currently not supporting default values");
|
||||
}
|
||||
|
||||
let mut args_str = func_body
|
||||
.params
|
||||
.params
|
||||
.iter()
|
||||
.map(|f| f.name.value)
|
||||
.join(", ");
|
||||
if args_str.is_empty() {
|
||||
args_str = "_".to_string();
|
||||
}
|
||||
|
||||
let compfor = Box::new(CompFor {
|
||||
target: AssignTargetExpression::Name(Box::new(Name {
|
||||
value: args_str.as_str(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
})),
|
||||
iter: args.last().unwrap().value.clone(),
|
||||
ifs: vec![],
|
||||
inner_for_in: None,
|
||||
asynchronous: None,
|
||||
whitespace_before: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
|
||||
whitespace_after_for: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(
|
||||
" ",
|
||||
)),
|
||||
whitespace_before_in: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(
|
||||
" ",
|
||||
)),
|
||||
whitespace_after_in: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
|
||||
});
|
||||
|
||||
match kind {
|
||||
"generator" => {
|
||||
body.value = Expression::GeneratorExp(Box::new(GeneratorExp {
|
||||
elt: func_body.body.clone(),
|
||||
for_in: compfor,
|
||||
lpar: vec![LeftParen::default()],
|
||||
rpar: vec![RightParen::default()],
|
||||
}));
|
||||
}
|
||||
"list" => {
|
||||
body.value = Expression::ListComp(Box::new(ListComp {
|
||||
elt: func_body.body.clone(),
|
||||
for_in: compfor,
|
||||
lbracket: LeftSquareBracket::default(),
|
||||
rbracket: RightSquareBracket::default(),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
"set" => {
|
||||
body.value = Expression::SetComp(Box::new(SetComp {
|
||||
elt: func_body.body.clone(),
|
||||
for_in: compfor,
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
lbrace: LeftCurlyBrace::default(),
|
||||
rbrace: RightCurlyBrace::default(),
|
||||
}));
|
||||
}
|
||||
"dict" => {
|
||||
let (key, value) = if let Expression::Tuple(tuple) = func_body.body.as_ref() {
|
||||
if tuple.elements.len() != 2 {
|
||||
bail!("Expected two elements")
|
||||
}
|
||||
|
||||
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
|
||||
bail!(
|
||||
"Expected tuple to contain a key as the first element"
|
||||
);
|
||||
};
|
||||
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
|
||||
bail!(
|
||||
"Expected tuple to contain a key as the second element"
|
||||
);
|
||||
};
|
||||
|
||||
(key, value)
|
||||
} else {
|
||||
bail!("Expected tuple for dict comprehension")
|
||||
};
|
||||
|
||||
body.value = Expression::DictComp(Box::new(DictComp {
|
||||
for_in: compfor,
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
key: Box::new(key.clone()),
|
||||
value: Box::new(value.clone()),
|
||||
lbrace: LeftCurlyBrace::default(),
|
||||
rbrace: RightCurlyBrace::default(),
|
||||
whitespace_before_colon: ParenthesizableWhitespace::default(),
|
||||
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
|
||||
SimpleWhitespace(" "),
|
||||
),
|
||||
}));
|
||||
}
|
||||
_ => {
|
||||
bail!("Expected generator, list, set or dict");
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = CodegenState {
|
||||
default_newline: stylist.line_ending(),
|
||||
default_indent: stylist.indentation(),
|
||||
..CodegenState::default()
|
||||
};
|
||||
tree.codegen(&mut state);
|
||||
|
||||
let mut content = state.to_string();
|
||||
|
||||
// If the expression is embedded in an f-string, surround it with spaces to avoid
|
||||
// syntax errors.
|
||||
if kind == "set" || kind == "dict" {
|
||||
if let Some(parent_element) = parent {
|
||||
if let &rustpython_parser::ast::ExprKind::FormattedValue { .. } =
|
||||
&parent_element.node
|
||||
{
|
||||
content = format!(" {content} ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Fix::replacement(
|
||||
content,
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
))
|
||||
} else {
|
||||
bail!("Should have two arguments");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,29 @@ use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `list` or `reversed` calls around `sorted`
|
||||
/// calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It is unnecessary to use `list` around `sorted`, as the latter already
|
||||
/// returns a list.
|
||||
///
|
||||
/// It is also unnecessary to use `reversed` around `sorted`, as the latter
|
||||
/// has a `reverse` argument that can be used in lieu of an additional
|
||||
/// `reversed` call.
|
||||
///
|
||||
/// In both cases, it's clearer to avoid the redundant call.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// reversed(sorted(iterable))
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// sorted(iterable, reverse=True)
|
||||
/// ```
|
||||
pub struct UnnecessaryCallAroundSorted {
|
||||
pub func: String,
|
||||
}
|
||||
|
||||
@@ -1,24 +1,66 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use super::helpers;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `list`, `reversed`, `set`, `sorted`, and `tuple`
|
||||
/// call within `list`, `set`, `sorted`, and `tuple` calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It's unnecessary to double-cast or double-process iterables by wrapping
|
||||
/// the listed functions within an additional `list`, `set`, `sorted`, or
|
||||
/// `tuple` call. Doing so is redundant and can be confusing for readers.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// list(tuple(iterable))
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// list(iterable)
|
||||
/// ```
|
||||
///
|
||||
/// This rule applies to a variety of functions, including `list`, `reversed`,
|
||||
/// `set`, `sorted`, and `tuple`. For example:
|
||||
/// * Instead of `list(list(iterable))`, use `list(iterable)`.
|
||||
/// * Instead of `list(tuple(iterable))`, use `list(iterable)`.
|
||||
/// * Instead of `tuple(list(iterable))`, use `tuple(iterable)`.
|
||||
/// * Instead of `tuple(tuple(iterable))`, use `tuple(iterable)`.
|
||||
/// * Instead of `set(set(iterable))`, use `set(iterable)`.
|
||||
/// * Instead of `set(list(iterable))`, use `set(iterable)`.
|
||||
/// * Instead of `set(tuple(iterable))`, use `set(iterable)`.
|
||||
/// * Instead of `set(sorted(iterable))`, use `set(iterable)`.
|
||||
/// * Instead of `set(reversed(iterable))`, use `set(iterable)`.
|
||||
/// * Instead of `sorted(list(iterable))`, use `sorted(iterable)`.
|
||||
/// * Instead of `sorted(tuple(iterable))`, use `sorted(iterable)`.
|
||||
/// * Instead of `sorted(sorted(iterable))`, use `sorted(iterable)`.
|
||||
/// * Instead of `sorted(reversed(iterable))`, use `sorted(iterable)`.
|
||||
pub struct UnnecessaryDoubleCastOrProcess {
|
||||
pub inner: String,
|
||||
pub outer: String,
|
||||
}
|
||||
);
|
||||
impl Violation for UnnecessaryDoubleCastOrProcess {
|
||||
impl AlwaysAutofixableViolation for UnnecessaryDoubleCastOrProcess {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnnecessaryDoubleCastOrProcess { inner, outer } = self;
|
||||
format!("Unnecessary `{inner}` call within `{outer}()`")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let UnnecessaryDoubleCastOrProcess { inner, .. } = self;
|
||||
format!("Remove the inner `{inner}` call")
|
||||
}
|
||||
}
|
||||
|
||||
/// C414
|
||||
@@ -28,7 +70,7 @@ pub fn unnecessary_double_cast_or_process(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) {
|
||||
fn diagnostic(inner: &str, outer: &str, location: Range) -> Diagnostic {
|
||||
fn create_diagnostic(inner: &str, outer: &str, location: Range) -> Diagnostic {
|
||||
Diagnostic::new(
|
||||
UnnecessaryDoubleCastOrProcess {
|
||||
inner: inner.to_string(),
|
||||
@@ -63,27 +105,23 @@ pub fn unnecessary_double_cast_or_process(
|
||||
}
|
||||
|
||||
// Ex) set(tuple(...))
|
||||
if (outer == "set" || outer == "sorted")
|
||||
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted")
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic(inner, outer, Range::from_located(expr)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) list(tuple(...))
|
||||
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic(inner, outer, Range::from_located(expr)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) set(set(...))
|
||||
if outer == "set" && inner == "set" {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic(inner, outer, Range::from_located(expr)));
|
||||
if ((outer == "set" || outer == "sorted")
|
||||
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted"))
|
||||
|| (outer == "set" && inner == "set")
|
||||
|| ((outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple"))
|
||||
{
|
||||
let mut diagnostic = create_diagnostic(inner, outer, Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Ok(fix) = fixes::fix_unnecessary_double_cast_or_process(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
expr,
|
||||
) {
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,24 @@ use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary generators that can be rewritten as `dict`
|
||||
/// comprehensions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It is unnecessary to use `dict` around a generator expression, since
|
||||
/// there are equivalent comprehensions for these types. Using a
|
||||
/// comprehension is clearer and more idiomatic.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// dict((x, f(x)) for x in foo)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// {x: f(x) for x in foo}
|
||||
/// ```
|
||||
pub struct UnnecessaryGeneratorDict;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for UnnecessaryGeneratorDict {
|
||||
@@ -27,6 +45,7 @@ impl AlwaysAutofixableViolation for UnnecessaryGeneratorDict {
|
||||
pub fn unnecessary_generator_dict(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
parent: Option<&Expr>,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
@@ -44,6 +63,7 @@ pub fn unnecessary_generator_dict(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
expr,
|
||||
parent,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
diagnostic.amend(fix);
|
||||
|
||||
@@ -10,6 +10,24 @@ use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary generators that can be rewritten as `list`
|
||||
/// comprehensions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It is unnecessary to use `list` around a generator expression, since
|
||||
/// there are equivalent comprehensions for these types. Using a
|
||||
/// comprehension is clearer and more idiomatic.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// list(f(x) for x in foo)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// [f(x) for x in foo]
|
||||
/// ```
|
||||
pub struct UnnecessaryGeneratorList;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for UnnecessaryGeneratorList {
|
||||
|
||||
@@ -10,6 +10,24 @@ use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary generators that can be rewritten as `set`
|
||||
/// comprehensions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// It is unnecessary to use `set` around a generator expression, since
|
||||
/// there are equivalent comprehensions for these types. Using a
|
||||
/// comprehension is clearer and more idiomatic.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// set(f(x) for x in foo)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// {f(x) for x in foo}
|
||||
/// ```
|
||||
pub struct UnnecessaryGeneratorSet;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for UnnecessaryGeneratorSet {
|
||||
@@ -27,6 +45,7 @@ impl AlwaysAutofixableViolation for UnnecessaryGeneratorSet {
|
||||
pub fn unnecessary_generator_set(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
parent: Option<&Expr>,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
@@ -40,7 +59,12 @@ pub fn unnecessary_generator_set(
|
||||
if let ExprKind::GeneratorExp { .. } = argument {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
match fixes::fix_unnecessary_generator_set(checker.locator, checker.stylist, expr) {
|
||||
match fixes::fix_unnecessary_generator_set(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
expr,
|
||||
parent,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use log::error;
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use super::helpers;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
use crate::rules::flake8_comprehensions::fixes;
|
||||
use crate::violation::{AutofixKind, Availability, Violation};
|
||||
|
||||
use super::helpers;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary `map` calls with `lambda` functions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `map(func, iterable)` when `func` is a `lambda` is slower than
|
||||
/// using a generator expression or a comprehension, as the latter approach
|
||||
/// avoids the function call overhead, in addition to being more readable.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// map(lambda x: x + 1, iterable)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// (x + 1 for x in iterable)
|
||||
/// ```
|
||||
///
|
||||
/// This rule also applies to `map` calls within `list`, `set`, and `dict`
|
||||
/// calls. For example:
|
||||
/// * Instead of `list(map(lambda num: num * 2, nums))`, use
|
||||
/// `[num * 2 for num in nums]`.
|
||||
/// * Instead of `set(map(lambda num: num % 2 == 0, nums))`, use
|
||||
/// `{num % 2 == 0 for num in nums}`.
|
||||
/// * Instead of `dict(map(lambda v: (v, v ** 2), values))`, use
|
||||
/// `{v: v ** 2 for v in values}`.
|
||||
pub struct UnnecessaryMap {
|
||||
pub obj_type: String,
|
||||
}
|
||||
);
|
||||
impl Violation for UnnecessaryMap {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnnecessaryMap { obj_type } = self;
|
||||
@@ -22,11 +54,27 @@ impl Violation for UnnecessaryMap {
|
||||
format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)")
|
||||
}
|
||||
}
|
||||
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
Some(|UnnecessaryMap { obj_type }| {
|
||||
if obj_type == "generator" {
|
||||
format!("Replace `map` using a generator expression")
|
||||
} else {
|
||||
format!("Replace `map` using a `{obj_type}` comprehension")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// C417
|
||||
pub fn unnecessary_map(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
||||
fn diagnostic(kind: &str, location: Range) -> Diagnostic {
|
||||
pub fn unnecessary_map(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
parent: Option<&Expr>,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) {
|
||||
fn create_diagnostic(kind: &str, location: Range) -> Diagnostic {
|
||||
Diagnostic::new(
|
||||
UnnecessaryMap {
|
||||
obj_type: kind.to_string(),
|
||||
@@ -44,10 +92,34 @@ pub fn unnecessary_map(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[
|
||||
return;
|
||||
}
|
||||
|
||||
// Exclude the parent if already matched by other arms
|
||||
if let Some(parent) = parent {
|
||||
if let ExprKind::Call { func: f, .. } = &parent.node {
|
||||
if let Some(id_parent) = helpers::function_name(f) {
|
||||
if id_parent == "dict" || id_parent == "set" || id_parent == "list" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic("generator", Range::from_located(expr)));
|
||||
let mut diagnostic = create_diagnostic("generator", Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
match fixes::fix_unnecessary_map(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
expr,
|
||||
parent,
|
||||
"generator",
|
||||
) {
|
||||
Ok(fix) => {
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Err(e) => error!("Failed to generate fix: {e}"),
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
"list" | "set" => {
|
||||
@@ -57,13 +129,29 @@ pub fn unnecessary_map(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[
|
||||
|
||||
if let Some(arg) = args.first() {
|
||||
if let ExprKind::Call { func, args, .. } = &arg.node {
|
||||
if args.len() != 2 {
|
||||
return;
|
||||
}
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else {
|
||||
return;
|
||||
};
|
||||
if let ExprKind::Lambda { .. } = argument {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic(id, Range::from_located(expr)));
|
||||
let mut diagnostic = create_diagnostic(id, Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
match fixes::fix_unnecessary_map(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
expr,
|
||||
parent,
|
||||
id,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Err(e) => error!("Failed to generate fix: {e}"),
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,9 +169,22 @@ pub fn unnecessary_map(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[
|
||||
if let ExprKind::Lambda { body, .. } = &argument {
|
||||
if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2)
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic(id, Range::from_located(expr)));
|
||||
let mut diagnostic = create_diagnostic(id, Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
match fixes::fix_unnecessary_map(
|
||||
checker.locator,
|
||||
checker.stylist,
|
||||
expr,
|
||||
parent,
|
||||
id,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
diagnostic.amend(fix);
|
||||
}
|
||||
Err(e) => error!("Failed to generate fix: {e}"),
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_comprehensions/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
@@ -40,4 +40,58 @@ expression: diagnostics
|
||||
row: 4
|
||||
column: 1
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryGeneratorSet: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 7
|
||||
end_location:
|
||||
row: 5
|
||||
column: 48
|
||||
fix:
|
||||
content:
|
||||
- " {a if a < 6 else 0 for a in range(3)} "
|
||||
location:
|
||||
row: 5
|
||||
column: 7
|
||||
end_location:
|
||||
row: 5
|
||||
column: 48
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryGeneratorSet: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 16
|
||||
end_location:
|
||||
row: 6
|
||||
column: 57
|
||||
fix:
|
||||
content:
|
||||
- "{a if a < 6 else 0 for a in range(3)}"
|
||||
location:
|
||||
row: 6
|
||||
column: 16
|
||||
end_location:
|
||||
row: 6
|
||||
column: 57
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryGeneratorSet: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 15
|
||||
end_location:
|
||||
row: 7
|
||||
column: 39
|
||||
fix:
|
||||
content:
|
||||
- " {a for a in range(3)} "
|
||||
location:
|
||||
row: 7
|
||||
column: 15
|
||||
end_location:
|
||||
row: 7
|
||||
column: 39
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_comprehensions/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
@@ -40,4 +40,40 @@ expression: diagnostics
|
||||
row: 4
|
||||
column: 1
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryGeneratorDict: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 7
|
||||
end_location:
|
||||
row: 6
|
||||
column: 37
|
||||
fix:
|
||||
content:
|
||||
- " {x: x for x in range(3)} "
|
||||
location:
|
||||
row: 6
|
||||
column: 7
|
||||
end_location:
|
||||
row: 6
|
||||
column: 37
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryGeneratorDict: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 15
|
||||
end_location:
|
||||
row: 7
|
||||
column: 45
|
||||
fix:
|
||||
content:
|
||||
- " {x: x for x in range(3)} "
|
||||
location:
|
||||
row: 7
|
||||
column: 15
|
||||
end_location:
|
||||
row: 7
|
||||
column: 45
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_comprehensions/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
@@ -70,7 +70,7 @@ expression: diagnostics
|
||||
column: 33
|
||||
fix:
|
||||
content:
|
||||
- "sorted(x, reverse=True)"
|
||||
- "sorted(x, reverse=False)"
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
@@ -78,4 +78,61 @@ expression: diagnostics
|
||||
row: 6
|
||||
column: 33
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryCallAroundSorted:
|
||||
func: reversed
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 50
|
||||
fix:
|
||||
content:
|
||||
- "sorted(x, key=lambda e: e, reverse=False)"
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 50
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryCallAroundSorted:
|
||||
func: reversed
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 50
|
||||
fix:
|
||||
content:
|
||||
- "sorted(x, reverse=False, key=lambda e: e)"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 50
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryCallAroundSorted:
|
||||
func: reversed
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 34
|
||||
fix:
|
||||
content:
|
||||
- "sorted(x, reverse=True)"
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 34
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_comprehensions/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
@@ -12,7 +12,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 2
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- list(x)
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 13
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -24,7 +32,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- list(x)
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -36,7 +52,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- tuple(x)
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -48,7 +72,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 5
|
||||
column: 15
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- tuple(x)
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -60,7 +92,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 6
|
||||
column: 11
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- set(x)
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 11
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -72,7 +112,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 7
|
||||
column: 12
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- set(x)
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 12
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -84,7 +132,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 8
|
||||
column: 13
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- set(x)
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 13
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -96,7 +152,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 9
|
||||
column: 14
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- set(x)
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 14
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -108,7 +172,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 10
|
||||
column: 16
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- set(x)
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 16
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -120,7 +192,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 11
|
||||
column: 15
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- sorted(x)
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -132,7 +212,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 12
|
||||
column: 16
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- sorted(x)
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 16
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -144,7 +232,15 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 13
|
||||
column: 17
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- sorted(x)
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 17
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
@@ -156,6 +252,37 @@ expression: diagnostics
|
||||
end_location:
|
||||
row: 14
|
||||
column: 19
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- sorted(x)
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 19
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
inner: list
|
||||
outer: tuple
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 1
|
||||
fix:
|
||||
content:
|
||||
- tuple(
|
||||
- " [x, 3, \"hell\"\\"
|
||||
- " \"o\"]"
|
||||
- " )"
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 1
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -1,93 +1,244 @@
|
||||
---
|
||||
source: src/rules/flake8_comprehensions/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 2
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
row: 3
|
||||
column: 26
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- (x + 1 for x in nums)
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 26
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 3
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
row: 4
|
||||
column: 27
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- (str(x) for x in nums)
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 27
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: list
|
||||
location:
|
||||
row: 4
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
row: 5
|
||||
column: 32
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 31
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- "[x * 2 for x in nums]"
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 32
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: set
|
||||
location:
|
||||
row: 5
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
row: 6
|
||||
column: 36
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 5
|
||||
column: 35
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- "{x % 2 == 0 for x in nums}"
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 36
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: dict
|
||||
location:
|
||||
row: 6
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
row: 7
|
||||
column: 36
|
||||
fix: ~
|
||||
fix:
|
||||
content:
|
||||
- "{v: v**2 for v in nums}"
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 36
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 6
|
||||
column: 5
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
row: 8
|
||||
column: 26
|
||||
fix:
|
||||
content:
|
||||
- "(\"const\" for _ in nums)"
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 26
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 24
|
||||
fix:
|
||||
content:
|
||||
- (3.0 for _ in nums)
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 24
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 10
|
||||
column: 12
|
||||
end_location:
|
||||
row: 10
|
||||
column: 63
|
||||
fix:
|
||||
content:
|
||||
- "(x in nums and \"1\" or \"0\" for x in range(123))"
|
||||
location:
|
||||
row: 10
|
||||
column: 12
|
||||
end_location:
|
||||
row: 10
|
||||
column: 63
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 44
|
||||
fix:
|
||||
content:
|
||||
- "(isinstance(v, dict) for v in nums)"
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 44
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 12
|
||||
column: 13
|
||||
end_location:
|
||||
row: 12
|
||||
column: 35
|
||||
fix:
|
||||
content:
|
||||
- (v for v in nums)
|
||||
location:
|
||||
row: 12
|
||||
column: 13
|
||||
end_location:
|
||||
row: 12
|
||||
column: 35
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: set
|
||||
location:
|
||||
row: 15
|
||||
column: 7
|
||||
end_location:
|
||||
row: 15
|
||||
column: 43
|
||||
fix:
|
||||
content:
|
||||
- " {x % 2 == 0 for x in nums} "
|
||||
location:
|
||||
row: 15
|
||||
column: 7
|
||||
end_location:
|
||||
row: 15
|
||||
column: 43
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: dict
|
||||
location:
|
||||
row: 16
|
||||
column: 7
|
||||
end_location:
|
||||
row: 16
|
||||
column: 43
|
||||
fix:
|
||||
content:
|
||||
- " {v: v**2 for v in nums} "
|
||||
location:
|
||||
row: 16
|
||||
column: 7
|
||||
end_location:
|
||||
row: 16
|
||||
column: 43
|
||||
parent: ~
|
||||
- kind:
|
||||
UnnecessaryMap:
|
||||
obj_type: generator
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 24
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
|
||||
27
crates/ruff/src/rules/flake8_django/mod.rs
Normal file
27
crates/ruff/src/rules/flake8_django/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Rules from [django-flake8](https://pypi.org/project/flake8-django/)
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_yaml_snapshot, settings};
|
||||
|
||||
#[test_case(Rule::ModelStringFieldNullable, Path::new("DJ001.py"); "DJ001")]
|
||||
#[test_case(Rule::ModelDunderStr, Path::new("DJ008.py"); "DJ008")]
|
||||
#[test_case(Rule::ReceiverDecoratorChecker, Path::new("DJ013.py"); "DJ013")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_django").join(path).as_path(),
|
||||
&settings::Settings::for_rule(rule_code),
|
||||
)?;
|
||||
assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
20
crates/ruff/src/rules/flake8_django/rules/helpers.rs
Normal file
20
crates/ruff/src/rules/flake8_django/rules/helpers.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use rustpython_parser::ast::Expr;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// Return `true` if a Python class appears to be a Django model based on a base class.
|
||||
pub fn is_model(checker: &Checker, base: &Expr) -> bool {
|
||||
checker.resolve_call_path(base).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["django", "db", "models", "Model"]
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_model_field_name<'a>(checker: &'a Checker, expr: &'a Expr) -> Option<&'a str> {
|
||||
checker.resolve_call_path(expr).and_then(|call_path| {
|
||||
let call_path = call_path.as_slice();
|
||||
if !call_path.starts_with(&["django", "db", "models"]) {
|
||||
return None;
|
||||
}
|
||||
call_path.last().copied()
|
||||
})
|
||||
}
|
||||
8
crates/ruff/src/rules/flake8_django/rules/mod.rs
Normal file
8
crates/ruff/src/rules/flake8_django/rules/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod helpers;
|
||||
mod model_dunder_str;
|
||||
mod model_string_field_nullable;
|
||||
mod receiver_decorator_checker;
|
||||
|
||||
pub use model_dunder_str::{model_dunder_str, ModelDunderStr};
|
||||
pub use model_string_field_nullable::{model_string_field_nullable, ModelStringFieldNullable};
|
||||
pub use receiver_decorator_checker::{receiver_decorator_checker, ReceiverDecoratorChecker};
|
||||
123
crates/ruff/src/rules/flake8_django/rules/model_dunder_str.rs
Normal file
123
crates/ruff/src/rules/flake8_django/rules/model_dunder_str.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use rustpython_parser::ast::{Constant, Expr, StmtKind};
|
||||
use rustpython_parser::ast::{ExprKind, Stmt};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that `__str__` method is defined in Django models.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Django models should define `__str__` method to return a string representation
|
||||
/// of the model instance, as Django calls this method to display the object in
|
||||
/// the Django Admin and elsewhere.
|
||||
///
|
||||
/// Models without `__str__` method will display a non-meaningful representation
|
||||
/// of the object in the Django Admin.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from django.db import models
|
||||
///
|
||||
/// class MyModel(models.Model):
|
||||
/// field = models.CharField(max_length=255)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from django.db import models
|
||||
///
|
||||
/// class MyModel(models.Model):
|
||||
/// field = models.CharField(max_length=255)
|
||||
///
|
||||
/// def __str__(self):
|
||||
/// return f"{self.field}"
|
||||
/// ```
|
||||
pub struct ModelDunderStr;
|
||||
);
|
||||
impl Violation for ModelDunderStr {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Model does not define `__str__` method")
|
||||
}
|
||||
}
|
||||
|
||||
/// DJ008
|
||||
pub fn model_dunder_str(
|
||||
checker: &Checker,
|
||||
bases: &[Expr],
|
||||
body: &[Stmt],
|
||||
class_location: &Stmt,
|
||||
) -> Option<Diagnostic> {
|
||||
if !checker_applies(checker, bases, body) {
|
||||
return None;
|
||||
}
|
||||
if !has_dunder_method(body) {
|
||||
return Some(Diagnostic::new(
|
||||
ModelDunderStr,
|
||||
Range::from_located(class_location),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn has_dunder_method(body: &[Stmt]) -> bool {
|
||||
body.iter().any(|val| match &val.node {
|
||||
StmtKind::FunctionDef { name, .. } => {
|
||||
if name == "__str__" {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn checker_applies(checker: &Checker, bases: &[Expr], body: &[Stmt]) -> bool {
|
||||
for base in bases.iter() {
|
||||
if is_model_abstract(body) {
|
||||
continue;
|
||||
}
|
||||
if helpers::is_model(checker, base) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if class is abstract, in terms of Django model inheritance.
|
||||
fn is_model_abstract(body: &[Stmt]) -> bool {
|
||||
for element in body.iter() {
|
||||
let StmtKind::ClassDef {name, body, ..} = &element.node else {
|
||||
continue
|
||||
};
|
||||
if name != "Meta" {
|
||||
continue;
|
||||
}
|
||||
for element in body.iter() {
|
||||
let StmtKind::Assign {targets, value, ..} = &element.node else {
|
||||
continue;
|
||||
};
|
||||
for target in targets.iter() {
|
||||
let ExprKind::Name {id , ..} = &target.node else {
|
||||
continue;
|
||||
};
|
||||
if id != "abstract" {
|
||||
continue;
|
||||
}
|
||||
let ExprKind::Constant{value: Constant::Bool(true), ..} = &value.node else {
|
||||
continue;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use super::helpers;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::Constant::Bool;
|
||||
use rustpython_parser::ast::{Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks nullable string-based fields (like `CharField` and `TextField`)
|
||||
/// in Django models.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If a string-based field is nullable, then your model will have two possible
|
||||
/// representations for "no data": `None` and the empty string. This can lead to
|
||||
/// confusion, as clients of the API have to check for both `None` and the
|
||||
/// empty string when trying to determine if the field has data.
|
||||
///
|
||||
/// The Django convention is to use the empty string in lieu of `None` for
|
||||
/// string-based fields.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from django.db import models
|
||||
///
|
||||
/// class MyModel(models.Model):
|
||||
/// field = models.CharField(max_length=255, null=True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from django.db import models
|
||||
///
|
||||
/// class MyModel(models.Model):
|
||||
/// field = models.CharField(max_length=255, default="")
|
||||
/// ```
|
||||
pub struct ModelStringFieldNullable {
|
||||
pub field_name: String,
|
||||
}
|
||||
);
|
||||
impl Violation for ModelStringFieldNullable {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ModelStringFieldNullable { field_name } = self;
|
||||
format!("Avoid using `null=True` on string-based fields such as {field_name}")
|
||||
}
|
||||
}
|
||||
|
||||
const NOT_NULL_TRUE_FIELDS: [&str; 6] = [
|
||||
"CharField",
|
||||
"TextField",
|
||||
"SlugField",
|
||||
"EmailField",
|
||||
"FilePathField",
|
||||
"URLField",
|
||||
];
|
||||
|
||||
/// DJ001
|
||||
pub fn model_string_field_nullable(
|
||||
checker: &Checker,
|
||||
bases: &[Expr],
|
||||
body: &[Stmt],
|
||||
) -> Vec<Diagnostic> {
|
||||
if !bases.iter().any(|base| helpers::is_model(checker, base)) {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut errors = Vec::new();
|
||||
for statement in body.iter() {
|
||||
let StmtKind::Assign {value, ..} = &statement.node else {
|
||||
continue
|
||||
};
|
||||
if let Some(field_name) = check_nullable_field(checker, value) {
|
||||
errors.push(Diagnostic::new(
|
||||
ModelStringFieldNullable {
|
||||
field_name: field_name.to_string(),
|
||||
},
|
||||
Range::from_located(value),
|
||||
));
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
||||
fn check_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a str> {
|
||||
let ExprKind::Call {func, keywords, ..} = &value.node else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(valid_field_name) = helpers::get_model_field_name(checker, func) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !NOT_NULL_TRUE_FIELDS.contains(&valid_field_name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut null_key = false;
|
||||
let mut blank_key = false;
|
||||
let mut unique_key = false;
|
||||
for keyword in keywords.iter() {
|
||||
let ExprKind::Constant {value: Bool(true), ..} = &keyword.node.value.node else {
|
||||
continue
|
||||
};
|
||||
let Some(argument) = &keyword.node.arg else {
|
||||
continue
|
||||
};
|
||||
match argument.as_str() {
|
||||
"blank" => blank_key = true,
|
||||
"null" => null_key = true,
|
||||
"unique" => unique_key = true,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
if blank_key && unique_key {
|
||||
return None;
|
||||
}
|
||||
if !null_key {
|
||||
return None;
|
||||
}
|
||||
Some(valid_field_name)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use rustpython_parser::ast::{Expr, ExprKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::types::{CallPath, Range};
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks that Django's `@receiver` decorator is listed first, prior to
|
||||
/// any other decorators.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Django's `@receiver` decorator is special in that it does not return
|
||||
/// a wrapped function. Rather, `@receiver` connects the decorated function
|
||||
/// to a signal. If any other decorators are listed before `@receiver`,
|
||||
/// the decorated function will not be connected to the signal.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from django.dispatch import receiver
|
||||
/// from django.db.models.signals import post_save
|
||||
///
|
||||
/// @transaction.atomic
|
||||
/// @receiver(post_save, sender=MyModel)
|
||||
/// def my_handler(sender, instance, created, **kwargs):
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from django.dispatch import receiver
|
||||
/// from django.db.models.signals import post_save
|
||||
///
|
||||
/// @receiver(post_save, sender=MyModel)
|
||||
/// @transaction.atomic
|
||||
/// def my_handler(sender, instance, created, **kwargs):
|
||||
/// pass
|
||||
/// ```
|
||||
pub struct ReceiverDecoratorChecker;
|
||||
);
|
||||
impl Violation for ReceiverDecoratorChecker {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("`@receiver` decorator must be on top of all the other decorators")
|
||||
}
|
||||
}
|
||||
|
||||
/// DJ013
|
||||
pub fn receiver_decorator_checker<'a, F>(
|
||||
decorator_list: &'a [Expr],
|
||||
resolve_call_path: F,
|
||||
) -> Option<Diagnostic>
|
||||
where
|
||||
F: Fn(&'a Expr) -> Option<CallPath<'a>>,
|
||||
{
|
||||
for (i, decorator) in decorator_list.iter().enumerate() {
|
||||
if i == 0 {
|
||||
continue;
|
||||
}
|
||||
let ExprKind::Call{ func, ..} = &decorator.node else {
|
||||
continue;
|
||||
};
|
||||
if resolve_call_path(func).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["django", "dispatch", "receiver"]
|
||||
}) {
|
||||
return Some(Diagnostic::new(
|
||||
ReceiverDecoratorChecker,
|
||||
Range::from_located(decorator),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_django/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: CharField
|
||||
location:
|
||||
row: 7
|
||||
column: 16
|
||||
end_location:
|
||||
row: 7
|
||||
column: 59
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: TextField
|
||||
location:
|
||||
row: 8
|
||||
column: 16
|
||||
end_location:
|
||||
row: 8
|
||||
column: 59
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: SlugField
|
||||
location:
|
||||
row: 9
|
||||
column: 16
|
||||
end_location:
|
||||
row: 9
|
||||
column: 59
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: EmailField
|
||||
location:
|
||||
row: 10
|
||||
column: 17
|
||||
end_location:
|
||||
row: 10
|
||||
column: 61
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: FilePathField
|
||||
location:
|
||||
row: 11
|
||||
column: 20
|
||||
end_location:
|
||||
row: 11
|
||||
column: 67
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: URLField
|
||||
location:
|
||||
row: 12
|
||||
column: 15
|
||||
end_location:
|
||||
row: 12
|
||||
column: 57
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: CharField
|
||||
location:
|
||||
row: 16
|
||||
column: 16
|
||||
end_location:
|
||||
row: 16
|
||||
column: 64
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: CharField
|
||||
location:
|
||||
row: 17
|
||||
column: 16
|
||||
end_location:
|
||||
row: 17
|
||||
column: 56
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: SlugField
|
||||
location:
|
||||
row: 18
|
||||
column: 16
|
||||
end_location:
|
||||
row: 18
|
||||
column: 59
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: EmailField
|
||||
location:
|
||||
row: 19
|
||||
column: 17
|
||||
end_location:
|
||||
row: 19
|
||||
column: 61
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: FilePathField
|
||||
location:
|
||||
row: 20
|
||||
column: 20
|
||||
end_location:
|
||||
row: 20
|
||||
column: 67
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelStringFieldNullable:
|
||||
field_name: URLField
|
||||
location:
|
||||
row: 21
|
||||
column: 15
|
||||
end_location:
|
||||
row: 21
|
||||
column: 57
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_django/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ModelDunderStr: ~
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelDunderStr: ~
|
||||
location:
|
||||
row: 21
|
||||
column: 0
|
||||
end_location:
|
||||
row: 33
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ModelDunderStr: ~
|
||||
location:
|
||||
row: 36
|
||||
column: 0
|
||||
end_location:
|
||||
row: 47
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_django/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ReceiverDecoratorChecker: ~
|
||||
location:
|
||||
row: 15
|
||||
column: 1
|
||||
end_location:
|
||||
row: 15
|
||||
column: 35
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -24,10 +24,9 @@ define_violation!(
|
||||
/// `__init__.py` file is typically meant to be a regular package, and
|
||||
/// the absence of the `__init__.py` file is probably an oversight.
|
||||
///
|
||||
/// Note that namespace packages can be specified via the
|
||||
/// [`namespace-packages`](https://github.com/charliermarsh/ruff#namespace-packages)
|
||||
/// configuration option. Adding a namespace package to the configuration
|
||||
/// will suppress this violation for a given package.
|
||||
/// ## Options
|
||||
///
|
||||
/// * `namespace-packages`
|
||||
pub struct ImplicitNamespacePackage {
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ mod tests {
|
||||
use crate::{assert_yaml_snapshot, settings};
|
||||
|
||||
#[test_case(Rule::DupeClassFieldDefinitions, Path::new("PIE794.py"); "PIE794")]
|
||||
#[test_case(Rule::NoUnnecessaryDictKwargs, Path::new("PIE804.py"); "PIE804")]
|
||||
#[test_case(Rule::UnnecessaryDictKwargs, Path::new("PIE804.py"); "PIE804")]
|
||||
#[test_case(Rule::SingleStartsEndsWith, Path::new("PIE810.py"); "PIE810")]
|
||||
#[test_case(Rule::NoUnnecessaryPass, Path::new("PIE790.py"); "PIE790")]
|
||||
#[test_case(Rule::NoUnnecessarySpread, Path::new("PIE800.py"); "PIE800")]
|
||||
#[test_case(Rule::UnnecessaryPass, Path::new("PIE790.py"); "PIE790")]
|
||||
#[test_case(Rule::UnnecessarySpread, Path::new("PIE800.py"); "PIE800")]
|
||||
#[test_case(Rule::PreferListBuiltin, Path::new("PIE807.py"); "PIE807")]
|
||||
#[test_case(Rule::PreferUniqueEnums, Path::new("PIE796.py"); "PIE796")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -16,9 +16,9 @@ use crate::registry::Diagnostic;
|
||||
use crate::violation::{AlwaysAutofixableViolation, Violation};
|
||||
|
||||
define_violation!(
|
||||
pub struct NoUnnecessaryPass;
|
||||
pub struct UnnecessaryPass;
|
||||
);
|
||||
impl AlwaysAutofixableViolation for NoUnnecessaryPass {
|
||||
impl AlwaysAutofixableViolation for UnnecessaryPass {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary `pass` statement")
|
||||
@@ -59,9 +59,9 @@ impl Violation for PreferUniqueEnums {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct NoUnnecessarySpread;
|
||||
pub struct UnnecessarySpread;
|
||||
);
|
||||
impl Violation for NoUnnecessarySpread {
|
||||
impl Violation for UnnecessarySpread {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary spread `**`")
|
||||
@@ -82,9 +82,9 @@ impl Violation for SingleStartsEndsWith {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct NoUnnecessaryDictKwargs;
|
||||
pub struct UnnecessaryDictKwargs;
|
||||
);
|
||||
impl Violation for NoUnnecessaryDictKwargs {
|
||||
impl Violation for UnnecessaryDictKwargs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary `dict` kwargs")
|
||||
@@ -124,7 +124,7 @@ pub fn no_unnecessary_pass(checker: &mut Checker, body: &[Stmt]) {
|
||||
) {
|
||||
if matches!(pass_stmt.node, StmtKind::Pass) {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(NoUnnecessaryPass, Range::from_located(pass_stmt));
|
||||
Diagnostic::new(UnnecessaryPass, Range::from_located(pass_stmt));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(index) = match_trailing_comment(pass_stmt, checker.locator) {
|
||||
diagnostic.amend(Fix::deletion(
|
||||
@@ -275,7 +275,7 @@ pub fn no_unnecessary_spread(checker: &mut Checker, keys: &[Option<Expr>], value
|
||||
// We only care about when the key is None which indicates a spread `**`
|
||||
// inside a dict.
|
||||
if let ExprKind::Dict { .. } = value.node {
|
||||
let diagnostic = Diagnostic::new(NoUnnecessarySpread, Range::from_located(value));
|
||||
let diagnostic = Diagnostic::new(UnnecessarySpread, Range::from_located(value));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -307,7 +307,7 @@ pub fn no_unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs: &[
|
||||
(keys.len() == 1 && keys[0].is_none())
|
||||
{
|
||||
let diagnostic =
|
||||
Diagnostic::new(NoUnnecessaryDictKwargs, Range::from_located(expr));
|
||||
Diagnostic::new(UnnecessaryDictKwargs, Range::from_located(expr));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_pie/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_pie/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 4
|
||||
column: 4
|
||||
@@ -21,7 +21,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 4
|
||||
@@ -39,7 +39,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 14
|
||||
column: 4
|
||||
@@ -57,7 +57,7 @@ expression: diagnostics
|
||||
column: 10
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 21
|
||||
column: 4
|
||||
@@ -75,7 +75,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 28
|
||||
column: 4
|
||||
@@ -93,7 +93,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 35
|
||||
column: 4
|
||||
@@ -111,7 +111,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 42
|
||||
column: 4
|
||||
@@ -129,7 +129,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 50
|
||||
column: 4
|
||||
@@ -147,7 +147,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 58
|
||||
column: 4
|
||||
@@ -165,7 +165,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 65
|
||||
column: 4
|
||||
@@ -183,7 +183,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 74
|
||||
column: 4
|
||||
@@ -201,7 +201,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 79
|
||||
column: 4
|
||||
@@ -219,7 +219,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 83
|
||||
column: 4
|
||||
@@ -237,7 +237,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 87
|
||||
column: 4
|
||||
@@ -255,7 +255,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 92
|
||||
column: 4
|
||||
@@ -273,7 +273,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 96
|
||||
column: 4
|
||||
@@ -291,7 +291,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryPass: ~
|
||||
UnnecessaryPass: ~
|
||||
location:
|
||||
row: 101
|
||||
column: 4
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_pie/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_pie/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
UnnecessarySpread: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 13
|
||||
@@ -13,7 +13,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
UnnecessarySpread: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 14
|
||||
@@ -23,7 +23,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
UnnecessarySpread: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 10
|
||||
@@ -33,7 +33,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
UnnecessarySpread: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 18
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
source: src/rules/flake8_pie/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_pie/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
UnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
@@ -13,7 +13,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
UnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
@@ -23,7 +23,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
UnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
@@ -33,7 +33,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
UnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
@@ -43,7 +43,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
UnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
|
||||
@@ -14,6 +14,10 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.pyi"))]
|
||||
#[test_case(Rule::PrefixTypeParams, Path::new("PYI001.py"))]
|
||||
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.pyi"))]
|
||||
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.py"))]
|
||||
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))]
|
||||
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
7
crates/ruff/src/rules/flake8_pyi/rules/mod.rs
Normal file
7
crates/ruff/src/rules/flake8_pyi/rules/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub use prefix_type_params::{prefix_type_params, PrefixTypeParams};
|
||||
pub use unrecognized_platform::{
|
||||
unrecognized_platform, UnrecognizedPlatformCheck, UnrecognizedPlatformName,
|
||||
};
|
||||
|
||||
mod prefix_type_params;
|
||||
mod unrecognized_platform;
|
||||
154
crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs
Normal file
154
crates/ruff/src/rules/flake8_pyi/rules/unrecognized_platform.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind};
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Check for unrecognized `sys.platform` checks. Platform checks should be
|
||||
/// simple string comparisons.
|
||||
///
|
||||
/// **Note**: this rule is only enabled in `.pyi` stub files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Some `sys.platform` checks are too complex for type checkers to
|
||||
/// understand, and thus result in false positives. `sys.platform` checks
|
||||
/// should be simple string comparisons, like `sys.platform == "linux"`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// if sys.platform.startswith("linux"):
|
||||
/// # Linux specific definitions
|
||||
/// else:
|
||||
/// # Posix specific definitions
|
||||
/// ```
|
||||
///
|
||||
/// Instead, use a simple string comparison, such as `==` or `!=`:
|
||||
/// ```python
|
||||
/// if sys.platform == "linux":
|
||||
/// # Linux specific definitions
|
||||
/// else:
|
||||
/// # Posix specific definitions
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
|
||||
pub struct UnrecognizedPlatformCheck;
|
||||
);
|
||||
impl Violation for UnrecognizedPlatformCheck {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unrecognized sys.platform check")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Check for unrecognized platform names in `sys.platform` checks.
|
||||
///
|
||||
/// **Note**: this rule is only enabled in `.pyi` stub files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If a `sys.platform` check compares to a platform name outside of a
|
||||
/// small set of known platforms (e.g. "linux", "win32", etc.), it's likely
|
||||
/// a typo or a platform name that is not recognized by type checkers.
|
||||
///
|
||||
/// The list of known platforms is: "linux", "win32", "cygwin", "darwin".
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// if sys.platform == "linus":
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// if sys.platform == "linux":
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 484](https://peps.python.org/pep-0484/#version-and-platform-checking)
|
||||
pub struct UnrecognizedPlatformName {
|
||||
pub platform: String,
|
||||
}
|
||||
);
|
||||
impl Violation for UnrecognizedPlatformName {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnrecognizedPlatformName { platform } = self;
|
||||
format!("Unrecognized platform `{platform}`")
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI007, PYI008
|
||||
pub fn unrecognized_platform(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
left: &Expr,
|
||||
ops: &[Cmpop],
|
||||
comparators: &[Expr],
|
||||
) {
|
||||
let ([op], [right]) = (ops, comparators) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let diagnostic_unrecognized_platform_check =
|
||||
Diagnostic::new(UnrecognizedPlatformCheck, Range::from_located(expr));
|
||||
if !checker.resolve_call_path(left).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["sys", "platform"]
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// "in" might also make sense but we don't currently have one.
|
||||
if !matches!(op, Cmpop::Eq | Cmpop::NotEq)
|
||||
&& checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UnrecognizedPlatformCheck)
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic_unrecognized_platform_check);
|
||||
return;
|
||||
}
|
||||
|
||||
match &right.node {
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} => {
|
||||
// Other values are possible but we don't need them right now.
|
||||
// This protects against typos.
|
||||
if !["linux", "win32", "cygwin", "darwin"].contains(&value.as_str())
|
||||
&& checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UnrecognizedPlatformName)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnrecognizedPlatformName {
|
||||
platform: value.clone(),
|
||||
},
|
||||
Range::from_located(right),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::UnrecognizedPlatformCheck)
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(diagnostic_unrecognized_platform_check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnrecognizedPlatformCheck: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 3
|
||||
end_location:
|
||||
row: 7
|
||||
column: 28
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnrecognizedPlatformCheck: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 3
|
||||
end_location:
|
||||
row: 9
|
||||
column: 19
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
UnrecognizedPlatformCheck: ~
|
||||
location:
|
||||
row: 11
|
||||
column: 3
|
||||
end_location:
|
||||
row: 11
|
||||
column: 24
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
UnrecognizedPlatformName:
|
||||
platform: linus
|
||||
location:
|
||||
row: 3
|
||||
column: 19
|
||||
end_location:
|
||||
row: 3
|
||||
column: 26
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{Constant, Expr, ExprContext, ExprKind};
|
||||
|
||||
use super::super::types;
|
||||
use super::helpers::{is_pytest_parametrize, split_names};
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::helpers::{create_expr, unparse_expr};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::source_code::Generator;
|
||||
use crate::violation::{AlwaysAutofixableViolation, Violation};
|
||||
|
||||
use super::super::types;
|
||||
use super::helpers::{is_pytest_parametrize, split_names};
|
||||
|
||||
define_violation!(
|
||||
pub struct ParametrizeNamesWrongType {
|
||||
pub expected: types::ParametrizeNameType,
|
||||
@@ -99,24 +100,25 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(
|
||||
&create_expr(ExprKind::Tuple {
|
||||
elts: names
|
||||
.iter()
|
||||
.map(|&name| {
|
||||
create_expr(ExprKind::Constant {
|
||||
value: Constant::Str(name.to_string()),
|
||||
kind: None,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
1,
|
||||
);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
format!(
|
||||
"({})",
|
||||
unparse_expr(
|
||||
&create_expr(ExprKind::Tuple {
|
||||
elts: names
|
||||
.iter()
|
||||
.map(|&name| {
|
||||
create_expr(ExprKind::Constant {
|
||||
value: Constant::Str(name.to_string()),
|
||||
kind: None,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
checker.stylist,
|
||||
)
|
||||
),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
@@ -224,16 +226,17 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(
|
||||
&create_expr(ExprKind::Tuple {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
1, // so tuple is generated with parentheses
|
||||
);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
format!(
|
||||
"({})",
|
||||
unparse_expr(
|
||||
&create_expr(ExprKind::Tuple {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
checker.stylist,
|
||||
)
|
||||
),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -14,13 +14,16 @@ use crate::violation::AlwaysAutofixableViolation;
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for inline strings that use single quotes or double quotes,
|
||||
/// depending on the value of the [`inline-quotes`](https://github.com/charliermarsh/ruff#inline-quotes)
|
||||
/// setting.
|
||||
/// depending on the value of the [`flake8-quotes.inline-quotes`] option.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use either single or double quotes for inline
|
||||
/// strings, but be consistent.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// * `flake8-quotes.inline-quotes`
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo = 'bar'
|
||||
@@ -56,13 +59,17 @@ impl AlwaysAutofixableViolation for BadQuotesInlineString {
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for multiline strings that use single quotes or double quotes,
|
||||
/// depending on the value of the [`multiline-quotes`](https://github.com/charliermarsh/ruff#multiline-quotes)
|
||||
/// depending on the value of the [`flake8-quotes.multiline-quotes`]
|
||||
/// setting.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use either single or double quotes for multiline
|
||||
/// strings, but be consistent.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// * `flake8-quotes.multiline-quotes`
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// foo = '''
|
||||
@@ -101,13 +108,17 @@ impl AlwaysAutofixableViolation for BadQuotesMultilineString {
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for docstrings that use single quotes or double quotes, depending on the value of the [`docstring-quotes`](https://github.com/charliermarsh/ruff#docstring-quotes)
|
||||
/// setting.
|
||||
/// Checks for docstrings that use single quotes or double quotes, depending
|
||||
/// on the value of the [`flake8-quotes.docstring-quotes`] setting.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Consistency is good. Use either single or double quotes for docstring
|
||||
/// strings, but be consistent.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// * `flake8-quotes.docstring-quotes`
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// '''
|
||||
|
||||
@@ -21,15 +21,23 @@ impl Violation for PrivateMemberAccess {
|
||||
}
|
||||
}
|
||||
|
||||
const VALID_IDS: [&str; 3] = ["self", "cls", "mcs"];
|
||||
|
||||
/// SLF001
|
||||
pub fn private_member_access(checker: &mut Checker, expr: &Expr) {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
|
||||
if !attr.ends_with("__") && (attr.starts_with('_') || attr.starts_with("__")) {
|
||||
let call_path = collect_call_path(value);
|
||||
if VALID_IDS.iter().any(|id| call_path.as_slice() == [*id]) {
|
||||
return;
|
||||
if let ExprKind::Call { func, .. } = &value.node {
|
||||
let call_path = collect_call_path(func);
|
||||
if call_path.as_slice() == ["super"] {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let call_path = collect_call_path(value);
|
||||
if call_path.as_slice() == ["self"]
|
||||
|| call_path.as_slice() == ["cls"]
|
||||
|| call_path.as_slice() == ["mcs"]
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -13,8 +13,8 @@ mod tests {
|
||||
use crate::{assert_yaml_snapshot, settings};
|
||||
|
||||
#[test_case(Rule::DuplicateIsinstanceCall, Path::new("SIM101.py"); "SIM101")]
|
||||
#[test_case(Rule::NestedIfStatements, Path::new("SIM102.py"); "SIM102")]
|
||||
#[test_case(Rule::ReturnBoolConditionDirectly, Path::new("SIM103.py"); "SIM103")]
|
||||
#[test_case(Rule::CollapsibleIf, Path::new("SIM102.py"); "SIM102")]
|
||||
#[test_case(Rule::NeedlessBool, Path::new("SIM103.py"); "SIM103")]
|
||||
#[test_case(Rule::UseContextlibSuppress, Path::new("SIM105.py"); "SIM105")]
|
||||
#[test_case(Rule::ReturnInTryExceptFinally, Path::new("SIM107.py"); "SIM107")]
|
||||
#[test_case(Rule::UseTernaryOperator, Path::new("SIM108.py"); "SIM108")]
|
||||
@@ -37,6 +37,7 @@ mod tests {
|
||||
#[test_case(Rule::AndFalse, Path::new("SIM223.py"); "SIM223")]
|
||||
#[test_case(Rule::YodaConditions, Path::new("SIM300.py"); "SIM300")]
|
||||
#[test_case(Rule::DictGetWithDefault, Path::new("SIM401.py"); "SIM401")]
|
||||
#[test_case(Rule::IfWithSameArms, Path::new("SIM114.py"); "SIM114")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
use rustpython_parser::ast::{
|
||||
Comprehension, Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind, Unaryop,
|
||||
Cmpop, Comprehension, Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind, Unaryop,
|
||||
};
|
||||
|
||||
use crate::ast::helpers::{create_expr, create_stmt, unparse_stmt};
|
||||
@@ -260,6 +260,36 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling:
|
||||
} = &loop_info.test.node
|
||||
{
|
||||
*operand.clone()
|
||||
} else if let ExprKind::Compare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
} = &loop_info.test.node
|
||||
{
|
||||
if ops.len() == 1 && comparators.len() == 1 {
|
||||
let op = match &ops[0] {
|
||||
Cmpop::Eq => Cmpop::NotEq,
|
||||
Cmpop::NotEq => Cmpop::Eq,
|
||||
Cmpop::Lt => Cmpop::GtE,
|
||||
Cmpop::LtE => Cmpop::Gt,
|
||||
Cmpop::Gt => Cmpop::LtE,
|
||||
Cmpop::GtE => Cmpop::Lt,
|
||||
Cmpop::Is => Cmpop::IsNot,
|
||||
Cmpop::IsNot => Cmpop::Is,
|
||||
Cmpop::In => Cmpop::NotIn,
|
||||
Cmpop::NotIn => Cmpop::In,
|
||||
};
|
||||
create_expr(ExprKind::Compare {
|
||||
left: left.clone(),
|
||||
ops: vec![op],
|
||||
comparators: vec![comparators[0].clone()],
|
||||
})
|
||||
} else {
|
||||
create_expr(ExprKind::UnaryOp {
|
||||
op: Unaryop::Not,
|
||||
operand: Box::new(loop_info.test.clone()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
create_expr(ExprKind::UnaryOp {
|
||||
op: Unaryop::Not,
|
||||
|
||||
@@ -3,7 +3,7 @@ use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprContext, ExprKind, Stmt,
|
||||
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::comparable::ComparableExpr;
|
||||
use crate::ast::comparable::{ComparableExpr, ComparableStmt};
|
||||
use crate::ast::helpers::{
|
||||
contains_call_path, contains_effect, create_expr, create_stmt, first_colon_range, has_comments,
|
||||
has_comments_in, unparse_expr, unparse_stmt,
|
||||
@@ -15,12 +15,30 @@ use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_simplify::rules::fix_if;
|
||||
use crate::violation::{AutofixKind, Availability, Violation};
|
||||
|
||||
fn compare_expr(expr1: &ComparableExpr, expr2: &ComparableExpr) -> bool {
|
||||
expr1.eq(expr2)
|
||||
}
|
||||
|
||||
fn compare_stmt(stmt1: &ComparableStmt, stmt2: &ComparableStmt) -> bool {
|
||||
stmt1.eq(stmt2)
|
||||
}
|
||||
|
||||
fn compare_body(body1: &[Stmt], body2: &[Stmt]) -> bool {
|
||||
if body1.len() != body2.len() {
|
||||
return false;
|
||||
}
|
||||
body1
|
||||
.iter()
|
||||
.zip(body2.iter())
|
||||
.all(|(stmt1, stmt2)| compare_stmt(&stmt1.into(), &stmt2.into()))
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct NestedIfStatements {
|
||||
pub struct CollapsibleIf {
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl Violation for NestedIfStatements {
|
||||
impl Violation for CollapsibleIf {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
@@ -29,7 +47,7 @@ impl Violation for NestedIfStatements {
|
||||
}
|
||||
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let NestedIfStatements { fixable, .. } = self;
|
||||
let CollapsibleIf { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|_| format!("Combine `if` statements using `and`"))
|
||||
} else {
|
||||
@@ -39,26 +57,24 @@ impl Violation for NestedIfStatements {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct ReturnBoolConditionDirectly {
|
||||
pub struct NeedlessBool {
|
||||
pub condition: String,
|
||||
pub fixable: bool,
|
||||
}
|
||||
);
|
||||
impl Violation for ReturnBoolConditionDirectly {
|
||||
impl Violation for NeedlessBool {
|
||||
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Sometimes));
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ReturnBoolConditionDirectly { condition, .. } = self;
|
||||
let NeedlessBool { condition, .. } = self;
|
||||
format!("Return the condition `{condition}` directly")
|
||||
}
|
||||
|
||||
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||
let ReturnBoolConditionDirectly { fixable, .. } = self;
|
||||
let NeedlessBool { fixable, .. } = self;
|
||||
if *fixable {
|
||||
Some(|ReturnBoolConditionDirectly { condition, .. }| {
|
||||
format!("Replace with `return {condition}`")
|
||||
})
|
||||
Some(|NeedlessBool { condition, .. }| format!("Replace with `return {condition}`"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -92,6 +108,36 @@ impl Violation for UseTernaryOperator {
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ### What it does
|
||||
/// Checks for `if` branches with identical arm bodies.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If multiple arms of an `if` statement have the same body, using `or`
|
||||
/// better signals the intent of the statement.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```python
|
||||
/// if x = 1:
|
||||
/// print("Hello")
|
||||
/// elif x = 2:
|
||||
/// print("Hello")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// if x = 1 or x = 2:
|
||||
/// print("Hello")
|
||||
/// ```
|
||||
pub struct IfWithSameArms;
|
||||
);
|
||||
impl Violation for IfWithSameArms {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Combine `if` branches using logical `or` operator")
|
||||
}
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
pub struct DictGetWithDefault {
|
||||
pub contents: String,
|
||||
@@ -211,7 +257,7 @@ pub fn nested_if_statements(
|
||||
);
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NestedIfStatements { fixable },
|
||||
CollapsibleIf { fixable },
|
||||
colon.map_or_else(
|
||||
|| Range::from_located(stmt),
|
||||
|colon| Range::new(stmt.location, colon.end_location),
|
||||
@@ -288,7 +334,7 @@ pub fn return_bool_condition_directly(checker: &mut Checker, stmt: &Stmt) {
|
||||
&& (matches!(test.node, ExprKind::Compare { .. }) || checker.is_builtin("bool"));
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
ReturnBoolConditionDirectly { condition, fixable },
|
||||
NeedlessBool { condition, fixable },
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||
@@ -451,8 +497,76 @@ pub fn use_ternary_operator(checker: &mut Checker, stmt: &Stmt, parent: Option<&
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn compare_expr(expr1: &ComparableExpr, expr2: &ComparableExpr) -> bool {
|
||||
expr1.eq(expr2)
|
||||
fn get_if_body_pairs<'a>(
|
||||
test: &'a Expr,
|
||||
body: &'a [Stmt],
|
||||
orelse: &'a [Stmt],
|
||||
) -> Vec<(&'a Expr, &'a [Stmt])> {
|
||||
let mut pairs = vec![(test, body)];
|
||||
let mut orelse = orelse;
|
||||
loop {
|
||||
if orelse.len() != 1 {
|
||||
break;
|
||||
}
|
||||
let StmtKind::If { test, body, orelse: orelse_orelse, .. } = &orelse[0].node else {
|
||||
break;
|
||||
};
|
||||
pairs.push((test, body));
|
||||
orelse = orelse_orelse;
|
||||
}
|
||||
pairs
|
||||
}
|
||||
|
||||
/// SIM114
|
||||
pub fn if_with_same_arms(checker: &mut Checker, stmt: &Stmt, parent: Option<&Stmt>) {
|
||||
let StmtKind::If { test, body, orelse } = &stmt.node else {
|
||||
return;
|
||||
};
|
||||
|
||||
// It's part of a bigger if-elif block:
|
||||
// https://github.com/MartinThoma/flake8-simplify/issues/115
|
||||
if let Some(StmtKind::If {
|
||||
orelse: parent_orelse,
|
||||
..
|
||||
}) = parent.map(|parent| &parent.node)
|
||||
{
|
||||
if parent_orelse.len() == 1 && stmt == &parent_orelse[0] {
|
||||
// TODO(charlie): These two cases have the same AST:
|
||||
//
|
||||
// if True:
|
||||
// pass
|
||||
// elif a:
|
||||
// b = 1
|
||||
// else:
|
||||
// b = 2
|
||||
//
|
||||
// if True:
|
||||
// pass
|
||||
// else:
|
||||
// if a:
|
||||
// b = 1
|
||||
// else:
|
||||
// b = 2
|
||||
//
|
||||
// We want to flag the latter, but not the former. Right now, we flag neither.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let if_body_pairs = get_if_body_pairs(test, body, orelse);
|
||||
for i in 0..(if_body_pairs.len() - 1) {
|
||||
let (test, body) = &if_body_pairs[i];
|
||||
let (.., next_body) = &if_body_pairs[i + 1];
|
||||
if compare_body(body, next_body) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
IfWithSameArms,
|
||||
Range::new(
|
||||
if i == 0 { stmt.location } else { test.location },
|
||||
next_body.last().unwrap().end_location.unwrap(),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SIM401
|
||||
|
||||
@@ -5,9 +5,9 @@ pub use ast_bool_op::{
|
||||
pub use ast_expr::{use_capital_environment_variables, UseCapitalEnvironmentVariables};
|
||||
pub use ast_for::{convert_for_loop_to_any_all, ConvertLoopToAll, ConvertLoopToAny};
|
||||
pub use ast_if::{
|
||||
nested_if_statements, return_bool_condition_directly, use_dict_get_with_default,
|
||||
use_ternary_operator, DictGetWithDefault, NestedIfStatements, ReturnBoolConditionDirectly,
|
||||
UseTernaryOperator,
|
||||
if_with_same_arms, nested_if_statements, return_bool_condition_directly,
|
||||
use_dict_get_with_default, use_ternary_operator, CollapsibleIf, DictGetWithDefault,
|
||||
IfWithSameArms, NeedlessBool, UseTernaryOperator,
|
||||
};
|
||||
pub use ast_ifexp::{
|
||||
explicit_false_true_in_ifexpr, explicit_true_false_in_ifexpr, twisted_arms_in_ifexpr,
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 2
|
||||
@@ -24,7 +24,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 7
|
||||
@@ -46,7 +46,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 15
|
||||
@@ -67,7 +67,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: false
|
||||
location:
|
||||
row: 20
|
||||
@@ -78,7 +78,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 26
|
||||
@@ -100,7 +100,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 51
|
||||
@@ -131,7 +131,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 67
|
||||
@@ -162,7 +162,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 83
|
||||
@@ -185,7 +185,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 90
|
||||
@@ -208,7 +208,7 @@ expression: diagnostics
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NestedIfStatements:
|
||||
CollapsibleIf:
|
||||
fixable: true
|
||||
location:
|
||||
row: 117
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
NeedlessBool:
|
||||
condition: a
|
||||
fixable: true
|
||||
location:
|
||||
@@ -23,7 +23,7 @@ expression: diagnostics
|
||||
column: 20
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
NeedlessBool:
|
||||
condition: a == b
|
||||
fixable: true
|
||||
location:
|
||||
@@ -43,7 +43,7 @@ expression: diagnostics
|
||||
column: 20
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
NeedlessBool:
|
||||
condition: b
|
||||
fixable: true
|
||||
location:
|
||||
@@ -63,7 +63,7 @@ expression: diagnostics
|
||||
column: 20
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
NeedlessBool:
|
||||
condition: b
|
||||
fixable: true
|
||||
location:
|
||||
@@ -83,7 +83,7 @@ expression: diagnostics
|
||||
column: 24
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
NeedlessBool:
|
||||
condition: a
|
||||
fixable: false
|
||||
location:
|
||||
@@ -95,7 +95,7 @@ expression: diagnostics
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ReturnBoolConditionDirectly:
|
||||
NeedlessBool:
|
||||
condition: a
|
||||
fixable: false
|
||||
location:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_simplify/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
@@ -108,4 +108,42 @@ expression: diagnostics
|
||||
row: 157
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertLoopToAll:
|
||||
all: return all(x in y for x in iterable)
|
||||
location:
|
||||
row: 162
|
||||
column: 4
|
||||
end_location:
|
||||
row: 164
|
||||
column: 24
|
||||
fix:
|
||||
content:
|
||||
- return all(x in y for x in iterable)
|
||||
location:
|
||||
row: 162
|
||||
column: 4
|
||||
end_location:
|
||||
row: 165
|
||||
column: 15
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertLoopToAll:
|
||||
all: return all(x <= y for x in iterable)
|
||||
location:
|
||||
row: 170
|
||||
column: 4
|
||||
end_location:
|
||||
row: 172
|
||||
column: 24
|
||||
fix:
|
||||
content:
|
||||
- return all(x <= y for x in iterable)
|
||||
location:
|
||||
row: 170
|
||||
column: 4
|
||||
end_location:
|
||||
row: 173
|
||||
column: 15
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 5
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 26
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 23
|
||||
column: 0
|
||||
end_location:
|
||||
row: 36
|
||||
column: 26
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 24
|
||||
column: 4
|
||||
end_location:
|
||||
row: 29
|
||||
column: 26
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 31
|
||||
column: 4
|
||||
end_location:
|
||||
row: 36
|
||||
column: 26
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 38
|
||||
column: 0
|
||||
end_location:
|
||||
row: 56
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
IfWithSameArms: ~
|
||||
location:
|
||||
row: 62
|
||||
column: 5
|
||||
end_location:
|
||||
row: 65
|
||||
column: 14
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -20,6 +20,23 @@ pub struct ApiBan {
|
||||
}
|
||||
|
||||
define_violation!(
|
||||
/// ## What it does
|
||||
/// Checks for banned imports.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Projects may want to ensure that specific modules or module members are
|
||||
/// not be imported or accessed.
|
||||
///
|
||||
/// Security or other company policies may be a reason to impose
|
||||
/// restrictions on importing external Python libraries. In some cases,
|
||||
/// projects may adopt conventions around the use of certain modules or
|
||||
/// module members that are not enforceable by the language itself.
|
||||
///
|
||||
/// This rule enforces certain import conventions project-wide in an
|
||||
/// automatic way.
|
||||
///
|
||||
/// ## Options
|
||||
/// * `flake8-tidy-imports.banned-api`
|
||||
pub struct BannedApi {
|
||||
pub name: String,
|
||||
pub message: String,
|
||||
|
||||
@@ -48,11 +48,9 @@ define_violation!(
|
||||
/// > from .sibling import example
|
||||
/// > ```
|
||||
///
|
||||
/// Note that degree of strictness packages can be specified via the
|
||||
/// [`strictness`](https://github.com/charliermarsh/ruff#strictness)
|
||||
/// configuration option, which allows banning all relative imports (`strictness = "all"`)
|
||||
/// or only those that extend into the parent module or beyond (`strictness = "parents"`,
|
||||
/// the default).
|
||||
/// ## Options
|
||||
///
|
||||
/// * `flake8-tidy-imports.ban-relative-imports`
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -29,6 +29,7 @@ mod tests {
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_9.py"); "TCH004_9")]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"); "TCH004_10")]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"); "TCH004_11")]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"); "TCH004_12")]
|
||||
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"); "TCH005")]
|
||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("strict.py"); "strict")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
RuntimeImportInTypeCheckingBlock:
|
||||
full_name: collections.abc.Callable
|
||||
location:
|
||||
row: 6
|
||||
column: 32
|
||||
end_location:
|
||||
row: 6
|
||||
column: 40
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_type_checking/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: src/rules/flake8_type_checking/mod.rs
|
||||
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user